Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9ee402af4b | ||
|
00f788fad5 | ||
|
1ecfa19fd4 |
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,7 +1,3 @@
|
||||
.idea
|
||||
/wp-go.iml
|
||||
/config.yaml
|
||||
err.log
|
||||
/plugins/
|
||||
/config.json
|
||||
go.sum
|
||||
wp-go.iml
|
||||
config.yaml
|
@ -1,7 +1,8 @@
|
||||
FROM golang:1.22.2-alpine as gobulidIso
|
||||
FROM golang:latest as gobulidIso
|
||||
COPY ./ /go/src/wp-go
|
||||
WORKDIR /go/src/wp-go
|
||||
RUN go build -ldflags "-w" -tags netgo -o wp-go app/cmd/main.go
|
||||
ENV GOPROXY="https://goproxy.cn"
|
||||
RUN go build -ldflags "-w" -tags netgo -o wp-go internal/cmd/main.go
|
||||
|
||||
FROM alpine:latest
|
||||
WORKDIR /opt/wp-go
|
||||
|
79
README.md
79
README.md
@ -1,87 +1,18 @@
|
||||
## wp-go
|
||||
|
||||
[en readme](https://github.com/fthvgb1/wp-go/blob/master/readme_en.md)
|
||||
|
||||
一个go写的WordPress的前端,功能比较简单,只有列表页和详情页,rss2,主题只有twentyfifteen和twentyseventeen两套主题,插件的话只有一个简单的列表页的摘要生成和enlighter代码高亮。本身只用于展示文章及评论。要求go的版本在1.20以上,越新越好。。。
|
||||
一个go写的WordPress的前端,功能比较简单,只有列表页和详情页,rss2,主题只有twentyfifteen和twentyseventeen两套主题,插件的话只有一个简单的列表页的摘要生成和enlighter代码高亮。本身只用于展示文章,添加评论走的转发请求到php的WordPress。因为大量用了泛型功能,所以要求go的版本在1.19及以上,越新越好。。。。
|
||||
|
||||
#### 特色功能
|
||||
|
||||
- 基本实现全站缓存,并且可防止缓存击穿
|
||||
- 列表页也可以高亮语法格式化显示代码
|
||||
- 简易插件扩展开发机制、配置后支持热加载更新
|
||||
- 使用.so扩展主题、插件、路由等
|
||||
- 丰富繁杂的配置,呃,配置是有点儿多,虽然大部分都是可选项。。。
|
||||
- 多种缓存配置
|
||||
- 添加评论或panic时发邮件通知,包涵栈调用和请求信息
|
||||
- 简单的流量限制中间件,可以限制全瞬时最大请求数量
|
||||
- 简单的流量限制中间件
|
||||
- 除配置文件外将所有静态资源都打包到执行文件中
|
||||
- 支持密码查看,且cookie信息可被php版所验证
|
||||
- 支持rss2订阅
|
||||
- 热更新配置、切换主题、清空缓存
|
||||
- 热更新配置、清空缓存
|
||||
- kill -SIGUSR1 PID 更新配置和清空缓存
|
||||
- kill -SIGUSR2 PID 清空缓存
|
||||
|
||||
#### 运行
|
||||
```
|
||||
go run app/cmd/main.go [-c configpath] [-p port]
|
||||
```
|
||||
|
||||
#### 数据显示支持程度
|
||||
|
||||
| 页表 | 支持程度 |
|
||||
|-----|---------------------------------------------|
|
||||
| 列表页 | 首页/搜索/归档/分类/标签/作者 分页列表 |
|
||||
| 详情页 | 显示内容、评论并可以添加评论(转发的php处理,需要配置php版的添加评论的url) |
|
||||
| 侧边栏 | 支持旧版 近期文章、近期评论、规档、分类、其它操作 显示及设置, 支持新版 分类 |
|
||||
|
||||
#### 后台设置支持程度
|
||||
|
||||
- 仪表盘
|
||||
- 外观
|
||||
- 小工具
|
||||
- 搜索
|
||||
- 规档
|
||||
- 近期文章
|
||||
- 近期评论
|
||||
- 分类
|
||||
- 其它操作
|
||||
|
||||
- 设置-
|
||||
- 常规
|
||||
- 站点标题
|
||||
- 副标题
|
||||
- 阅读
|
||||
- 博客页面至多显示数量
|
||||
- Feed中显示最近数量
|
||||
- 讨论
|
||||
- 其他评论设置
|
||||
- `启用|禁止`评论嵌套,最多嵌套层数
|
||||
- 分页显示评论,每页显示评论条数,默认显示`最前/后`页
|
||||
- 在每个页面顶部显示 `新旧`评论
|
||||
|
||||
#### 主题支持程度
|
||||
|
||||
| twentyfifteen | twentyseventeen |
|
||||
|---------------|-----------------|
|
||||
| 站点身份 | 站点身份 |
|
||||
| 颜色 | 颜色 |
|
||||
| 页眉图片 | 页眉媒体 |
|
||||
| 背景图片 | 额外css |
|
||||
| 额外css | |
|
||||
|
||||
#### 插件机制
|
||||
|
||||
分为对列表页文章数据的修改的插件和对影响整个程序表现的插件
|
||||
|
||||
| 列表页文章数据插件 | 整个程序表现的插件 |
|
||||
|---------------------|--------------------------------------|
|
||||
| digest 自动生成指定长度的摘要 | enlighter 代码高亮(需要在后台安装enlighterjs插件) |
|
||||
| | hiddenLogin 隐藏登录入口 |
|
||||
|
||||
#### 其它
|
||||
|
||||
用的gin框架和sqlx,在外面封装了层查询的方法。
|
||||
|
||||
#### 鸣谢
|
||||
|
||||
<a href="https://jb.gg/OpenSourceSupport"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main) logo." width="30%"></a>
|
||||
|
||||
用的gin框架和sqlx,在外面封装了层查询的方法。后台可以设置的比较少,大部分设置还没打通。
|
@ -1,293 +0,0 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/mail"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CommentForm struct {
|
||||
CommentPostId uint64 `form:"comment_post_ID" binding:"required" json:"comment_post_ID"`
|
||||
Author string `form:"author" binding:"required" label:"显示名称" json:"author"`
|
||||
Email string `form:"email" binding:"required,email"`
|
||||
Comment string `form:"comment" binding:"required" label:"评论" json:"comment"`
|
||||
}
|
||||
|
||||
func PostComment(c *gin.Context) {
|
||||
cli := &http.Client{
|
||||
Timeout: time.Second * 3,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
data, err := c.GetRawData()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Writer.WriteHeader(http.StatusConflict)
|
||||
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
var v validator.ValidationErrors
|
||||
if errors.As(err, &v) {
|
||||
e := v.Translate(config.GetZh())
|
||||
for _, v := range e {
|
||||
fmt.Fprintf(c.Writer, fail, v)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.Writer.WriteString("评论出错,请联系管理员或稍后再度")
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
conf := config.GetConfig()
|
||||
if err != nil {
|
||||
logs.Error(err, "获取评论数据错误")
|
||||
return
|
||||
}
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
|
||||
var comment CommentForm
|
||||
if err = c.ShouldBind(&comment); err != nil {
|
||||
return
|
||||
}
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
|
||||
req, err := http.NewRequest("POST", conf.PostCommentUrl, strings.NewReader(c.Request.PostForm.Encode()))
|
||||
if err != nil {
|
||||
logs.Error(err, "创建评论请求错误")
|
||||
return
|
||||
}
|
||||
defer req.Body.Close()
|
||||
req.Header = c.Request.Header.Clone()
|
||||
home, err := url.Parse(wpconfig.GetOption("siteurl"))
|
||||
if err != nil {
|
||||
logs.Error(err, "解析评论接口错误")
|
||||
return
|
||||
}
|
||||
req.Host = home.Host
|
||||
res, err := cli.Do(req)
|
||||
if err != nil && !errors.Is(err, http.ErrUseLastResponse) {
|
||||
logs.Error(err, "请求评论接口错误")
|
||||
return
|
||||
}
|
||||
if res.StatusCode == http.StatusFound {
|
||||
for _, cookie := range res.Cookies() {
|
||||
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
|
||||
}
|
||||
u := res.Header.Get("Location")
|
||||
up, er := url.Parse(u)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
cu, er := url.Parse(conf.PostCommentUrl)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
up.Host = cu.Host
|
||||
up.Scheme = "http"
|
||||
newReq, _ := http.NewRequest("GET", up.String(), nil)
|
||||
newReq.Host = home.Host
|
||||
newReq.Header.Set("Cookie", strings.Join(slice.Map(c.Request.Cookies(), func(t *http.Cookie) string {
|
||||
return fmt.Sprintf("%s=%s", t.Name, t.Value)
|
||||
}), "; "))
|
||||
ress, er := http.DefaultClient.Do(newReq)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
cc := c.Copy()
|
||||
go func() {
|
||||
if gin.Mode() != gin.ReleaseMode {
|
||||
return
|
||||
}
|
||||
id := comment.CommentPostId
|
||||
if id <= 0 {
|
||||
logs.Error(errors.New("获取文档id错误"), "", comment.CommentPostId)
|
||||
return
|
||||
}
|
||||
post, err := cache.GetPostById(cc, id)
|
||||
if err != nil {
|
||||
logs.Error(err, "获取文档错误", id)
|
||||
return
|
||||
}
|
||||
su := fmt.Sprintf("%s: %s[%s]发表了评论对文档[%v]的评论", wpconfig.GetOption("siteurl"), comment.Author, comment.Email, post.PostTitle)
|
||||
err = mail.SendMail([]string{conf.Mail.User}, su, comment.Comment)
|
||||
logs.IfError(err, "发送邮件", conf.Mail.User, su, comment)
|
||||
}()
|
||||
|
||||
s, er := io.ReadAll(ress.Body)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
uuu := ""
|
||||
uu, _ := url.Parse(res.Header.Get("Location"))
|
||||
if up.RawQuery != "" {
|
||||
cache.NewCommentCache().Set(c, up.RawQuery, string(s))
|
||||
uuu = str.Join(uu.Path, "?", uu.RawQuery)
|
||||
} else {
|
||||
uuu = str.Join(uu.Path)
|
||||
}
|
||||
c.Redirect(http.StatusFound, uuu)
|
||||
return
|
||||
}
|
||||
var r io.Reader
|
||||
if res.Header.Get("Content-Encoding") == "gzip" {
|
||||
r, err = gzip.NewReader(res.Body)
|
||||
if err != nil {
|
||||
logs.Error(err, "gzip解压错误")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
r = res.Body
|
||||
}
|
||||
s, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
logs.Error(err, "读取结果错误")
|
||||
return
|
||||
}
|
||||
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
c.Writer.WriteHeader(res.StatusCode)
|
||||
_, _ = c.Writer.Write(s)
|
||||
|
||||
}
|
||||
|
||||
var fail = `
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name='robots' content='max-image-preview:large, noindex, follow' />
|
||||
<title>评论提交失败</title>
|
||||
<style type="text/css">
|
||||
html {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
body {
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
color: #444;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
margin: 2em auto;
|
||||
padding: 1em 2em;
|
||||
max-width: 700px;
|
||||
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
|
||||
}
|
||||
h1 {
|
||||
border-bottom: 1px solid #dadada;
|
||||
clear: both;
|
||||
color: #666;
|
||||
font-size: 24px;
|
||||
margin: 30px 0 0 0;
|
||||
padding: 0;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
#error-page {
|
||||
margin-top: 50px;
|
||||
}
|
||||
#error-page p,
|
||||
#error-page .wp-die-message {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
margin: 25px 0 20px;
|
||||
}
|
||||
#error-page code {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
ul li {
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px ;
|
||||
}
|
||||
a {
|
||||
color: #0073aa;
|
||||
}
|
||||
a:hover,
|
||||
a:active {
|
||||
color: #006799;
|
||||
}
|
||||
a:focus {
|
||||
color: #124964;
|
||||
-webkit-box-shadow:
|
||||
0 0 0 1px #5b9dd9,
|
||||
0 0 2px 1px rgba(30, 140, 190, 0.8);
|
||||
box-shadow:
|
||||
0 0 0 1px #5b9dd9,
|
||||
0 0 2px 1px rgba(30, 140, 190, 0.8);
|
||||
outline: none;
|
||||
}
|
||||
.button {
|
||||
background: #f3f5f6;
|
||||
border: 1px solid #016087;
|
||||
color: #016087;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
line-height: 2;
|
||||
height: 28px;
|
||||
margin: 0;
|
||||
padding: 0 10px 1px;
|
||||
cursor: pointer;
|
||||
-webkit-border-radius: 3px;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.button.button-large {
|
||||
line-height: 2.30769231;
|
||||
min-height: 32px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.button:focus {
|
||||
background: #f3f5f6;
|
||||
border-color: #007cba;
|
||||
-webkit-box-shadow: 0 0 0 1px #007cba;
|
||||
box-shadow: 0 0 0 1px #007cba;
|
||||
color: #016087;
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
.button:active {
|
||||
background: #f3f5f6;
|
||||
border-color: #7e8993;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body id="error-page">
|
||||
<div class="wp-die-message"><p><strong>错误:</strong>%s</p></div>
|
||||
<p><a href='javascript:history.back()'>« 返回</a></p></body>
|
||||
</html>
|
||||
|
||||
`
|
@ -1,15 +0,0 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/theme"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ThemeHook(scene string) func(*gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
t := theme.GetCurrentTheme()
|
||||
h := wp.NewHandle(c, scene, t)
|
||||
theme.Hook(t, h)
|
||||
}
|
||||
}
|
111
app/cmd/main.go
111
app/cmd/main.go
@ -1,111 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/ossigns"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/db"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/plugins"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
|
||||
"github.com/fthvgb1/wp-go/app/route"
|
||||
"github.com/fthvgb1/wp-go/app/theme"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var confPath string
|
||||
var address string
|
||||
var intReg = regexp.MustCompile(`^\d`)
|
||||
|
||||
func inits() {
|
||||
flag.StringVar(&confPath, "c", "config.yaml", "config file support json,yaml or url")
|
||||
flag.StringVar(&address, "p", "", "listen address and port")
|
||||
flag.Parse()
|
||||
if address == "" && os.Getenv("PORT") == "" {
|
||||
address = "80"
|
||||
}
|
||||
if intReg.MatchString(address) && !strings.Contains(address, ":") {
|
||||
address = ":" + address
|
||||
}
|
||||
err := initConf(confPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cache.InitActionsCommonCache()
|
||||
plugins.InitDigestCache()
|
||||
theme.InitTheme()
|
||||
go cronClearCache()
|
||||
}
|
||||
|
||||
func initConf(c string) (err error) {
|
||||
err = config.InitConfig(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = config.InitTrans()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = logs.InitLogger()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
database, err := db.InitDb()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
model.InitDB(db.QueryDb(database))
|
||||
err = wpconfig.InitOptions()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = wpconfig.InitTerms()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wphandle.LoadPlugins()
|
||||
return
|
||||
}
|
||||
|
||||
func cronClearCache() {
|
||||
t := time.NewTicker(config.GetConfig().CacheTime.CrontabClearCacheTime)
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
cachemanager.ClearExpired()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println(r)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
inits()
|
||||
ossigns.SetConfPath(confPath)
|
||||
go ossigns.SignalNotify()
|
||||
Gin := route.SetupRouter()
|
||||
c := config.GetConfig()
|
||||
if c.Ssl.Key != "" && c.Ssl.Cert != "" {
|
||||
err := Gin.RunTLS(address, c.Ssl.Cert, c.Ssl.Key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err := Gin.Run(address)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package ossigns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/mail"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/db"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/signs"
|
||||
"log"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var confPath string
|
||||
|
||||
func SetConfPath(path string) {
|
||||
confPath = path
|
||||
}
|
||||
|
||||
func FlushCache() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err := mail.SendMail([]string{config.GetConfig().Mail.User}, "清空缓存失败", fmt.Sprintf("err:[%s]", r))
|
||||
logs.IfError(err, "发邮件失败")
|
||||
}
|
||||
}()
|
||||
cachemanager.Flush()
|
||||
log.Println("all cache flushed")
|
||||
}
|
||||
|
||||
func Reloads() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Println(r)
|
||||
}
|
||||
}()
|
||||
err := config.InitConfig(confPath)
|
||||
logs.IfError(err, "获取配置文件失败", confPath)
|
||||
err = logs.InitLogger()
|
||||
logs.IfError(err, "日志配置错误")
|
||||
_, err = db.InitDb()
|
||||
logs.IfError(err, "重新读取db失败", config.GetConfig().Mysql)
|
||||
err = wpconfig.InitOptions()
|
||||
logs.IfError(err, "获取网站设置WpOption失败")
|
||||
err = wpconfig.InitTerms()
|
||||
logs.IfError(err, "获取WpTerms表失败")
|
||||
wphandle.LoadPlugins()
|
||||
reload.Reloads("themeArgAndConfig")
|
||||
FlushCache()
|
||||
log.Println("reload complete")
|
||||
}
|
||||
|
||||
func SignalNotify() {
|
||||
rel := func() bool {
|
||||
go Reloads()
|
||||
return true
|
||||
}
|
||||
flu := func() bool {
|
||||
go FlushCache()
|
||||
return true
|
||||
}
|
||||
signs.Install(syscall.SIGUSR1, rel, "reload")
|
||||
signs.Install(syscall.SIGUSR2, flu, "flush")
|
||||
signs.Wait()
|
||||
}
|
126
app/pkg/cache/cache.go
vendored
126
app/pkg/cache/cache.go
vendored
@ -1,126 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/dao"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"time"
|
||||
)
|
||||
|
||||
func InitActionsCommonCache() {
|
||||
c := config.GetConfig()
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.SearchPostCacheTime, "searchPostIds", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.SearchPostCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.PostListCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.MonthPost, c.CacheTime.MonthPostCacheTime, "monthPostIds", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.MonthPostCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.GetPostContext, c.CacheTime.ContextPostCacheTime, "postContext", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.ContextPostCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(dao.GetPostsByIds, nil, c.CacheTime.PostDataCacheTime, "postData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.PostDataCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(dao.GetPostMetaByPostIds, nil, c.CacheTime.PostDataCacheTime, "postMetaData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.PostDataCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.CategoriesAndTags, c.CacheTime.CategoryCacheTime, "categoryAndTagsData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CategoryCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewVarMemoryCache(dao.RecentPosts, c.CacheTime.RecentPostCacheTime, "recentPosts", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.RecentPostCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewVarMemoryCache(RecentComment, c.CacheTime.RecentCommentsCacheTime, "recentComments", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.RecentCommentsCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.CommentNum, 30*time.Second, "commentNumber", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, PostTopComments, 30*time.Second, "PostCommentsIds", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
||||
})
|
||||
cachemanager.NewMemoryMapCache(nil, dao.PostTopCommentNum, 30*time.Second, "postTopCommentsNum", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(dao.GetCommentByIds, nil, time.Hour, "postCommentData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CommentsCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(dao.CommentChildren, nil, time.Minute, "commentChildren", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
||||
})
|
||||
|
||||
cachemanager.NewVarMemoryCache(dao.GetMaxPostId, c.CacheTime.MaxPostIdCacheTime, "maxPostId", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.MaxPostIdCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.GetUserById, c.CacheTime.UserInfoCacheTime, "userData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.UserInfoCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.GetUserByName, c.CacheTime.UserInfoCacheTime, "usernameToUserData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.UserInfoCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewVarMemoryCache(dao.AllUsername, c.CacheTime.UserInfoCacheTime, "allUsername", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.UserInfoCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewVarMemoryCache(SiteFeed, time.Hour, "siteFeed")
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, PostFeed, time.Hour, "postFeed")
|
||||
|
||||
cachemanager.NewVarMemoryCache(CommentsFeed, time.Hour, "commentsFeed")
|
||||
|
||||
cachemanager.NewMemoryMapCache[string, string](nil, nil, 15*time.Minute, "NewComment")
|
||||
|
||||
InitFeed()
|
||||
}
|
||||
|
||||
type Arch struct {
|
||||
data []models.PostArchive
|
||||
fn func(context.Context) ([]models.PostArchive, error)
|
||||
month time.Month
|
||||
}
|
||||
|
||||
var arch = reload.Vars(Arch{
|
||||
fn: dao.Archives,
|
||||
}, "archives-year-month-data")
|
||||
|
||||
func Archives(ctx context.Context) []models.PostArchive {
|
||||
a := arch.Load()
|
||||
data := a.data
|
||||
l := len(data)
|
||||
m := time.Now().Month()
|
||||
if l < 1 || a.month != m {
|
||||
r, err := a.fn(ctx)
|
||||
if err != nil {
|
||||
logs.Error(err, "set cache Archives fail")
|
||||
return nil
|
||||
}
|
||||
a.month = m
|
||||
a.data = r
|
||||
arch.Store(a)
|
||||
data = r
|
||||
}
|
||||
return data
|
||||
}
|
39
app/pkg/cache/categoryandtag.go
vendored
39
app/pkg/cache/categoryandtag.go
vendored
@ -1,39 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CategoriesTags get all categories or tags
|
||||
//
|
||||
// query func see dao.CategoriesAndTags
|
||||
//
|
||||
// t is constraints.Tag or constraints.Category
|
||||
func CategoriesTags(ctx context.Context, t ...string) []models.TermsMy {
|
||||
tt := ""
|
||||
if len(t) > 0 {
|
||||
tt = t[0]
|
||||
}
|
||||
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
|
||||
logs.IfError(err, "get category fail")
|
||||
return r
|
||||
}
|
||||
func AllCategoryTagsNames(ctx context.Context, t ...string) map[string]struct{} {
|
||||
tt := ""
|
||||
if len(t) > 0 {
|
||||
tt = t[0]
|
||||
}
|
||||
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
|
||||
if err != nil {
|
||||
logs.Error(err, "get category fail")
|
||||
return nil
|
||||
}
|
||||
return slice.ToMap(r, func(t models.TermsMy) (string, struct{}) {
|
||||
return t.Name, struct{}{}
|
||||
}, true)
|
||||
}
|
124
app/pkg/cache/comments.go
vendored
124
app/pkg/cache/comments.go
vendored
@ -1,124 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/dao"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RecentComments query func see RecentComment
|
||||
func RecentComments(ctx context.Context, n int) (r []models.Comments) {
|
||||
nn := number.Max(n, 10)
|
||||
r, err := cachemanager.GetVarVal[[]models.Comments]("recentComments", ctx, time.Second, ctx, nn)
|
||||
if len(r) > n {
|
||||
r = r[0:n]
|
||||
}
|
||||
logs.IfError(err, "get recent comment fail")
|
||||
return
|
||||
}
|
||||
|
||||
// PostTopLevelCommentIds query func see PostTopComments
|
||||
func PostTopLevelCommentIds(ctx context.Context, postId uint64, page, limit, total int, order string, a ...any) ([]uint64, error) {
|
||||
var key string
|
||||
if len(a) > 0 {
|
||||
key = helper.ParseArgs("", a...)
|
||||
}
|
||||
if key == "" {
|
||||
key = fmt.Sprintf("%d-%d-%d-%d-%s", postId, page, limit, total, order)
|
||||
}
|
||||
return cachemanager.GetBy[[]uint64]("PostCommentsIds", ctx,
|
||||
key, time.Second, postId, page, limit, 0, order)
|
||||
}
|
||||
|
||||
// GetCommentById query func see dao.GetCommentByIds
|
||||
func GetCommentById(ctx context.Context, id uint64) (models.Comments, error) {
|
||||
return cachemanager.GetBy[models.Comments]("postCommentData", ctx, id, time.Second)
|
||||
}
|
||||
|
||||
// GetCommentDataByIds query func see dao.GetCommentByIds
|
||||
func GetCommentDataByIds(ctx context.Context, ids []uint64) ([]models.Comments, error) {
|
||||
return cachemanager.GetBatchBy[models.Comments]("postCommentData", ctx, ids, time.Second)
|
||||
}
|
||||
|
||||
func NewCommentCache() *cache.MapCache[string, string] {
|
||||
r, _ := cachemanager.GetMapCache[string, string]("NewComment")
|
||||
return r
|
||||
}
|
||||
|
||||
func PostTopComments(ctx context.Context, _ string, a ...any) ([]uint64, error) {
|
||||
postId := a[0].(uint64)
|
||||
page := a[1].(int)
|
||||
limit := a[2].(int)
|
||||
total := a[3].(int)
|
||||
order := helper.ParseArgs("", a...)
|
||||
if order == "" {
|
||||
order = wpconfig.GetOption("comment_order")
|
||||
}
|
||||
v, _, err := dao.PostCommentsIds(ctx, postId, page, limit, total, order)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func RecentComment(ctx context.Context, a ...any) (r []models.Comments, err error) {
|
||||
r, err = dao.RecentComments(ctx, a...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
for i, comment := range r {
|
||||
r[i].CommentAuthorUrl, err = GetCommentUrl(ctx, comment.CommentId, comment.CommentPostId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetCommentUrl(ctx context.Context, commentId, postId uint64) (string, error) {
|
||||
if wpconfig.GetOption("page_comments") != "1" {
|
||||
return fmt.Sprintf("/p/%d#comment-%d", postId, commentId), nil
|
||||
}
|
||||
commentsPerPage := str.ToInteger(wpconfig.GetOption("comments_per_page"), 5)
|
||||
topCommentId, err := AncestorCommentId(ctx, commentId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
totalNum, err := cachemanager.GetBy[int]("postTopCommentsNum", ctx, postId, time.Second)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if totalNum <= commentsPerPage {
|
||||
return fmt.Sprintf("/p/%d#comment-%d", postId, commentId), nil
|
||||
}
|
||||
num, err := dao.PreviousCommentNum(ctx, topCommentId, postId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
order := wpconfig.GetOption("comment_order")
|
||||
page := number.DivideCeil(num+1, commentsPerPage)
|
||||
if order == "desc" {
|
||||
page = number.DivideCeil(totalNum-num, commentsPerPage)
|
||||
}
|
||||
return fmt.Sprintf("/p/%d/comment-page-%d/#comment-%d", postId, page, commentId), nil
|
||||
}
|
||||
|
||||
func AncestorCommentId(ctx context.Context, commentId uint64) (uint64, error) {
|
||||
comment, err := GetCommentById(ctx, commentId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if comment.CommentParent == 0 {
|
||||
return comment.CommentId, nil
|
||||
}
|
||||
return AncestorCommentId(ctx, comment.CommentParent)
|
||||
}
|
17
app/pkg/cache/postmeta.go
vendored
17
app/pkg/cache/postmeta.go
vendored
@ -1,17 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetPostMetaByPostIds query func see dao.GetPostMetaByPostIds
|
||||
func GetPostMetaByPostIds(ctx context.Context, ids []uint64) ([]map[string]any, error) {
|
||||
return cachemanager.GetBatchBy[map[string]any]("postMetaData", ctx, ids, time.Second)
|
||||
}
|
||||
|
||||
// GetPostMetaByPostId query func see dao.GetPostMetaByPostIds
|
||||
func GetPostMetaByPostId(ctx context.Context, id uint64) (map[string]any, error) {
|
||||
return cachemanager.GetBy[map[string]any]("postMetaData", ctx, id, time.Second)
|
||||
}
|
91
app/pkg/cache/posts.go
vendored
91
app/pkg/cache/posts.go
vendored
@ -1,91 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/dao"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetPostById query func see dao.GetPostsByIds
|
||||
func GetPostById(ctx context.Context, id uint64) (models.Posts, error) {
|
||||
return cachemanager.GetBy[models.Posts]("postData", ctx, id, time.Second)
|
||||
}
|
||||
|
||||
// GetPostsByIds query func see dao.GetPostsByIds
|
||||
func GetPostsByIds(ctx context.Context, ids []uint64) ([]models.Posts, error) {
|
||||
return cachemanager.GetBatchBy[models.Posts]("postData", ctx, ids, time.Second)
|
||||
}
|
||||
|
||||
// SearchPost query func see dao.SearchPostIds
|
||||
func SearchPost(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
|
||||
ids, err := cachemanager.GetBy[dao.PostIds]("searchPostIds", ctx, key, time.Second, args...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
total = ids.Length
|
||||
r, err = GetPostsByIds(ctx, ids.Ids)
|
||||
return
|
||||
}
|
||||
|
||||
// PostLists query func see dao.SearchPostIds
|
||||
func PostLists(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
|
||||
ids, err := cachemanager.GetBy[dao.PostIds]("listPostIds", ctx, key, time.Second, args...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
total = ids.Length
|
||||
r, err = GetPostsByIds(ctx, ids.Ids)
|
||||
return
|
||||
}
|
||||
|
||||
// GetMaxPostId query func see dao.GetMaxPostId
|
||||
func GetMaxPostId(ctx context.Context) (uint64, error) {
|
||||
return cachemanager.GetVarVal[uint64]("maxPostId", ctx, time.Second)
|
||||
}
|
||||
|
||||
// RecentPosts query func see dao.RecentPosts
|
||||
func RecentPosts(ctx context.Context, n int) (r []models.Posts) {
|
||||
nn := n
|
||||
feedNum := str.ToInteger(wpconfig.GetOption("posts_per_rss"), 10)
|
||||
nn = number.Max(n, feedNum)
|
||||
r, err := cachemanager.GetVarVal[[]models.Posts]("recentPosts", ctx, time.Second, nn)
|
||||
if n < len(r) {
|
||||
r = r[:n]
|
||||
}
|
||||
logs.IfError(err, "get recent post")
|
||||
return
|
||||
}
|
||||
|
||||
// GetContextPost query func see dao.GetPostContext
|
||||
func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next models.Posts, err error) {
|
||||
postCtx, err := cachemanager.GetBy[dao.PostContext]("postContext", ctx, id, time.Second, date)
|
||||
if err != nil {
|
||||
return models.Posts{}, models.Posts{}, err
|
||||
}
|
||||
prev = postCtx.Prev
|
||||
next = postCtx.Next
|
||||
return
|
||||
}
|
||||
|
||||
// GetMonthPostIds query func see dao.MonthPost
|
||||
func GetMonthPostIds(ctx context.Context, year, month string, page, limit int, order string) (r []models.Posts, total int, err error) {
|
||||
res, err := cachemanager.GetBy[[]uint64]("monthPostIds", ctx, fmt.Sprintf("%s%s", year, month), time.Second, year, month)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if order == "desc" {
|
||||
res = slice.Reverse(res)
|
||||
}
|
||||
total = len(res)
|
||||
rr := slice.Pagination(res, page, limit)
|
||||
r, err = GetPostsByIds(ctx, rr)
|
||||
return
|
||||
}
|
26
app/pkg/cache/users.go
vendored
26
app/pkg/cache/users.go
vendored
@ -1,26 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetUserByName query func see dao.GetUserByName
|
||||
func GetUserByName(ctx context.Context, username string) (models.Users, error) {
|
||||
return cachemanager.GetBy[models.Users]("usernameToUserData", ctx, username, time.Second)
|
||||
}
|
||||
|
||||
// GetAllUsername query func see dao.AllUsername
|
||||
func GetAllUsername(ctx context.Context) (map[string]uint64, error) {
|
||||
return cachemanager.GetVarVal[map[string]uint64]("allUsername", ctx, time.Second)
|
||||
}
|
||||
|
||||
// GetUserById query func see dao.GetUserById
|
||||
func GetUserById(ctx context.Context, uid uint64) models.Users {
|
||||
r, err := cachemanager.GetBy[models.Users]("userData", ctx, uid, time.Second)
|
||||
logs.IfError(err, "get user", uid)
|
||||
return r
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var config safety.Var[Config]
|
||||
|
||||
func GetConfig() Config {
|
||||
return config.Load()
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Ssl Ssl `yaml:"ssl" json:"ssl"`
|
||||
Mysql Mysql `yaml:"mysql" json:"mysql"`
|
||||
Mail Mail `yaml:"mail" json:"mail"`
|
||||
CacheTime CacheTime `yaml:"cacheTime" json:"cacheTime"`
|
||||
PluginPath string `yaml:"pluginPath" json:"pluginPath"`
|
||||
ExternScript []string `json:"externScript" yaml:"externScript"`
|
||||
DigestWordCount int `yaml:"digestWordCount" json:"digestWordCount,omitempty"`
|
||||
DigestAllowTag string `yaml:"digestAllowTag" json:"digestAllowTag"`
|
||||
MaxRequestSleepNum int64 `yaml:"maxRequestSleepNum" json:"maxRequestSleepNum,omitempty"`
|
||||
MaxRequestNum int64 `yaml:"maxRequestNum" json:"maxRequestNum,omitempty"`
|
||||
SingleIpSearchNum int64 `yaml:"singleIpSearchNum" json:"singleIpSearchNum,omitempty"`
|
||||
Gzip bool `yaml:"gzip" json:"gzip,omitempty"`
|
||||
PostCommentUrl string `yaml:"postCommentUrl" json:"postCommentUrl,omitempty"`
|
||||
TrustIps []string `yaml:"trustIps" json:"trustIps,omitempty"`
|
||||
TrustServerNames []string `yaml:"trustServerNames" json:"trustServerNames,omitempty"`
|
||||
Theme string `yaml:"theme" json:"theme,omitempty"`
|
||||
PostOrder string `yaml:"postOrder" json:"postOrder,omitempty"`
|
||||
UploadDir string `yaml:"uploadDir" json:"uploadDir,omitempty"`
|
||||
Pprof string `yaml:"pprof" json:"pprof,omitempty"`
|
||||
ListPagePlugins []string `yaml:"listPagePlugins" json:"listPagePlugins,omitempty"`
|
||||
PaginationStep int `yaml:"paginationStep" json:"paginationStep,omitempty"`
|
||||
ShowQuerySql bool `yaml:"showQuerySql" json:"showQuerySql,omitempty"`
|
||||
Plugins []string `yaml:"plugins" json:"plugins,omitempty"`
|
||||
LogOutput string `yaml:"logOutput" json:"logOutput,omitempty"`
|
||||
WpDir string `yaml:"wpDir" json:"wpDir"`
|
||||
}
|
||||
|
||||
type CacheTime struct {
|
||||
CacheControl time.Duration `yaml:"cacheControl" json:"cacheControl,omitempty"`
|
||||
RecentPostCacheTime time.Duration `yaml:"recentPostCacheTime" json:"recentPostCacheTime,omitempty"`
|
||||
CategoryCacheTime time.Duration `yaml:"categoryCacheTime" json:"categoryCacheTime,omitempty"`
|
||||
ArchiveCacheTime time.Duration `yaml:"archiveCacheTime" json:"archiveCacheTime,omitempty"`
|
||||
ContextPostCacheTime time.Duration `yaml:"contextPostCacheTime" json:"contextPostCacheTime,omitempty"`
|
||||
RecentCommentsCacheTime time.Duration `yaml:"recentCommentsCacheTime" json:"recentCommentsCacheTime,omitempty"`
|
||||
DigestCacheTime time.Duration `yaml:"digestCacheTime" json:"digestCacheTime,omitempty"`
|
||||
PostListCacheTime time.Duration `yaml:"postListCacheTime" json:"postListCacheTime,omitempty"`
|
||||
SearchPostCacheTime time.Duration `yaml:"searchPostCacheTime" json:"searchPostCacheTime,omitempty"`
|
||||
MonthPostCacheTime time.Duration `yaml:"monthPostCacheTime" json:"monthPostCacheTime,omitempty"`
|
||||
PostDataCacheTime time.Duration `yaml:"postDataCacheTime" json:"postDataCacheTime,omitempty"`
|
||||
PostCommentsCacheTime time.Duration `yaml:"postCommentsCacheTime" json:"postCommentsCacheTime,omitempty"`
|
||||
CrontabClearCacheTime time.Duration `yaml:"crontabClearCacheTime" json:"crontabClearCacheTime,omitempty"`
|
||||
MaxPostIdCacheTime time.Duration `yaml:"maxPostIdCacheTime" json:"maxPostIdCacheTime,omitempty"`
|
||||
UserInfoCacheTime time.Duration `yaml:"userInfoCacheTime" json:"userInfoCacheTime,omitempty"`
|
||||
CommentsCacheTime time.Duration `yaml:"commentsCacheTime" json:"commentsCacheTime,omitempty"`
|
||||
SleepTime []time.Duration `yaml:"sleepTime" json:"sleepTime,omitempty"`
|
||||
CommentsIncreaseUpdateTime time.Duration `yaml:"commentsIncreaseUpdateTime" json:"commentsIncreaseUpdateTime"`
|
||||
}
|
||||
|
||||
type Ssl struct {
|
||||
Cert string `yaml:"cert" json:"cert,omitempty"`
|
||||
Key string `yaml:"key" json:"key,omitempty"`
|
||||
}
|
||||
|
||||
type Mail struct {
|
||||
User string `yaml:"user" json:"user,omitempty"`
|
||||
Alias string `yaml:"alias" json:"alias,omitempty"`
|
||||
Pass string `yaml:"pass" json:"pass,omitempty"`
|
||||
Host string `yaml:"host" json:"host,omitempty"`
|
||||
Port int `yaml:"port" json:"port,omitempty"`
|
||||
InsecureSkipVerify bool `yaml:"insecureSkipVerify" json:"insecureSkipVerify,omitempty"`
|
||||
}
|
||||
|
||||
type Mysql struct {
|
||||
Dsn Dsn `yaml:"dsn" json:"dsn"`
|
||||
Pool Pool `yaml:"pool" json:"pool"`
|
||||
}
|
||||
|
||||
func GetCustomizedConfig[T any]() (T, error) {
|
||||
var r T
|
||||
err := yaml.Unmarshal(fileData.Load(), &r)
|
||||
return r, err
|
||||
}
|
||||
|
||||
var fileData = safety.NewVar([]byte{})
|
||||
|
||||
func InitConfig(conf string) error {
|
||||
if conf == "" {
|
||||
conf = "config.yaml"
|
||||
}
|
||||
var file []byte
|
||||
var err error
|
||||
if strings.Contains(conf, "http") {
|
||||
get, err := http.Get(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer get.Body.Close()
|
||||
file, err = io.ReadAll(get.Body)
|
||||
} else {
|
||||
file, err = os.ReadFile(conf)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileData.Store(file)
|
||||
var c Config
|
||||
err = yaml.Unmarshal(file, &c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Store(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Dsn struct {
|
||||
Host string `yaml:"host" json:"host,omitempty"`
|
||||
Port string `yaml:"port" json:"port,omitempty"`
|
||||
Db string `yaml:"db" json:"db,omitempty"`
|
||||
User string `yaml:"user" json:"user,omitempty"`
|
||||
Password string `yaml:"password" json:"password,omitempty"`
|
||||
Charset string `yaml:"charset" json:"charset,omitempty"`
|
||||
}
|
||||
|
||||
func (m Dsn) GetDsn() string {
|
||||
if m.Charset == "" {
|
||||
m.Charset = "utf8"
|
||||
}
|
||||
t := "%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local"
|
||||
return fmt.Sprintf(t, m.User, m.Password, m.Host, m.Port, m.Db, m.Charset)
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
ConnMaxIdleTime time.Duration `yaml:"connMaxIdleTime" json:"connMaxIdleTime,omitempty"`
|
||||
MaxOpenConn int `yaml:"maxOpenConn" json:"maxOpenConn,omitempty"`
|
||||
MaxIdleConn int `yaml:"maxIdleConn" json:"maxIdleConn,omitempty"`
|
||||
ConnMaxLifetime time.Duration `yaml:"connMaxLifetime" json:"connMaxLifetime,omitempty"`
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-playground/locales/en"
|
||||
"github.com/go-playground/locales/zh"
|
||||
ut "github.com/go-playground/universal-translator"
|
||||
"github.com/go-playground/validator/v10"
|
||||
enTrans "github.com/go-playground/validator/v10/translations/en"
|
||||
zhTrans "github.com/go-playground/validator/v10/translations/zh"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var enT ut.Translator
|
||||
var zhT ut.Translator
|
||||
|
||||
func GetZh() ut.Translator {
|
||||
return zhT
|
||||
}
|
||||
func GetEn() ut.Translator {
|
||||
return enT
|
||||
}
|
||||
|
||||
func InitTrans() error {
|
||||
if validate, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
||||
ens := en.New()
|
||||
uni := ut.New(ens, zh.New(), ens)
|
||||
zhT, _ = uni.GetTranslator("zh")
|
||||
enT, _ = uni.GetTranslator("en")
|
||||
err := enTrans.RegisterDefaultTranslations(validate, enT)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
|
||||
return field.Tag.Get("label")
|
||||
})
|
||||
err = zhTrans.RegisterDefaultTranslations(validate, zhT)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package blocks
|
||||
|
||||
const (
|
||||
Search = "block-search"
|
||||
RecentPosts = "block-recent-posts"
|
||||
RecentComments = "block-recent-comments"
|
||||
Archive = "block-archives"
|
||||
Categories = "block-categories"
|
||||
Meta = "block-meta"
|
||||
)
|
@ -1,30 +0,0 @@
|
||||
package constraints
|
||||
|
||||
const (
|
||||
Home = "Home"
|
||||
Archive = "Archive"
|
||||
Category = "Category"
|
||||
Tag = "Tag"
|
||||
Search = "Search"
|
||||
Author = "Author"
|
||||
Detail = "Detail"
|
||||
|
||||
NoRoute = "NoRoute"
|
||||
|
||||
Ok = "Ok"
|
||||
Error404 = "Error404"
|
||||
ParamError = "ParamError"
|
||||
InternalErr = "InternalErr"
|
||||
AllStats = "AllStats"
|
||||
AllScene = "AllScene"
|
||||
|
||||
PipeData = "PipeData"
|
||||
PipeMiddleware = "PipeMiddleware"
|
||||
PipeRender = "PipeRender"
|
||||
|
||||
Defaults = "default"
|
||||
|
||||
HeadScript = "headScript"
|
||||
FooterScript = "footerScript"
|
||||
SidebarsWidgets = "sidebarsWidgets"
|
||||
)
|
@ -1,12 +0,0 @@
|
||||
package widgets
|
||||
|
||||
const (
|
||||
Search = "widget-search"
|
||||
RecentPosts = "widget-recent-posts"
|
||||
RecentComments = "widget-recent-comments"
|
||||
Archive = "widget-archives"
|
||||
Categories = "widget-categories"
|
||||
Meta = "widget-meta"
|
||||
|
||||
Widget = "widget"
|
||||
)
|
@ -1,183 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
)
|
||||
|
||||
// RecentComments
|
||||
// param context.Context
|
||||
func RecentComments(ctx context.Context, a ...any) (r []models.Comments, err error) {
|
||||
n := helper.ParseArgs(10, a...)
|
||||
return model.Finds[models.Comments](ctx, model.Conditions(
|
||||
model.Where(model.SqlBuilder{
|
||||
{"comment_approved", "1"},
|
||||
{"post_status", "publish"},
|
||||
}),
|
||||
model.Fields("comment_ID,comment_author,comment_post_ID,post_title"),
|
||||
model.Order(model.SqlBuilder{{"comment_date_gmt", "desc"}}),
|
||||
model.Join(model.SqlBuilder{{"a", "left join", "wp_posts b", "a.comment_post_ID=b.ID"}}),
|
||||
model.Limit(n),
|
||||
))
|
||||
}
|
||||
|
||||
// PostComments
|
||||
// param1 context.Context
|
||||
// param2 postId
|
||||
func PostComments(ctx context.Context, postId uint64, _ ...any) ([]uint64, error) {
|
||||
r, err := model.ChunkFind[models.Comments](ctx, 300, model.Conditions(
|
||||
model.Where(model.SqlBuilder{
|
||||
{"comment_approved", "1"},
|
||||
{"comment_post_ID", "=", number.IntToString(postId), "int"},
|
||||
}),
|
||||
model.Fields("comment_ID"),
|
||||
model.Order(model.SqlBuilder{
|
||||
{"comment_date_gmt", "asc"},
|
||||
{"comment_ID", "asc"},
|
||||
})),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slice.Map(r, func(t models.Comments) uint64 {
|
||||
return t.CommentId
|
||||
}), err
|
||||
}
|
||||
|
||||
func GetCommentByIds(ctx context.Context, ids []uint64, _ ...any) (map[uint64]models.Comments, error) {
|
||||
if len(ids) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
m := make(map[uint64]models.Comments)
|
||||
off := 0
|
||||
for {
|
||||
id := slice.Slice(ids, off, 500)
|
||||
if len(id) < 1 {
|
||||
break
|
||||
}
|
||||
r, err := model.Finds[models.Comments](ctx, model.Conditions(
|
||||
model.Where(model.SqlBuilder{
|
||||
{"comment_ID", "in", ""}, {"comment_approved", "1"},
|
||||
}),
|
||||
model.Fields("*"),
|
||||
model.In(slice.ToAnySlice(id)),
|
||||
))
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
for _, comments := range r {
|
||||
m[comments.CommentId] = comments
|
||||
}
|
||||
off += 500
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func CommentNum(ctx context.Context, postId uint64, _ ...any) (int, error) {
|
||||
n, err := model.GetField[models.Posts](ctx, "comment_count", model.Conditions(
|
||||
model.Where(model.SqlBuilder{{"ID", "=", number.IntToString(postId), "int"}})))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return str.ToInteger(n, 0), err
|
||||
}
|
||||
|
||||
func PostTopCommentNum(ctx context.Context, postId uint64, _ ...any) (int, error) {
|
||||
v, err := model.GetField[models.Comments](ctx, "count(*) num", model.Conditions(
|
||||
model.Where(postTopCommentNumWhere(postId)),
|
||||
))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return str.ToInteger(v, 0), nil
|
||||
}
|
||||
|
||||
func postTopCommentNumWhere(postId uint64) model.SqlBuilder {
|
||||
threadComments := wpconfig.GetOption("thread_comments")
|
||||
pageComments := wpconfig.GetOption("page_comments")
|
||||
where := model.SqlBuilder{
|
||||
{"comment_approved", "1"},
|
||||
{"comment_post_ID", "=", number.IntToString(postId), "int"},
|
||||
}
|
||||
if pageComments != "1" || threadComments == "1" || "1" == wpconfig.GetOption("thread_comments_depth") {
|
||||
where = append(where, []string{"comment_parent", "0"})
|
||||
}
|
||||
return where
|
||||
}
|
||||
|
||||
func PostCommentsIds(ctx context.Context, postId uint64, page, limit, totalRaw int, order string) ([]uint64, int, error) {
|
||||
condition := model.Conditions(
|
||||
model.Where(postTopCommentNumWhere(postId)),
|
||||
model.TotalRaw(totalRaw),
|
||||
model.Fields("comment_ID"),
|
||||
model.Order(model.SqlBuilder{
|
||||
{"comment_date_gmt", order},
|
||||
{"comment_ID", "asc"},
|
||||
}),
|
||||
)
|
||||
var r []models.Comments
|
||||
var total int
|
||||
var err error
|
||||
if limit < 1 {
|
||||
r, err = model.ChunkFind[models.Comments](ctx, 300, condition)
|
||||
total = len(r)
|
||||
} else {
|
||||
r, total, err = model.Pagination[models.Comments](ctx, condition, page, limit)
|
||||
}
|
||||
|
||||
if err != nil && errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return slice.Map(r, func(t models.Comments) uint64 {
|
||||
return t.CommentId
|
||||
}), total, err
|
||||
}
|
||||
|
||||
func CommentChildren(ctx context.Context, commentIds []uint64, _ ...any) (r map[uint64][]uint64, err error) {
|
||||
rr, err := model.Finds[models.Comments](ctx, model.Conditions(
|
||||
model.Where(model.SqlBuilder{
|
||||
{"comment_parent", "in", ""},
|
||||
{"comment_approved", "1"},
|
||||
}),
|
||||
model.In(slice.ToAnySlice(commentIds)),
|
||||
model.Fields("comment_ID,comment_parent"),
|
||||
))
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
rrr := slice.GroupBy(rr, func(v models.Comments) (uint64, uint64) {
|
||||
return v.CommentParent, v.CommentId
|
||||
})
|
||||
r = make(map[uint64][]uint64)
|
||||
for _, id := range commentIds {
|
||||
r[id] = rrr[id]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func PreviousCommentNum(ctx context.Context, commentId, postId uint64) (int, error) {
|
||||
v, err := model.GetField[models.Comments](ctx, "count(*)", model.Conditions(
|
||||
model.Where(model.SqlBuilder{
|
||||
{"comment_approved", "1"},
|
||||
{"comment_post_ID", "=", number.IntToString(postId), "int"},
|
||||
{"comment_ID", "<", number.IntToString(commentId), "int"},
|
||||
{"comment_parent", "=", "0", "int"},
|
||||
}),
|
||||
))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return str.ToInteger(v, 0), nil
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
)
|
||||
|
||||
func GetUserById(ctx context.Context, uid uint64, _ ...any) (r models.Users, err error) {
|
||||
r, err = model.FindOneById[models.Users](ctx, uid)
|
||||
return
|
||||
}
|
||||
|
||||
func AllUsername(ctx context.Context, _ ...any) (map[string]uint64, error) {
|
||||
r, err := model.SimpleFind[models.Users](ctx, model.SqlBuilder{
|
||||
{"user_status", "=", "0", "int"},
|
||||
}, "display_name,ID")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slice.ToMap(r, func(t models.Users) (string, uint64) {
|
||||
return t.DisplayName, t.Id
|
||||
}, true), nil
|
||||
}
|
||||
|
||||
func GetUserByName(ctx context.Context, u string, _ ...any) (r models.Users, err error) {
|
||||
r, err = model.FirstOne[models.Users](ctx, model.SqlBuilder{{
|
||||
"display_name", u,
|
||||
}}, "*", nil)
|
||||
return
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var logs = safety.NewVar[*log.Logger](nil)
|
||||
var logFile = safety.NewVar[*os.File](nil)
|
||||
|
||||
func InitLogger() error {
|
||||
c := config.GetConfig()
|
||||
return SetLogger(c.LogOutput)
|
||||
}
|
||||
|
||||
func SetLogger(loggerFile string) error {
|
||||
if loggerFile == "" {
|
||||
loggerFile = "stderr"
|
||||
}
|
||||
preFD := logFile.Load()
|
||||
l := &log.Logger{}
|
||||
var out io.Writer
|
||||
switch loggerFile {
|
||||
case "stdout":
|
||||
out = os.Stdout
|
||||
case "stderr":
|
||||
out = os.Stderr
|
||||
default:
|
||||
file, err := os.OpenFile(loggerFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out = file
|
||||
logFile.Store(file)
|
||||
}
|
||||
logs.Store(l)
|
||||
if preFD != nil {
|
||||
_ = preFD.Close()
|
||||
}
|
||||
l.SetFlags(log.Ldate | log.Ltime)
|
||||
l.SetOutput(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Errs(err error, depth int, desc string, args ...any) {
|
||||
var pcs [1]uintptr
|
||||
runtime.Callers(depth, pcs[:])
|
||||
f := runtime.CallersFrames([]uintptr{pcs[0]})
|
||||
ff, _ := f.Next()
|
||||
s := strings.Builder{}
|
||||
_, _ = fmt.Fprintf(&s, "%s:%d %s err:[%s]", ff.File, ff.Line, desc, err)
|
||||
if len(args) > 0 {
|
||||
s.WriteString(" args:")
|
||||
for _, arg := range args {
|
||||
_, _ = fmt.Fprintf(&s, "%v", arg)
|
||||
}
|
||||
}
|
||||
logs.Load().Println(s.String())
|
||||
}
|
||||
|
||||
func Error(err error, desc string, args ...any) {
|
||||
Errs(err, 3, desc, args...)
|
||||
}
|
||||
|
||||
func IfError(err error, desc string, args ...any) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
Errs(err, 3, desc, args...)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package relation
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
)
|
||||
|
||||
var PostsWithAuthor = model.RelationHasOne(func(m *models.Posts) uint64 {
|
||||
return m.PostAuthor
|
||||
}, func(p *models.Users) uint64 {
|
||||
return p.Id
|
||||
}, func(m *models.Posts, p *models.Users) {
|
||||
m.Author = p
|
||||
}, model.Relationship{
|
||||
RelationType: model.HasOne,
|
||||
Table: "wp_users user",
|
||||
ForeignKey: "ID",
|
||||
Local: "post_author",
|
||||
})
|
@ -1,9 +0,0 @@
|
||||
<div>
|
||||
{{.aa}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ range $k,$v := .posts}}
|
||||
<h2>{{$v.PostTitle}} </h2>
|
||||
{{end}}
|
||||
</div>
|
@ -1,81 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/route"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
|
||||
"github.com/fthvgb1/wp-go/app/theme"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/components"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget"
|
||||
route2 "github.com/fthvgb1/wp-go/app/theme/wp/route"
|
||||
"github.com/gin-gonic/gin"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"plugintt/xx"
|
||||
)
|
||||
|
||||
//go:embed a.gohtml
|
||||
var em embed.FS
|
||||
var tt *template.Template
|
||||
|
||||
func init() {
|
||||
// register as theme
|
||||
theme.AddThemeHookFunc("themename", hook)
|
||||
|
||||
//use the local template
|
||||
//note: must use embed.FS
|
||||
t, err := template.ParseFS(em, "a.gohtml")
|
||||
if err != nil {
|
||||
logs.Error(err, "")
|
||||
}
|
||||
tt = t
|
||||
|
||||
// register gin route. it will be effecting when server restart.
|
||||
route.Hook(func(r *gin.Engine) {
|
||||
r.GET("xx", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "xxoo")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func hook(h *wp.Handle) {
|
||||
wp.Run(h, config)
|
||||
}
|
||||
|
||||
func config(h *wp.Handle) {
|
||||
// same theme config
|
||||
wphandle.UsePlugins(h)
|
||||
wp.InitPipe(h)
|
||||
h.PushHandler(constraints.PipeMiddleware, constraints.Home,
|
||||
wp.NewHandleFn(widget.IsCategory, 100, "widget.IsCategory"))
|
||||
components.WidgetArea(h)
|
||||
h.PushHandler(constraints.PipeRender, constraints.Home, wp.NewHandleFn(func(h *wp.Handle) {
|
||||
h.SetData("aa", "xyxxxx")
|
||||
h.RenderHtml(tt, http.StatusOK, "a.gohtml")
|
||||
h.Abort()
|
||||
h.StopPipe()
|
||||
}, 10, "renderHome"))
|
||||
|
||||
// use simple reg route
|
||||
route2.PushRoute(`(?P<control>\w+)/(?P<method>\w+)`, route2.Route{
|
||||
Path: `(?P<control>\w+)/(?P<method>\w+)`,
|
||||
Scene: constraints.Home,
|
||||
Method: []string{"GET"},
|
||||
Type: "reg",
|
||||
})
|
||||
//...
|
||||
}
|
||||
|
||||
// Xo to be a func when theme init
|
||||
func Xo(h *wp.Handle) {
|
||||
xx.Xo()
|
||||
route2.Delete(`(?P<control>\w+)/(?P<method>\w+)`)
|
||||
h.ReplaceHandle(constraints.PipeRender, "wp.RenderTemplate", func(h *wp.Handle) {
|
||||
h.SetData("aa", "xyxxxx")
|
||||
h.RenderHtml(tt, http.StatusOK, "a.gohtml")
|
||||
h.StopPipe()
|
||||
})
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
#/bin/bash
|
||||
|
||||
# copy plugintt to other dir and remove .dev suffix
|
||||
# note the go version and build tool flag must same to server build
|
||||
# eg: -gcflags all="-N -l" --race may used in ide debug
|
||||
go mod tidy
|
||||
go build -buildmode=plugin -o xx.so main.go
|
@ -1,141 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/dao"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"strconv"
|
||||
str "strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RdmCache[K comparable, V any] struct {
|
||||
expired func() time.Duration
|
||||
rdb *redis.Client
|
||||
keyFn func(K) string
|
||||
name string
|
||||
resFn func(map[string]string) V
|
||||
saveData func(V) map[string]string
|
||||
}
|
||||
|
||||
func (r *RdmCache[K, V]) SetExpiredTime(f func() time.Duration) {
|
||||
r.expired = f
|
||||
}
|
||||
|
||||
func (r *RdmCache[K, V]) Get(ctx context.Context, key K) (V, bool) {
|
||||
var re V
|
||||
result, err := r.rdb.Exists(ctx, r.keyFn(key)).Result()
|
||||
if result <= 0 || err != nil {
|
||||
return re, false
|
||||
}
|
||||
|
||||
rr, err := r.rdb.HGetAll(ctx, r.keyFn(key)).Result()
|
||||
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return re, false
|
||||
}
|
||||
if err != nil {
|
||||
return re, false
|
||||
}
|
||||
return r.resFn(rr), true
|
||||
}
|
||||
|
||||
func (r *RdmCache[K, V]) Set(ctx context.Context, key K, val V) {
|
||||
k := r.keyFn(key)
|
||||
result, err := r.rdb.HSet(ctx, k, r.saveData(val)).Result()
|
||||
b, err := r.rdb.Expire(ctx, k, r.expired()).Result()
|
||||
if err != nil {
|
||||
fmt.Println(result, b, err)
|
||||
return
|
||||
}
|
||||
fmt.Println(result, err)
|
||||
}
|
||||
|
||||
func (r *RdmCache[K, V]) GetExpireTime(ctx context.Context) time.Duration {
|
||||
return r.expired()
|
||||
}
|
||||
|
||||
func (r *RdmCache[K, V]) Ttl(ctx context.Context, key K) time.Duration {
|
||||
result, err := r.rdb.TTL(ctx, r.keyFn(key)).Result()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *RdmCache[K, V]) Flush(ctx context.Context) {
|
||||
fmt.Println("flush redis cache")
|
||||
}
|
||||
|
||||
func (r *RdmCache[K, V]) Del(ctx context.Context, key ...K) {
|
||||
r.rdb.Del(ctx, slice.Map(key, r.keyFn)...)
|
||||
}
|
||||
|
||||
func (r *RdmCache[K, V]) ClearExpired(ctx context.Context) {
|
||||
fmt.Println("clear expired redis cache")
|
||||
}
|
||||
|
||||
// RedisCache use step:
|
||||
// 1 go build -gcflags all="-N -l" --race -buildmode=plugin -o redisCache.so main.go && cp ./redisCache.so ../wp-go/plugins/
|
||||
// 2 wp-go config add redisCache plugin
|
||||
func RedisCache(h *wp.Handle) {
|
||||
vv, ok := cachemanager.GetMapCache[string, dao.PostIds]("listPostIds")
|
||||
if ok {
|
||||
_, ok := any(vv.Cache).(*RdmCache[string, dao.PostIds])
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
reload.AppendOnceFn(func() {
|
||||
err := cachemanager.SetMapCache("listPostIds", vv)
|
||||
if err != nil {
|
||||
logs.Error(err, "set recovery listPostIds cache err")
|
||||
} else {
|
||||
cachemanager.PushOrSetFlush(cachemanager.Queue{Name: "listPostIds", Fn: vv.Flush})
|
||||
cachemanager.PushOrSetClearExpired(cachemanager.Queue{Name: "listPostIds", Fn: vv.Flush})
|
||||
fmt.Println("recovery listPostIds cache ok")
|
||||
}
|
||||
})
|
||||
rdm := redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "", // no password set
|
||||
DB: 0, // use default DB
|
||||
})
|
||||
r := RdmCache[string, dao.PostIds]{
|
||||
expired: func() time.Duration {
|
||||
return time.Minute
|
||||
},
|
||||
keyFn: func(u string) string {
|
||||
return strings.Join("postIds:", u)
|
||||
},
|
||||
rdb: rdm,
|
||||
name: "",
|
||||
resFn: func(m map[string]string) dao.PostIds {
|
||||
return dao.PostIds{
|
||||
Ids: slice.Map(str.Split(m["ids"], ","), strings.ToInt[uint64]),
|
||||
Length: strings.ToInt[int](m["length"]),
|
||||
}
|
||||
},
|
||||
saveData: func(ids dao.PostIds) map[string]string {
|
||||
t := slice.Map(ids.Ids, number.IntToString[uint64])
|
||||
return map[string]string{
|
||||
"ids": str.Join(t, ","),
|
||||
"length": strconv.Itoa(ids.Length),
|
||||
}
|
||||
},
|
||||
}
|
||||
cachemanager.NewMapCache[string, dao.PostIds](&r, nil, dao.SearchPostIds, config.GetConfig().CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.PostListCacheTime
|
||||
})
|
||||
fmt.Println("redis cache inited ok")
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
module redisCache
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/fthvgb1/wp-go v0.0.0-20231210111549-d72bed0c8c4e
|
||||
github.com/redis/go-redis/v9 v9.3.0
|
||||
)
|
||||
|
||||
replace github.com/fthvgb1/wp-go v0.0.0-20231210111549-d72bed0c8c4e => ../wp-go
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.10.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/elliotchance/phpserialize v1.3.3 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sessions v0.0.5 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.16.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.2.2 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/arch v0.6.0 // indirect
|
||||
golang.org/x/crypto v0.15.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
@ -1,11 +0,0 @@
|
||||
package xx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func Xo() {
|
||||
fmt.Println("xxoo")
|
||||
fmt.Println(decimal.Max(decimal.NewFromFloat(32.3333333333333), decimal.NewFromFloat(32.33333333333331)).String())
|
||||
}
|
@ -1,187 +0,0 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/plugin/digest"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var more = regexp.MustCompile("<!--more(.*?)?-->")
|
||||
|
||||
var removeWpBlock = regexp.MustCompile("<!-- /?wp:.*-->")
|
||||
|
||||
type DigestConfig struct {
|
||||
DigestWordCount int `yaml:"digestWordCount"`
|
||||
DigestAllowTag string `yaml:"digestAllowTag"`
|
||||
DigestRegex string `yaml:"digestRegex"`
|
||||
DigestTagOccupyNum []struct {
|
||||
Tag string `yaml:"tag"`
|
||||
Num int `yaml:"num"`
|
||||
ChuckOvered bool `yaml:"chuckOvered"`
|
||||
EscapeCharacter []struct {
|
||||
Tags string `yaml:"tags"`
|
||||
Character []string `yaml:"character"`
|
||||
Num int `yaml:"num"`
|
||||
ChuckOvered bool `yaml:"chuckOvered"`
|
||||
} `yaml:"escapeCharacter"`
|
||||
} `yaml:"digestTagOccupyNum"`
|
||||
specialSolve map[string]digest.SpecialSolveConf
|
||||
}
|
||||
|
||||
var digestConfig *safety.Var[DigestConfig]
|
||||
|
||||
func InitDigestCache() {
|
||||
cachemanager.NewMemoryMapCache(nil, digestRaw, config.GetConfig().CacheTime.DigestCacheTime, "digestPlugin", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.DigestCacheTime
|
||||
})
|
||||
|
||||
digestConfig = reload.VarsBy(func() DigestConfig {
|
||||
c, err := config.GetCustomizedConfig[DigestConfig]()
|
||||
if err != nil {
|
||||
logs.Error(err, "get digest config fail")
|
||||
c.DigestWordCount = config.GetConfig().DigestWordCount
|
||||
c.DigestAllowTag = config.GetConfig().DigestAllowTag
|
||||
return c
|
||||
}
|
||||
if c.DigestRegex != "" {
|
||||
digest.SetQutos(c.DigestRegex)
|
||||
}
|
||||
if len(c.DigestTagOccupyNum) <= 1 {
|
||||
return c
|
||||
}
|
||||
c.specialSolve = ParseDigestConf(c)
|
||||
return c
|
||||
}, "digestConfig")
|
||||
}
|
||||
|
||||
func ParseDigestConf(c DigestConfig) map[string]digest.SpecialSolveConf {
|
||||
specialSolve := map[string]digest.SpecialSolveConf{}
|
||||
for _, item := range c.DigestTagOccupyNum {
|
||||
tags := strings.Split(strings.ReplaceAll(item.Tag, " ", ""), "<")
|
||||
for _, tag := range tags {
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
ec := make(map[rune]digest.SpecialSolve)
|
||||
specialTags := make(map[string]digest.SpecialSolve)
|
||||
tag = str.Join("<", tag)
|
||||
if len(item.EscapeCharacter) > 0 {
|
||||
for _, esc := range item.EscapeCharacter {
|
||||
for _, i := range esc.Character {
|
||||
s := []rune(i)
|
||||
if len(s) == 1 {
|
||||
ec[s[0]] = digest.SpecialSolve{
|
||||
Num: esc.Num,
|
||||
ChuckOvered: esc.ChuckOvered,
|
||||
}
|
||||
}
|
||||
}
|
||||
if esc.Tags == "" {
|
||||
continue
|
||||
}
|
||||
tagss := strings.Split(strings.ReplaceAll(esc.Tags, " ", ""), "<")
|
||||
for _, t := range tagss {
|
||||
if t == "" {
|
||||
continue
|
||||
}
|
||||
t = str.Join("<", t)
|
||||
specialTags[t] = digest.SpecialSolve{
|
||||
Num: esc.Num,
|
||||
ChuckOvered: esc.ChuckOvered,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
v, ok := specialSolve[tag]
|
||||
if !ok {
|
||||
specialSolve[tag] = digest.SpecialSolveConf{
|
||||
Num: item.Num,
|
||||
ChuckOvered: item.ChuckOvered,
|
||||
EscapeCharacter: ec,
|
||||
Tags: specialTags,
|
||||
}
|
||||
continue
|
||||
}
|
||||
v.Num = item.Num
|
||||
v.ChuckOvered = item.ChuckOvered
|
||||
v.EscapeCharacter = maps.Merge(v.EscapeCharacter, ec)
|
||||
v.Tags = maps.Merge(v.Tags, specialTags)
|
||||
specialSolve[tag] = v
|
||||
}
|
||||
}
|
||||
return specialSolve
|
||||
}
|
||||
|
||||
func RemoveWpBlock(s string) string {
|
||||
return removeWpBlock.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
func digestRaw(ctx context.Context, id uint64, arg ...any) (string, error) {
|
||||
s := arg[1].(string)
|
||||
limit := arg[3].(int)
|
||||
if limit < 0 {
|
||||
return s, nil
|
||||
} else if limit == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
s = more.ReplaceAllString(s, "")
|
||||
fn := helper.GetContextVal(ctx, "postMoreFn", PostsMore)
|
||||
return Digests(s, id, limit, fn), nil
|
||||
}
|
||||
|
||||
func Digests(content string, id uint64, limit int, fn func(id uint64, content, closeTag string) string) string {
|
||||
closeTag := ""
|
||||
content = RemoveWpBlock(content)
|
||||
c := digestConfig.Load()
|
||||
tag := c.DigestAllowTag
|
||||
if tag == "" {
|
||||
tag = "<a><b><blockquote><br><cite><code><dd><del><div><dl><dt><em><h1><h2><h3><h4><h5><h6><i><img><li><ol><p><pre><span><strong><ul>"
|
||||
}
|
||||
content = digest.StripTags(content, tag)
|
||||
length := utf8.RuneCountInString(content) + 1
|
||||
if length <= limit {
|
||||
return content
|
||||
}
|
||||
if len(c.specialSolve) > 0 {
|
||||
content, closeTag = digest.CustomizeHtml(content, limit, c.specialSolve)
|
||||
} else {
|
||||
content, closeTag = digest.Html(content, limit)
|
||||
}
|
||||
|
||||
if fn == nil {
|
||||
return PostsMore(id, content, closeTag)
|
||||
}
|
||||
return fn(id, content, closeTag)
|
||||
}
|
||||
|
||||
func PostsMore(id uint64, content, closeTag string) string {
|
||||
tmp := `%s......%s<p class="read-more"><a href="/p/%d">继续阅读</a></p>`
|
||||
if strings.Contains(closeTag, "pre") || strings.Contains(closeTag, "code") {
|
||||
tmp = `%s%s......<p class="read-more"><a href="/p/%d">继续阅读</a></p>`
|
||||
}
|
||||
content = fmt.Sprintf(tmp, content, closeTag, id)
|
||||
return content
|
||||
}
|
||||
|
||||
func Digest(ctx context.Context, post *models.Posts, limit int) {
|
||||
content, _ := cachemanager.GetBy[string]("digestPlugin", ctx, post.Id, time.Second, ctx, post.PostContent, post.Id, limit)
|
||||
post.PostContent = content
|
||||
}
|
||||
|
||||
func PostExcerpt(post *models.Posts) {
|
||||
post.PostContent = strings.Replace(post.PostExcerpt, "\n", "<br/>", -1)
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PageEle struct {
|
||||
PrevEle string
|
||||
NextEle string
|
||||
DotsEle string
|
||||
MiddleEle string
|
||||
CurrentEle string
|
||||
}
|
||||
|
||||
func TwentyFifteenPagination() PageEle {
|
||||
return twentyFifteen
|
||||
}
|
||||
func TwentyFifteenCommentPagination() CommentPageEle {
|
||||
return twentyFifteenComment
|
||||
}
|
||||
|
||||
type CommentPageEle struct {
|
||||
PageEle
|
||||
}
|
||||
|
||||
var twentyFifteen = PageEle{
|
||||
PrevEle: `<a class="prev page-numbers" href="%s">上一页</a>`,
|
||||
NextEle: `<a class="next page-numbers" href="%s">下一页</a>`,
|
||||
DotsEle: `<span class="page-numbers dots">…</span>`,
|
||||
MiddleEle: `<a class="page-numbers" href="%s"><span class="meta-nav screen-reader-text">页 </span>%d</a>
|
||||
`,
|
||||
CurrentEle: `<span aria-current="page" class="page-numbers current">
|
||||
<span class="meta-nav screen-reader-text">页 </span>%d</span>`,
|
||||
}
|
||||
var twentyFifteenComment = CommentPageEle{
|
||||
PageEle{
|
||||
PrevEle: `<div class="nav-previous"><a href="%s">%s</a></div>`,
|
||||
NextEle: `<div class="nav-next"><a href="%s">%s</a></div>`,
|
||||
},
|
||||
}
|
||||
|
||||
func (p PageEle) Current(page, totalPage, totalRow int) string {
|
||||
return fmt.Sprintf(p.CurrentEle, page)
|
||||
}
|
||||
|
||||
func (p PageEle) Prev(url string) string {
|
||||
return fmt.Sprintf(p.PrevEle, url)
|
||||
}
|
||||
|
||||
func (p PageEle) Next(url string) string {
|
||||
return fmt.Sprintf(p.NextEle, url)
|
||||
}
|
||||
|
||||
func (p PageEle) Dots() string {
|
||||
return p.DotsEle
|
||||
}
|
||||
|
||||
func (p PageEle) Middle(page int, url string) string {
|
||||
return fmt.Sprintf(p.MiddleEle, url, page)
|
||||
}
|
||||
|
||||
var reg = regexp.MustCompile(`(/page)/(\d+)`)
|
||||
var commentReg = regexp.MustCompile(`/comment-page-(\d+)`)
|
||||
|
||||
var queryParam = []string{"paged", "cat", "m", "author", "tag"}
|
||||
|
||||
func (p PageEle) Urls(u url.URL, page int, isTLS bool) string {
|
||||
var path, query = u.Path, u.RawQuery
|
||||
if path == "/" {
|
||||
v := u.Query()
|
||||
for _, q := range queryParam {
|
||||
if v.Get(q) != "" {
|
||||
v.Set("paged", strconv.Itoa(page))
|
||||
return str.Join(path, "?", v.Encode())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.Contains(path, "/page/") {
|
||||
path = fmt.Sprintf("%s%s", path, "/page/1")
|
||||
}
|
||||
if page == 1 {
|
||||
path = reg.ReplaceAllString(path, "")
|
||||
} else {
|
||||
s := fmt.Sprintf("$1/%d", page)
|
||||
path = reg.ReplaceAllString(path, s)
|
||||
}
|
||||
path = strings.Replace(path, "//", "/", -1)
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
if query != "" {
|
||||
return str.Join(path, "?", query)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (p CommentPageEle) Urls(u url.URL, page int, isTLS bool) string {
|
||||
var path, query = u.Path, u.RawQuery
|
||||
if path == "/" {
|
||||
v := u.Query()
|
||||
if v.Get("p") != "" {
|
||||
v.Set("cpage", strconv.Itoa(page))
|
||||
return str.Join(path, "?", v.Encode(), "#comments")
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.Contains(path, "/comment-page-") {
|
||||
path = fmt.Sprintf("%s%s", path, "/comment-page-1")
|
||||
}
|
||||
path = commentReg.ReplaceAllString(path, fmt.Sprintf("/comment-page-%d", page))
|
||||
path = strings.Replace(path, "//", "/", -1)
|
||||
ur := path
|
||||
if query != "" {
|
||||
ur = str.Join(path, "?", query)
|
||||
}
|
||||
ur = str.Join(ur, "#comments")
|
||||
return ur
|
||||
}
|
||||
|
||||
func (p CommentPageEle) Middle(page int, url string) string {
|
||||
return ""
|
||||
}
|
||||
func (p CommentPageEle) Dots() string {
|
||||
return ""
|
||||
}
|
||||
func (p CommentPageEle) Current(page, totalPage, totalRow int) string {
|
||||
return ""
|
||||
}
|
||||
func (p CommentPageEle) Prev(url string) string {
|
||||
return fmt.Sprintf(p.PrevEle, url, helper.Or(wpconfig.GetOption("comment_order") == "asc", "较早评论", "较新评论"))
|
||||
}
|
||||
|
||||
func (p CommentPageEle) Next(url string) string {
|
||||
return fmt.Sprintf(p.NextEle, url, helper.Or(wpconfig.GetOption("comment_order") == "asc", "较新评论", "较早评论"))
|
||||
}
|
||||
|
||||
type PaginationNav struct {
|
||||
Currents func(page, totalPage, totalRows int) string
|
||||
Prevs func(url string) string
|
||||
Nexts func(url string) string
|
||||
Dotss func() string
|
||||
Middles func(page int, url string) string
|
||||
Urlss func(u url.URL, page int, isTLS bool) string
|
||||
}
|
||||
|
||||
func (p PaginationNav) Current(page, totalPage, totalRows int) string {
|
||||
return p.Currents(page, totalPage, totalRows)
|
||||
}
|
||||
|
||||
func (p PaginationNav) Prev(url string) string {
|
||||
return p.Prevs(url)
|
||||
}
|
||||
|
||||
func (p PaginationNav) Next(url string) string {
|
||||
return p.Nexts(url)
|
||||
}
|
||||
|
||||
func (p PaginationNav) Dots() string {
|
||||
return p.Dotss()
|
||||
}
|
||||
|
||||
func (p PaginationNav) Middle(page int, url string) string {
|
||||
return p.Middles(page, url)
|
||||
}
|
||||
|
||||
func (p PaginationNav) Urls(u url.URL, page int, isTLS bool) string {
|
||||
return p.Urlss(u, page, isTLS)
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package apply
|
||||
|
||||
import "github.com/fthvgb1/wp-go/safety"
|
||||
|
||||
var contains = safety.NewMap[string, any]()
|
||||
|
||||
func SetVal(key string, val any) {
|
||||
contains.Store(key, val)
|
||||
}
|
||||
|
||||
func DelVal(key string) {
|
||||
contains.Delete(key)
|
||||
}
|
||||
|
||||
func GetVal[V any](key string) (V, bool) {
|
||||
v, ok := contains.Load(key)
|
||||
if !ok {
|
||||
var vv V
|
||||
return vv, ok
|
||||
}
|
||||
return v.(V), ok
|
||||
}
|
||||
func GetRawVal(key string) (any, bool) {
|
||||
return contains.Load(key)
|
||||
}
|
||||
|
||||
func GetPlugins() any {
|
||||
v, _ := contains.Load("wp-plugins")
|
||||
return v
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package enlightjs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/phphelper"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Selectors Selectors `json:"selectors"`
|
||||
Options Options `json:"options"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Indent int64 `json:"indent,omitempty"`
|
||||
AmpersandCleanup bool `json:"ampersandCleanup,omitempty"`
|
||||
Linehover bool `json:"linehover,omitempty"`
|
||||
RawcodeDbclick bool `json:"rawcodeDbclick,omitempty"`
|
||||
TextOverflow string `json:"textOverflow,omitempty"`
|
||||
Linenumbers bool `json:"linenumbers,omitempty"`
|
||||
Theme string `json:"theme,omitempty"`
|
||||
Language string `json:"language,omitempty"`
|
||||
RetainCssClasses bool `json:"retainCssClasses,omitempty"`
|
||||
Collapse bool `json:"collapse,omitempty"`
|
||||
ToolbarOuter string `json:"toolbarOuter,omitempty"`
|
||||
ToolbarTop string `json:"toolbarTop,omitempty"`
|
||||
ToolbarBottom string `json:"toolbarBottom,omitempty"`
|
||||
}
|
||||
|
||||
type Selectors struct {
|
||||
Block string `json:"block,omitempty"`
|
||||
Inline string `json:"inline,omitempty"`
|
||||
}
|
||||
|
||||
func EnlighterJS(h *wp.Handle) {
|
||||
h.PushGroupHeadScript(constraints.AllScene, "enlighterjs-css", 20, `<link rel='stylesheet' id='enlighterjs-css' href='/wp-content/plugins/enlighter/cache/enlighterjs.min.css' media='all' />`)
|
||||
|
||||
h.PushCacheGroupFooterScript(constraints.AllScene, "enlighterJs", 10, func(h *wp.Handle) string {
|
||||
op := wpconfig.GetOption("enlighter-options")
|
||||
opp, err := phphelper.UnPHPSerializeToStrAnyMap(op)
|
||||
if err != nil {
|
||||
logs.Error(err, "获取enlighter-option失败", op)
|
||||
return ""
|
||||
}
|
||||
v := Config{
|
||||
Selectors: Selectors{
|
||||
Block: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-selector-block", "pre.EnlighterJSRAW"),
|
||||
Inline: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-selector-inline", "code.EnlighterJSRAW"),
|
||||
},
|
||||
Options: Options{
|
||||
Indent: maps.GetStrAnyValWithDefaults[int64](opp, "enlighterjs-indent", 4),
|
||||
AmpersandCleanup: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-ampersandcleanup", true),
|
||||
Linehover: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linehover", true),
|
||||
RawcodeDbclick: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-rawcodedbclick", true),
|
||||
TextOverflow: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-textoverflow", "break"),
|
||||
Linenumbers: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linenumbers", true),
|
||||
Theme: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-theme", "enlighter"),
|
||||
Language: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-language", "generic"),
|
||||
RetainCssClasses: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-retaincss", false),
|
||||
Collapse: false,
|
||||
ToolbarOuter: "",
|
||||
ToolbarTop: "{BTN_RAW}{BTN_COPY}{BTN_WINDOW}{BTN_WEBSITE}",
|
||||
ToolbarBottom: "",
|
||||
},
|
||||
}
|
||||
conf, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
logs.Error(err, "json化enlighterjs配置失败")
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(enlighterjs, conf)
|
||||
})
|
||||
}
|
||||
|
||||
var enlighterjs = `<script src='/wp-content/plugins/enlighter/cache/enlighterjs.min.js?ver=0A0B0C' id='enlighterjs-js'></script>
|
||||
<script id='enlighterjs-js-after'>
|
||||
!function(e,n){if("undefined"!=typeof EnlighterJS){var o=%s;(e.EnlighterJSINIT=function(){EnlighterJS.init(o.selectors.block,o.selectors.inline,o.options)})()}else{(n&&(n.error||n.log)||function(){})("Error: EnlighterJS resources not loaded yet!")}}(window,console);
|
||||
</script>`
|
@ -1,72 +0,0 @@
|
||||
package wphandle
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle/apply"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle/enlightjs"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle/hiddenlogin"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
)
|
||||
|
||||
var plugins = func() *safety.Map[string, func(*wp.Handle)] {
|
||||
m := safety.NewMap[string, func(*wp.Handle)]()
|
||||
m.Store("Enlightjs", enlightjs.EnlighterJS)
|
||||
m.Store("HiddenLogin", hiddenlogin.HiddenLogin)
|
||||
return m
|
||||
}()
|
||||
|
||||
func RegisterPlugin(name string, fn func(*wp.Handle)) {
|
||||
plugins.Store(name, fn)
|
||||
}
|
||||
|
||||
func UsePlugins(h *wp.Handle, calls ...string) {
|
||||
calls = append(calls, config.GetConfig().Plugins...)
|
||||
for _, call := range calls {
|
||||
call = str.FirstUpper(call)
|
||||
if fn, ok := plugins.Load(call); ok {
|
||||
fn(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func LoadPlugins() {
|
||||
dirPath := config.GetConfig().PluginPath
|
||||
if dirPath == "" {
|
||||
return
|
||||
}
|
||||
glob, err := filepath.Glob(filepath.Join(dirPath, "*.so"))
|
||||
if err != nil {
|
||||
logs.Error(err, "读取插件目录错误", dirPath)
|
||||
return
|
||||
}
|
||||
for _, entry := range glob {
|
||||
p, err := plugin.Open(entry)
|
||||
if err != nil {
|
||||
logs.Error(err, "读取插件错误", entry)
|
||||
continue
|
||||
}
|
||||
name := filepath.Ext(entry)
|
||||
name = filepath.Base(entry[0 : len(entry)-len(name)])
|
||||
name = str.FirstUpper(name)
|
||||
pl, err := p.Lookup(name)
|
||||
if err != nil {
|
||||
logs.Error(err, "插件lookup错误", entry)
|
||||
continue
|
||||
}
|
||||
plu, ok := pl.(func(*wp.Handle))
|
||||
if !ok {
|
||||
logs.Error(errors.New("switch func(*wp.Handle) fail"), "插件转换错误", entry)
|
||||
continue
|
||||
}
|
||||
RegisterPlugin(name, plu)
|
||||
}
|
||||
apply.SetVal("wp-plugins", func(h *wp.Handle) {
|
||||
UsePlugins(h)
|
||||
})
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package hiddenlogin
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
)
|
||||
|
||||
func HiddenLogin(h *wp.Handle) {
|
||||
h.AddActionFilter(widgets.Meta, func(h *wp.Handle, s string, args ...any) string {
|
||||
return str.Replace(s, map[string]string{
|
||||
`<li><a href="/wp-login.php">登录</a></li>`: "",
|
||||
`<li><a href="/feed">登录</a></li>`: "",
|
||||
`<li><a href="/comments/feed">登录</a></li>`: "",
|
||||
})
|
||||
})
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
)
|
||||
|
||||
func Tt(h *wp.Handle) {
|
||||
h.HookHandle(constraints.PipeMiddleware, constraints.AllScene, func(call wp.HandleCall) (wp.HandleCall, bool) {
|
||||
return call, false
|
||||
})
|
||||
/*h.PushPipeHook(constraints.Home, func(pipe wp.Pipe) (wp.Pipe, bool) {
|
||||
return wp.Pipe{}, false
|
||||
})*/
|
||||
//h.DeletePipe(constraints.Home, constraints.PipeMiddleware)
|
||||
h.ReplacePipe(constraints.Home, constraints.PipeMiddleware, wp.NewPipe("log", 500, func(next wp.HandleFn[*wp.Handle], h *wp.Handle) {
|
||||
fmt.Println("ffff")
|
||||
next(h)
|
||||
fmt.Println("iiiii")
|
||||
}))
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/actions"
|
||||
"github.com/fthvgb1/wp-go/app/middleware"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/theme"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-contrib/pprof"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type GinSetter func(*gin.Engine)
|
||||
|
||||
var setters mockmap.Map[string, GinSetter]
|
||||
|
||||
var setterHooks []func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool)
|
||||
|
||||
// SetGinAction 方便插件在init时使用
|
||||
func SetGinAction(name string, hook GinSetter, orders ...float64) {
|
||||
setters.Set(name, hook, orders...)
|
||||
}
|
||||
|
||||
func HookGinSetter(fn func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool)) {
|
||||
setterHooks = append(setterHooks, fn)
|
||||
}
|
||||
|
||||
// DelGinSetter 方便插件在init时使用
|
||||
func DelGinSetter(name string) {
|
||||
setterHooks = append(setterHooks, func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool) {
|
||||
return item, item.Name != name
|
||||
})
|
||||
}
|
||||
|
||||
func SetupRouter() *gin.Engine {
|
||||
// Disable Console Color
|
||||
// gin.DisableConsoleColor()
|
||||
r := gin.New()
|
||||
c := config.GetConfig()
|
||||
SetGinAction("initTrustIp", func(r *gin.Engine) {
|
||||
if len(c.TrustIps) > 0 {
|
||||
err := r.SetTrustedProxies(c.TrustIps)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}, 99.5)
|
||||
|
||||
SetGinAction("setTemplate", func(r *gin.Engine) {
|
||||
r.HTMLRender = theme.BuildTemplate()
|
||||
wpconfig.SetTemplateFs(theme.TemplateFs)
|
||||
}, 90.5)
|
||||
|
||||
siteFlowLimitMiddleware, siteFlow := middleware.FlowLimit(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
|
||||
reload.Append(func() {
|
||||
c = config.GetConfig()
|
||||
siteFlow(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
|
||||
}, "site-flowLimit-config")
|
||||
|
||||
SetGinAction("setGlobalMiddleware", func(r *gin.Engine) {
|
||||
r.Use(
|
||||
gin.Logger(),
|
||||
middleware.ValidateServerNames(),
|
||||
middleware.RecoverAndSendMail(gin.DefaultErrorWriter),
|
||||
siteFlowLimitMiddleware,
|
||||
middleware.SetStaticFileCache,
|
||||
)
|
||||
}, 88.5)
|
||||
|
||||
SetGinAction("setGzip", func(r *gin.Engine) {
|
||||
//gzip 因为一般会用nginx做反代时自动使用gzip,所以go这边本身可以不用
|
||||
if c.Gzip {
|
||||
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{
|
||||
"/wp-includes/", "/wp-content/",
|
||||
})))
|
||||
}
|
||||
}, 87.6)
|
||||
|
||||
SetGinAction("setWpDir", func(r *gin.Engine) {
|
||||
if c.WpDir == "" {
|
||||
panic("wordpress path can't be empty")
|
||||
}
|
||||
r.Static("/wp-content/uploads", str.Join(c.WpDir, "/wp-content/uploads"))
|
||||
r.Static("/wp-content/themes", str.Join(c.WpDir, "/wp-content/themes"))
|
||||
r.Static("/wp-content/plugins", str.Join(c.WpDir, "/wp-content/plugins"))
|
||||
r.Static("/wp-includes/css", str.Join(c.WpDir, "/wp-includes/css"))
|
||||
r.Static("/wp-includes/fonts", str.Join(c.WpDir, "/wp-includes/fonts"))
|
||||
r.Static("/wp-includes/js", str.Join(c.WpDir, "/wp-includes/js"))
|
||||
}, 86.1)
|
||||
|
||||
SetGinAction("setSession", func(r *gin.Engine) {
|
||||
store := cookie.NewStore([]byte("secret"))
|
||||
r.Use(sessions.Sessions("go-wp", store))
|
||||
}, 85.1)
|
||||
|
||||
SetGinAction("setRoute", func(r *gin.Engine) {
|
||||
r.GET("/", actions.Feed, middleware.SearchLimit(c.SingleIpSearchNum),
|
||||
actions.ThemeHook(constraints.Home))
|
||||
r.GET("/page/:page", actions.ThemeHook(constraints.Home))
|
||||
r.GET("/p/category/:category", actions.ThemeHook(constraints.Category))
|
||||
r.GET("/p/category/:category/page/:page", actions.ThemeHook(constraints.Category))
|
||||
r.GET("/p/tag/:tag", actions.ThemeHook(constraints.Tag))
|
||||
r.GET("/p/tag/:tag/page/:page", actions.ThemeHook(constraints.Tag))
|
||||
r.GET("/p/date/:year/:month", actions.ThemeHook(constraints.Archive))
|
||||
r.GET("/p/date/:year/:month/page/:page", actions.ThemeHook(constraints.Archive))
|
||||
r.GET("/p/author/:author", actions.ThemeHook(constraints.Author))
|
||||
r.GET("/p/author/:author/page/:page", actions.ThemeHook(constraints.Author))
|
||||
r.POST("/login", actions.Login)
|
||||
r.GET("/p/:id", actions.ThemeHook(constraints.Detail))
|
||||
r.GET("/p/:id/comment-page-:page", actions.ThemeHook(constraints.Detail))
|
||||
r.GET("/p/:id/feed", actions.PostFeed)
|
||||
r.GET("/feed", actions.SiteFeed)
|
||||
r.GET("/comments/feed", actions.CommentsFeed)
|
||||
commentMiddleWare, _ := middleware.FlowLimit(c.MaxRequestSleepNum, 5, c.CacheTime.SleepTime)
|
||||
r.POST("/comment", commentMiddleWare, actions.PostComment)
|
||||
|
||||
r.NoRoute(actions.ThemeHook(constraints.NoRoute))
|
||||
}, 84.6)
|
||||
|
||||
SetGinAction("setpprof", func(r *gin.Engine) {
|
||||
if c.Pprof != "" {
|
||||
pprof.Register(r, c.Pprof)
|
||||
}
|
||||
}, 80.8)
|
||||
|
||||
for _, hook := range setterHooks {
|
||||
setters = slice.FilterAndMap(setters, hook)
|
||||
}
|
||||
|
||||
slice.SimpleSort(setters, slice.DESC, func(t mockmap.Item[string, GinSetter]) float64 {
|
||||
return t.Order
|
||||
})
|
||||
|
||||
for _, fn := range setters {
|
||||
fn.Value(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/fthvgb1/wp-go/multipTemplate"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed *[^.go]
|
||||
var TemplateFs embed.FS
|
||||
|
||||
var templates = safety.NewMap[string, *template.Template]() //方便外部获取模板render后的字符串,不然在gin中获取不了
|
||||
|
||||
var multiple *multipTemplate.MultipleFsTemplate
|
||||
|
||||
func BuildTemplate() *multipTemplate.MultipleFsTemplate {
|
||||
if multiple != nil {
|
||||
tt := multipTemplate.NewFsTemplate(TemplateFs)
|
||||
commonTemplate(tt)
|
||||
for k, v := range map[string]*template.Template(any(tt.Template).(multipTemplate.TemplateMaps)) {
|
||||
multiple.Template.Store(k, v)
|
||||
}
|
||||
} else {
|
||||
multiple = multipTemplate.NewFsTemplates(TemplateFs, templates)
|
||||
commonTemplate(multiple)
|
||||
}
|
||||
|
||||
/*t.AddTemplate("twentyfifteen/*[^layout]/*.gohtml", FuncMap(), "twentyfifteen/layout/*.gohtml","wp/template.gohtml"). //单个主题设置
|
||||
AddTemplate("twentyseventeen/*[^layout]/*.gohtml", FuncMap(), "twentyseventeen/layout/*.gohtml","wp/template.gohtml")*/
|
||||
return multiple
|
||||
}
|
||||
|
||||
func GetMultipleTemplate() *multipTemplate.MultipleFsTemplate {
|
||||
if multiple == nil {
|
||||
BuildTemplate()
|
||||
}
|
||||
return multiple
|
||||
}
|
||||
|
||||
func GetTemplate(name string) (*template.Template, bool) {
|
||||
t, ok := templates.Load(name)
|
||||
return t, ok
|
||||
}
|
||||
|
||||
// 所有主题模板通用设置
|
||||
func commonTemplate(t *multipTemplate.MultipleFsTemplate) {
|
||||
m, err := fs.Glob(t.Fs, "*/posts/*.gohtml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
funMap := FuncMap()
|
||||
for _, main := range m {
|
||||
file := filepath.Base(main)
|
||||
dir := strings.Split(main, "/")[0]
|
||||
templ := template.Must(template.New(file).Funcs(funMap).ParseFS(t.Fs, main, filepath.Join(dir, "layout/*.gohtml"), "wp/template.gohtml"))
|
||||
t.SetTemplate(main, templ)
|
||||
}
|
||||
}
|
||||
|
||||
func IsTemplateDirExists(tml string) bool {
|
||||
arr, err := TemplateFs.ReadDir(tml)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if len(arr) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsTemplateExists(tml string) bool {
|
||||
t, ok := templates.Load(tml)
|
||||
return ok && t != nil
|
||||
}
|
||||
|
||||
func SetTemplate(name string, val *template.Template) {
|
||||
templates.Store(name, val)
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
)
|
||||
|
||||
var themeMap = safety.NewMap[string, func(*wp.Handle)]()
|
||||
|
||||
func AddTheme(name string, fn func(handle *wp.Handle)) {
|
||||
themeMap.Store(name, fn)
|
||||
}
|
||||
|
||||
func DelTheme(name string) {
|
||||
themeMap.Delete(name)
|
||||
}
|
||||
|
||||
func GetTheme(name string) (func(*wp.Handle), bool) {
|
||||
return themeMap.Load(name)
|
||||
}
|
||||
|
||||
func IsThemeHookFuncExist(name string) bool {
|
||||
_, ok := themeMap.Load(name)
|
||||
return ok
|
||||
}
|
||||
|
||||
func Hook(themeName string, h *wp.Handle) {
|
||||
fn, ok := themeMap.Load(themeName)
|
||||
if ok && fn != nil {
|
||||
fn(h)
|
||||
return
|
||||
}
|
||||
panic(str.Join("theme ", themeName, " don't exist"))
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"html/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
func postsFn(fn func(models.Posts) string, a models.Posts) string {
|
||||
return fn(a)
|
||||
}
|
||||
|
||||
func FuncMap() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"unescaped": func(s string) any {
|
||||
return template.HTML(s)
|
||||
},
|
||||
"dateCh": func(t time.Time) any {
|
||||
return t.Format("2006年 01月 02日")
|
||||
},
|
||||
"timeFormat": func(t time.Time, format string) any {
|
||||
return t.Format(format)
|
||||
},
|
||||
"getOption": func(k string) string {
|
||||
return wpconfig.GetOption(k)
|
||||
},
|
||||
"getLang": wpconfig.GetLang,
|
||||
"postsFn": postsFn,
|
||||
"exec": func(fn func() string) template.HTML {
|
||||
return template.HTML(fn())
|
||||
},
|
||||
"callFuncString": func(fn func(string) string, s string) template.HTML {
|
||||
return template.HTML(fn(s))
|
||||
},
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/theme/twentyfifteen"
|
||||
"github.com/fthvgb1/wp-go/app/theme/twentyseventeen"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
)
|
||||
|
||||
func InitTheme() {
|
||||
AddTheme(twentyfifteen.ThemeName, twentyfifteen.Hook)
|
||||
AddTheme(twentyseventeen.ThemeName, twentyseventeen.Hook)
|
||||
}
|
||||
|
||||
func GetCurrentTheme() string {
|
||||
themeName := config.GetConfig().Theme
|
||||
if themeName == "" {
|
||||
themeName = wpconfig.GetOption("template")
|
||||
}
|
||||
if !IsTemplateDirExists(themeName) {
|
||||
themeName = "twentyfifteen"
|
||||
}
|
||||
return themeName
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
{{define "layout/sidebar" }}
|
||||
<div id="widget-area" class="widget-area" role="complementary">
|
||||
{{template "common/sidebarWidget" .}}
|
||||
</div>
|
||||
{{end}}
|
@ -1,295 +0,0 @@
|
||||
package twentyfifteen
|
||||
|
||||
import "github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
|
||||
type themeSupport struct {
|
||||
CustomBackground customBackground `json:"custom-background"`
|
||||
EditorColorPalette []EditorColorPalette `json:"editor-color-palette"`
|
||||
EditorGradientPresets []EditorGradientPresets `json:"editor-gradient-presets"`
|
||||
}
|
||||
type customBackground struct {
|
||||
DefaultImage string `json:"default-image"`
|
||||
DefaultPreset string `json:"default-preset"`
|
||||
DefaultPositionX string `json:"default-position-x"`
|
||||
DefaultPositionY string `json:"default-position-y"`
|
||||
DefaultSize string `json:"default-size"`
|
||||
DefaultRepeat string `json:"default-repeat"`
|
||||
DefaultAttachment string `json:"default-attachment"`
|
||||
DefaultColor string `json:"default-color"`
|
||||
WpHeadCallback string `json:"wp-head-callback"`
|
||||
AdminHeadCallback string `json:"admin-head-callback"`
|
||||
AdminPreviewCallback string `json:"admin-preview-callback"`
|
||||
}
|
||||
|
||||
type EditorColorPalette struct {
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Color string `json:"color"`
|
||||
}
|
||||
type EditorGradientPresets struct {
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Gradient string `json:"gradient"`
|
||||
}
|
||||
|
||||
var themesupport = themeSupport{
|
||||
CustomBackground: customBackground{
|
||||
DefaultImage: "",
|
||||
DefaultPreset: "default",
|
||||
DefaultPositionX: "left",
|
||||
DefaultPositionY: "top",
|
||||
DefaultSize: "auto",
|
||||
DefaultRepeat: "repeat",
|
||||
DefaultAttachment: "fixed",
|
||||
DefaultColor: "f1f1f1",
|
||||
WpHeadCallback: "_custom_background_cb",
|
||||
AdminHeadCallback: "",
|
||||
AdminPreviewCallback: "",
|
||||
},
|
||||
EditorColorPalette: []EditorColorPalette{
|
||||
{
|
||||
Name: "暗灰色",
|
||||
Slug: "dark-gray",
|
||||
Color: "#111",
|
||||
},
|
||||
{
|
||||
Name: "亮灰色",
|
||||
Slug: "light-gray",
|
||||
Color: "#f1f1f1",
|
||||
},
|
||||
{
|
||||
Name: "白色",
|
||||
Slug: "white",
|
||||
Color: "#fff",
|
||||
},
|
||||
{
|
||||
Name: "黄色",
|
||||
Slug: "yellow",
|
||||
Color: "#f4ca16",
|
||||
},
|
||||
{
|
||||
Name: "暗棕色",
|
||||
Slug: "dark-brown",
|
||||
Color: "#352712",
|
||||
},
|
||||
{
|
||||
Name: "粉色",
|
||||
Slug: "medium-pink",
|
||||
Color: "#e53b51",
|
||||
},
|
||||
{
|
||||
Name: "浅粉色",
|
||||
Slug: "light-pink",
|
||||
Color: "#ffe5d1",
|
||||
},
|
||||
{
|
||||
Name: "暗紫色",
|
||||
Slug: "dark-purple",
|
||||
Color: "#2e2256",
|
||||
},
|
||||
{
|
||||
Name: "紫色",
|
||||
Slug: "purple",
|
||||
Color: "#674970",
|
||||
},
|
||||
{
|
||||
Name: "蓝灰色",
|
||||
Slug: "blue-gray",
|
||||
Color: "#22313f",
|
||||
},
|
||||
{
|
||||
Name: "亮蓝色",
|
||||
Slug: "bright-blue",
|
||||
Color: "#55c3dc",
|
||||
},
|
||||
{
|
||||
Name: "浅蓝色",
|
||||
Slug: "light-blue",
|
||||
Color: "#e9f2f9",
|
||||
},
|
||||
},
|
||||
EditorGradientPresets: []EditorGradientPresets{
|
||||
{
|
||||
Name: "Dark Gray Gradient",
|
||||
Slug: "dark-gray-gradient-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(17,17,17,1) 0%, rgba(42,42,42,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "Light Gray Gradient",
|
||||
Slug: "light-gray-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(241,241,241,1) 0%, rgba(215,215,215,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "White Gradient",
|
||||
Slug: "white-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(255,255,255,1) 0%, rgba(230,230,230,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "Yellow Gradient",
|
||||
Slug: "yellow-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(244,202,22,1) 0%, rgba(205,168,10,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "Dark Brown Gradient",
|
||||
Slug: "dark-brown-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(53,39,18,1) 0%, rgba(91,67,31,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "Medium Pink Gradient",
|
||||
Slug: "medium-pink-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(229,59,81,1) 0%, rgba(209,28,51,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "Light Pink Gradient",
|
||||
Slug: "light-pink-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(255,229,209,1) 0%, rgba(255,200,158,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "Dark Purple Gradient",
|
||||
Slug: "dark-purple-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(46,34,86,1) 0%, rgba(66,48,123,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "Purple Gradient",
|
||||
Slug: "purple-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(103,73,112,1) 0%, rgba(131,93,143,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "Blue Gray Gradient",
|
||||
Slug: "blue-gray-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(34,49,63,1) 0%, rgba(52,75,96,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "Bright Blue Gradient",
|
||||
Slug: "bright-blue-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(85,195,220,1) 0%, rgba(43,180,211,1) 100%)",
|
||||
},
|
||||
{
|
||||
Name: "Light Blue Gradient",
|
||||
Slug: "light-blue-gradient",
|
||||
Gradient: "linear-gradient(90deg, rgba(233,242,249,1) 0%, rgba(193,218,238,1) 100%)",
|
||||
},
|
||||
},
|
||||
}
|
||||
var colorscheme = map[string]ColorScheme{
|
||||
"default": {
|
||||
Label: "Default",
|
||||
Colors: []string{
|
||||
"#f1f1f1",
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#333333",
|
||||
"#333333",
|
||||
"#f7f7f7",
|
||||
},
|
||||
},
|
||||
"dark": {
|
||||
Label: "Dark",
|
||||
Colors: []string{
|
||||
"#111111",
|
||||
"#202020",
|
||||
"#202020",
|
||||
"#bebebe",
|
||||
"#bebebe",
|
||||
"#1b1b1b",
|
||||
},
|
||||
},
|
||||
|
||||
"pink": {
|
||||
Label: "Pink",
|
||||
Colors: []string{
|
||||
"#ffe5d1",
|
||||
"#e53b51",
|
||||
"#ffffff",
|
||||
"#352712",
|
||||
"#ffffff",
|
||||
"#f1f1f1",
|
||||
},
|
||||
},
|
||||
"purple": {
|
||||
Label: "Purple",
|
||||
Colors: []string{
|
||||
"#674970",
|
||||
"#2e2256",
|
||||
"#ffffff",
|
||||
"#2e2256",
|
||||
"#ffffff",
|
||||
"#f1f1f1",
|
||||
},
|
||||
},
|
||||
"blue": {
|
||||
Label: "Blue",
|
||||
Colors: []string{
|
||||
"#e9f2f9",
|
||||
"#55c3dc",
|
||||
"#ffffff",
|
||||
"#22313f",
|
||||
"#ffffff",
|
||||
"#f1f1f1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var _ = func() struct{} {
|
||||
v := wpconfig.ThemeSupport{
|
||||
CoreBlockPatterns: true,
|
||||
WidgetsBlockEditor: true,
|
||||
AutomaticFeedLinks: true,
|
||||
TitleTag: true,
|
||||
PostThumbnails: true,
|
||||
Menus: true,
|
||||
HTML5: []string{
|
||||
"search-form",
|
||||
"comment-form",
|
||||
"comment-list",
|
||||
"gallery",
|
||||
"caption",
|
||||
"script",
|
||||
"style",
|
||||
"navigation-widgets",
|
||||
},
|
||||
PostFormats: []string{
|
||||
"aside",
|
||||
"image",
|
||||
"video",
|
||||
"quote",
|
||||
"link",
|
||||
"gallery",
|
||||
"status",
|
||||
"audio",
|
||||
"chat",
|
||||
},
|
||||
CustomLogo: wpconfig.CustomLogo{
|
||||
Width: 248,
|
||||
Height: 248,
|
||||
FlexWidth: false,
|
||||
FlexHeight: true,
|
||||
HeaderText: "",
|
||||
UnlinkHomepageLogo: false,
|
||||
},
|
||||
CustomizeSelectiveRefreshWidgets: true,
|
||||
EditorStyle: true,
|
||||
EditorStyles: true,
|
||||
WpBlockStyles: true,
|
||||
ResponsiveEmbeds: true,
|
||||
CustomHeader: wpconfig.CustomHeader{
|
||||
DefaultImage: "",
|
||||
RandomDefault: false,
|
||||
Width: 954,
|
||||
Height: 1300,
|
||||
FlexHeight: false,
|
||||
FlexWidth: false,
|
||||
DefaultTextColor: "333333",
|
||||
HeaderText: true,
|
||||
Uploads: true,
|
||||
WpHeadCallback: "twentyfifteen_header_style",
|
||||
AdminHeadCallback: "",
|
||||
AdminPreviewCallback: "",
|
||||
Video: false,
|
||||
VideoActiveCallback: "is_front_page",
|
||||
},
|
||||
Widgets: true,
|
||||
}
|
||||
wpconfig.SetThemeSupport(ThemeName, v)
|
||||
return struct{}{}
|
||||
}()
|
@ -1,63 +0,0 @@
|
||||
package twentyfifteen
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/plugins"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/components"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/middleware"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ThemeName = "twentyfifteen"
|
||||
|
||||
func Hook(h *wp.Handle) {
|
||||
wp.Run(h, configs)
|
||||
}
|
||||
|
||||
func configs(h *wp.Handle) {
|
||||
h.AddActionFilter(widgets.Search, func(h *wp.Handle, s string, args ...any) string {
|
||||
return strings.ReplaceAll(s, `class="search-submit"`, `class="search-submit screen-reader-text"`)
|
||||
})
|
||||
wp.InitPipe(h)
|
||||
middleware.CommonMiddleware(h)
|
||||
setPaginationAndRender(h)
|
||||
h.PushCacheGroupHeadScript(constraints.AllScene, "CalCustomBackGround", 10.005, CalCustomBackGround)
|
||||
h.PushCacheGroupHeadScript(constraints.AllScene, "colorSchemeCss", 10.0056, colorSchemeCss)
|
||||
h.CommonComponents()
|
||||
components.WidgetArea(h)
|
||||
h.PushRender(constraints.AllScene, wp.NewHandleFn(renderCustomHeader, 20.5, "renderCustomHeader"))
|
||||
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 50.005, "wp.IndexRender"))
|
||||
h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 50.005, "wp.DetailRender"))
|
||||
h.PushRender(constraints.Detail, wp.NewHandleFn(postThumb, 60.005, "postThumb"))
|
||||
h.PushDataHandler(constraints.Detail, wp.NewHandleFn(wp.Detail, 100.005, "wp.Detail"))
|
||||
wp.PushIndexHandler(constraints.PipeData, h, wp.NewHandleFn(wp.Index, 100.005, "wp.Index"))
|
||||
h.PushDataHandler(constraints.AllScene, wp.NewHandleFn(wp.PreCodeAndStats, 80.005, "wp.PreCodeAndStats"))
|
||||
h.PushRender(constraints.AllScene, wp.NewHandleFn(wp.PreTemplate, 70.005, "wp.PreTemplate"))
|
||||
}
|
||||
|
||||
func setPaginationAndRender(h *wp.Handle) {
|
||||
h.PushHandler(constraints.PipeRender, constraints.Detail, wp.NewHandleFn(func(hh *wp.Handle) {
|
||||
d := hh.GetDetailHandle()
|
||||
d.CommentRender = plugins.CommentRender()
|
||||
d.CommentPageEle = plugins.TwentyFifteenCommentPagination()
|
||||
}, 150, "setPaginationAndRender"))
|
||||
|
||||
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(func(hh *wp.Handle) {
|
||||
i := hh.GetIndexHandle()
|
||||
i.SetPageEle(plugins.TwentyFifteenPagination())
|
||||
}, 150, "setPaginationAndRender"))
|
||||
}
|
||||
|
||||
func postThumb(h *wp.Handle) {
|
||||
d := h.GetDetailHandle()
|
||||
if d.Post.Thumbnail.Path != "" {
|
||||
d.Post.Thumbnail = wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "post-thumbnail", "")
|
||||
}
|
||||
}
|
||||
|
||||
func renderCustomHeader(h *wp.Handle) {
|
||||
h.SetData("customHeader", customHeader(h))
|
||||
}
|
@ -1,574 +0,0 @@
|
||||
package twentyseventeen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
)
|
||||
|
||||
func colorScheme(h *wp.Handle) (r string) {
|
||||
if "custom" != wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light") {
|
||||
return
|
||||
}
|
||||
s := str.NewBuilder()
|
||||
hue := number.ToString(wpconfig.GetThemeModsVal[int64](ThemeName, "colorscheme_hue", 250))
|
||||
reducedSaturation := fmt.Sprintf("%d%%", int(.8*50))
|
||||
saturation := fmt.Sprintf("%d%%", 50)
|
||||
css := str.Replace(customCss, map[string]string{
|
||||
"' . $hue . '": hue,
|
||||
"' . esc_attr( $hue ) . '": hue,
|
||||
"' . $saturation . '": saturation,
|
||||
"' . esc_attr( $saturation ) .' ": saturation,
|
||||
"' . $reduced_saturation . '": reducedSaturation,
|
||||
})
|
||||
s.Sprintf(`<style type="text/css" id="custom-theme-colors">%s</style>`, css)
|
||||
r = s.String()
|
||||
return
|
||||
}
|
||||
|
||||
var customCss = `
|
||||
/**
|
||||
* Twenty Seventeen: Color Patterns
|
||||
*
|
||||
* Colors are ordered from dark to light.
|
||||
*/
|
||||
|
||||
.colors-custom a:hover,
|
||||
.colors-custom a:active,
|
||||
.colors-custom .entry-content a:focus,
|
||||
.colors-custom .entry-content a:hover,
|
||||
.colors-custom .entry-summary a:focus,
|
||||
.colors-custom .entry-summary a:hover,
|
||||
.colors-custom .comment-content a:focus,
|
||||
.colors-custom .comment-content a:hover,
|
||||
.colors-custom .widget a:focus,
|
||||
.colors-custom .widget a:hover,
|
||||
.colors-custom .site-footer .widget-area a:focus,
|
||||
.colors-custom .site-footer .widget-area a:hover,
|
||||
.colors-custom .posts-navigation a:focus,
|
||||
.colors-custom .posts-navigation a:hover,
|
||||
.colors-custom .comment-metadata a:focus,
|
||||
.colors-custom .comment-metadata a:hover,
|
||||
.colors-custom .comment-metadata a.comment-edit-link:focus,
|
||||
.colors-custom .comment-metadata a.comment-edit-link:hover,
|
||||
.colors-custom .comment-reply-link:focus,
|
||||
.colors-custom .comment-reply-link:hover,
|
||||
.colors-custom .widget_authors a:focus strong,
|
||||
.colors-custom .widget_authors a:hover strong,
|
||||
.colors-custom .entry-title a:focus,
|
||||
.colors-custom .entry-title a:hover,
|
||||
.colors-custom .entry-meta a:focus,
|
||||
.colors-custom .entry-meta a:hover,
|
||||
.colors-custom.blog .entry-meta a.post-edit-link:focus,
|
||||
.colors-custom.blog .entry-meta a.post-edit-link:hover,
|
||||
.colors-custom.archive .entry-meta a.post-edit-link:focus,
|
||||
.colors-custom.archive .entry-meta a.post-edit-link:hover,
|
||||
.colors-custom.search .entry-meta a.post-edit-link:focus,
|
||||
.colors-custom.search .entry-meta a.post-edit-link:hover,
|
||||
.colors-custom .page-links a:focus .page-number,
|
||||
.colors-custom .page-links a:hover .page-number,
|
||||
.colors-custom .entry-footer a:focus,
|
||||
.colors-custom .entry-footer a:hover,
|
||||
.colors-custom .entry-footer .cat-links a:focus,
|
||||
.colors-custom .entry-footer .cat-links a:hover,
|
||||
.colors-custom .entry-footer .tags-links a:focus,
|
||||
.colors-custom .entry-footer .tags-links a:hover,
|
||||
.colors-custom .post-navigation a:focus,
|
||||
.colors-custom .post-navigation a:hover,
|
||||
.colors-custom .pagination a:not(.prev):not(.next):focus,
|
||||
.colors-custom .pagination a:not(.prev):not(.next):hover,
|
||||
.colors-custom .comments-pagination a:not(.prev):not(.next):focus,
|
||||
.colors-custom .comments-pagination a:not(.prev):not(.next):hover,
|
||||
.colors-custom .logged-in-as a:focus,
|
||||
.colors-custom .logged-in-as a:hover,
|
||||
.colors-custom a:focus .nav-title,
|
||||
.colors-custom a:hover .nav-title,
|
||||
.colors-custom .edit-link a:focus,
|
||||
.colors-custom .edit-link a:hover,
|
||||
.colors-custom .site-info a:focus,
|
||||
.colors-custom .site-info a:hover,
|
||||
.colors-custom .widget .widget-title a:focus,
|
||||
.colors-custom .widget .widget-title a:hover,
|
||||
.colors-custom .widget ul li a:focus,
|
||||
.colors-custom .widget ul li a:hover {
|
||||
color: hsl( ' . $hue . ', ' . $saturation . ', 0% ); /* base: #000; */
|
||||
}
|
||||
|
||||
.colors-custom .entry-content a,
|
||||
.colors-custom .entry-summary a,
|
||||
.colors-custom .comment-content a,
|
||||
.colors-custom .widget a,
|
||||
.colors-custom .site-footer .widget-area a,
|
||||
.colors-custom .posts-navigation a,
|
||||
.colors-custom .widget_authors a strong {
|
||||
-webkit-box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 6% ); /* base: rgba(15, 15, 15, 1); */
|
||||
box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 6% ); /* base: rgba(15, 15, 15, 1); */
|
||||
}
|
||||
|
||||
.colors-custom button,
|
||||
.colors-custom input[type="button"],
|
||||
.colors-custom input[type="submit"],
|
||||
.colors-custom .entry-footer .edit-link a.post-edit-link {
|
||||
background-color: hsl( ' . $hue . ', ' . $saturation . ', 13% ); /* base: #222; */
|
||||
}
|
||||
|
||||
.colors-custom input[type="text"]:focus,
|
||||
.colors-custom input[type="email"]:focus,
|
||||
.colors-custom input[type="url"]:focus,
|
||||
.colors-custom input[type="password"]:focus,
|
||||
.colors-custom input[type="search"]:focus,
|
||||
.colors-custom input[type="number"]:focus,
|
||||
.colors-custom input[type="tel"]:focus,
|
||||
.colors-custom input[type="range"]:focus,
|
||||
.colors-custom input[type="date"]:focus,
|
||||
.colors-custom input[type="month"]:focus,
|
||||
.colors-custom input[type="week"]:focus,
|
||||
.colors-custom input[type="time"]:focus,
|
||||
.colors-custom input[type="datetime"]:focus,
|
||||
.colors-custom .colors-custom input[type="datetime-local"]:focus,
|
||||
.colors-custom input[type="color"]:focus,
|
||||
.colors-custom textarea:focus,
|
||||
.colors-custom button.secondary,
|
||||
.colors-custom input[type="reset"],
|
||||
.colors-custom input[type="button"].secondary,
|
||||
.colors-custom input[type="reset"].secondary,
|
||||
.colors-custom input[type="submit"].secondary,
|
||||
.colors-custom a,
|
||||
.colors-custom .site-title,
|
||||
.colors-custom .site-title a,
|
||||
.colors-custom .navigation-top a,
|
||||
.colors-custom .dropdown-toggle,
|
||||
.colors-custom .menu-toggle,
|
||||
.colors-custom .page .panel-content .entry-title,
|
||||
.colors-custom .page-title,
|
||||
.colors-custom.page:not(.twentyseventeen-front-page) .entry-title,
|
||||
.colors-custom .page-links a .page-number,
|
||||
.colors-custom .comment-metadata a.comment-edit-link,
|
||||
.colors-custom .comment-reply-link .icon,
|
||||
.colors-custom h2.widget-title,
|
||||
.colors-custom mark,
|
||||
.colors-custom .post-navigation a:focus .icon,
|
||||
.colors-custom .post-navigation a:hover .icon,
|
||||
.colors-custom .site-content .site-content-light,
|
||||
.colors-custom .twentyseventeen-panel .recent-posts .entry-header .edit-link {
|
||||
color: hsl( ' . $hue . ', ' . $saturation . ', 13% ); /* base: #222; */
|
||||
}
|
||||
|
||||
.colors-custom .entry-content a:focus,
|
||||
.colors-custom .entry-content a:hover,
|
||||
.colors-custom .entry-summary a:focus,
|
||||
.colors-custom .entry-summary a:hover,
|
||||
.colors-custom .comment-content a:focus,
|
||||
.colors-custom .comment-content a:hover,
|
||||
.colors-custom .widget a:focus,
|
||||
.colors-custom .widget a:hover,
|
||||
.colors-custom .site-footer .widget-area a:focus,
|
||||
.colors-custom .site-footer .widget-area a:hover,
|
||||
.colors-custom .posts-navigation a:focus,
|
||||
.colors-custom .posts-navigation a:hover,
|
||||
.colors-custom .comment-metadata a:focus,
|
||||
.colors-custom .comment-metadata a:hover,
|
||||
.colors-custom .comment-metadata a.comment-edit-link:focus,
|
||||
.colors-custom .comment-metadata a.comment-edit-link:hover,
|
||||
.colors-custom .comment-reply-link:focus,
|
||||
.colors-custom .comment-reply-link:hover,
|
||||
.colors-custom .widget_authors a:focus strong,
|
||||
.colors-custom .widget_authors a:hover strong,
|
||||
.colors-custom .entry-title a:focus,
|
||||
.colors-custom .entry-title a:hover,
|
||||
.colors-custom .entry-meta a:focus,
|
||||
.colors-custom .entry-meta a:hover,
|
||||
.colors-custom.blog .entry-meta a.post-edit-link:focus,
|
||||
.colors-custom.blog .entry-meta a.post-edit-link:hover,
|
||||
.colors-custom.archive .entry-meta a.post-edit-link:focus,
|
||||
.colors-custom.archive .entry-meta a.post-edit-link:hover,
|
||||
.colors-custom.search .entry-meta a.post-edit-link:focus,
|
||||
.colors-custom.search .entry-meta a.post-edit-link:hover,
|
||||
.colors-custom .page-links a:focus .page-number,
|
||||
.colors-custom .page-links a:hover .page-number,
|
||||
.colors-custom .entry-footer .cat-links a:focus,
|
||||
.colors-custom .entry-footer .cat-links a:hover,
|
||||
.colors-custom .entry-footer .tags-links a:focus,
|
||||
.colors-custom .entry-footer .tags-links a:hover,
|
||||
.colors-custom .post-navigation a:focus,
|
||||
.colors-custom .post-navigation a:hover,
|
||||
.colors-custom .pagination a:not(.prev):not(.next):focus,
|
||||
.colors-custom .pagination a:not(.prev):not(.next):hover,
|
||||
.colors-custom .comments-pagination a:not(.prev):not(.next):focus,
|
||||
.colors-custom .comments-pagination a:not(.prev):not(.next):hover,
|
||||
.colors-custom .logged-in-as a:focus,
|
||||
.colors-custom .logged-in-as a:hover,
|
||||
.colors-custom a:focus .nav-title,
|
||||
.colors-custom a:hover .nav-title,
|
||||
.colors-custom .edit-link a:focus,
|
||||
.colors-custom .edit-link a:hover,
|
||||
.colors-custom .site-info a:focus,
|
||||
.colors-custom .site-info a:hover,
|
||||
.colors-custom .widget .widget-title a:focus,
|
||||
.colors-custom .widget .widget-title a:hover,
|
||||
.colors-custom .widget ul li a:focus,
|
||||
.colors-custom .widget ul li a:hover {
|
||||
-webkit-box-shadow: inset 0 0 0 hsl( ' . $hue . ', ' . $saturation . ', 13% ), 0 3px 0 hsl( ' . $hue . ', ' . $saturation . ', 13% );
|
||||
box-shadow: inset 0 0 0 hsl( ' . $hue . ', ' . $saturation . ' , 13% ), 0 3px 0 hsl( ' . $hue . ', ' . $saturation . ', 13% );
|
||||
}
|
||||
|
||||
body.colors-custom,
|
||||
.colors-custom button,
|
||||
.colors-custom input,
|
||||
.colors-custom select,
|
||||
.colors-custom textarea,
|
||||
.colors-custom h3,
|
||||
.colors-custom h4,
|
||||
.colors-custom h6,
|
||||
.colors-custom label,
|
||||
.colors-custom .entry-title a,
|
||||
.colors-custom.twentyseventeen-front-page .panel-content .recent-posts article,
|
||||
.colors-custom .entry-footer .cat-links a,
|
||||
.colors-custom .entry-footer .tags-links a,
|
||||
.colors-custom .format-quote blockquote,
|
||||
.colors-custom .nav-title,
|
||||
.colors-custom .comment-body,
|
||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-current-item .wp-playlist-item-album {
|
||||
color: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
|
||||
}
|
||||
|
||||
.colors-custom .social-navigation a:hover,
|
||||
.colors-custom .social-navigation a:focus {
|
||||
background: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
|
||||
}
|
||||
|
||||
.colors-custom input[type="text"]:focus,
|
||||
.colors-custom input[type="email"]:focus,
|
||||
.colors-custom input[type="url"]:focus,
|
||||
.colors-custom input[type="password"]:focus,
|
||||
.colors-custom input[type="search"]:focus,
|
||||
.colors-custom input[type="number"]:focus,
|
||||
.colors-custom input[type="tel"]:focus,
|
||||
.colors-custom input[type="range"]:focus,
|
||||
.colors-custom input[type="date"]:focus,
|
||||
.colors-custom input[type="month"]:focus,
|
||||
.colors-custom input[type="week"]:focus,
|
||||
.colors-custom input[type="time"]:focus,
|
||||
.colors-custom input[type="datetime"]:focus,
|
||||
.colors-custom input[type="datetime-local"]:focus,
|
||||
.colors-custom input[type="color"]:focus,
|
||||
.colors-custom textarea:focus,
|
||||
.bypostauthor > .comment-body > .comment-meta > .comment-author .avatar {
|
||||
border-color: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
|
||||
}
|
||||
|
||||
.colors-custom h2,
|
||||
.colors-custom blockquote,
|
||||
.colors-custom input[type="text"],
|
||||
.colors-custom input[type="email"],
|
||||
.colors-custom input[type="url"],
|
||||
.colors-custom input[type="password"],
|
||||
.colors-custom input[type="search"],
|
||||
.colors-custom input[type="number"],
|
||||
.colors-custom input[type="tel"],
|
||||
.colors-custom input[type="range"],
|
||||
.colors-custom input[type="date"],
|
||||
.colors-custom input[type="month"],
|
||||
.colors-custom input[type="week"],
|
||||
.colors-custom input[type="time"],
|
||||
.colors-custom input[type="datetime"],
|
||||
.colors-custom input[type="datetime-local"],
|
||||
.colors-custom input[type="color"],
|
||||
.colors-custom textarea,
|
||||
.colors-custom .site-description,
|
||||
.colors-custom .entry-content blockquote.alignleft,
|
||||
.colors-custom .entry-content blockquote.alignright,
|
||||
.colors-custom .colors-custom .taxonomy-description,
|
||||
.colors-custom .site-info a,
|
||||
.colors-custom .wp-caption,
|
||||
.colors-custom .gallery-caption {
|
||||
color: hsl( ' . $hue . ', ' . $saturation . ', 40% ); /* base: #666; */
|
||||
}
|
||||
|
||||
.colors-custom abbr,
|
||||
.colors-custom acronym {
|
||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 40% ); /* base: #666; */
|
||||
}
|
||||
|
||||
.colors-custom h5,
|
||||
.colors-custom .entry-meta,
|
||||
.colors-custom .entry-meta a,
|
||||
.colors-custom.blog .entry-meta a.post-edit-link,
|
||||
.colors-custom.archive .entry-meta a.post-edit-link,
|
||||
.colors-custom.search .entry-meta a.post-edit-link,
|
||||
.colors-custom .nav-subtitle,
|
||||
.colors-custom .comment-metadata,
|
||||
.colors-custom .comment-metadata a,
|
||||
.colors-custom .no-comments,
|
||||
.colors-custom .comment-awaiting-moderation,
|
||||
.colors-custom .page-numbers.current,
|
||||
.colors-custom .page-links .page-number,
|
||||
.colors-custom .navigation-top .current-menu-item > a,
|
||||
.colors-custom .navigation-top .current_page_item > a,
|
||||
.colors-custom .main-navigation a:hover,
|
||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-current-item .wp-playlist-item-artist {
|
||||
color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */
|
||||
}
|
||||
|
||||
.colors-custom :not( .mejs-button ) > button:hover,
|
||||
.colors-custom :not( .mejs-button ) > button:focus,
|
||||
.colors-custom input[type="button"]:hover,
|
||||
.colors-custom input[type="button"]:focus,
|
||||
.colors-custom input[type="submit"]:hover,
|
||||
.colors-custom input[type="submit"]:focus,
|
||||
.colors-custom .entry-footer .edit-link a.post-edit-link:hover,
|
||||
.colors-custom .entry-footer .edit-link a.post-edit-link:focus,
|
||||
.colors-custom .social-navigation a,
|
||||
.colors-custom .prev.page-numbers:focus,
|
||||
.colors-custom .prev.page-numbers:hover,
|
||||
.colors-custom .next.page-numbers:focus,
|
||||
.colors-custom .next.page-numbers:hover,
|
||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover,
|
||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus {
|
||||
background: hsl( ' . esc_attr( $hue ) . ', ' . esc_attr( $saturation ) . ', 46% ); /* base: #767676; */
|
||||
}
|
||||
|
||||
.colors-custom button.secondary:hover,
|
||||
.colors-custom button.secondary:focus,
|
||||
.colors-custom input[type="reset"]:hover,
|
||||
.colors-custom input[type="reset"]:focus,
|
||||
.colors-custom input[type="button"].secondary:hover,
|
||||
.colors-custom input[type="button"].secondary:focus,
|
||||
.colors-custom input[type="reset"].secondary:hover,
|
||||
.colors-custom input[type="reset"].secondary:focus,
|
||||
.colors-custom input[type="submit"].secondary:hover,
|
||||
.colors-custom input[type="submit"].secondary:focus,
|
||||
.colors-custom hr {
|
||||
background: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
||||
}
|
||||
|
||||
.colors-custom input[type="text"],
|
||||
.colors-custom input[type="email"],
|
||||
.colors-custom input[type="url"],
|
||||
.colors-custom input[type="password"],
|
||||
.colors-custom input[type="search"],
|
||||
.colors-custom input[type="number"],
|
||||
.colors-custom input[type="tel"],
|
||||
.colors-custom input[type="range"],
|
||||
.colors-custom input[type="date"],
|
||||
.colors-custom input[type="month"],
|
||||
.colors-custom input[type="week"],
|
||||
.colors-custom input[type="time"],
|
||||
.colors-custom input[type="datetime"],
|
||||
.colors-custom input[type="datetime-local"],
|
||||
.colors-custom input[type="color"],
|
||||
.colors-custom textarea,
|
||||
.colors-custom select,
|
||||
.colors-custom fieldset,
|
||||
.colors-custom .widget .tagcloud a:hover,
|
||||
.colors-custom .widget .tagcloud a:focus,
|
||||
.colors-custom .widget.widget_tag_cloud a:hover,
|
||||
.colors-custom .widget.widget_tag_cloud a:focus,
|
||||
.colors-custom .wp_widget_tag_cloud a:hover,
|
||||
.colors-custom .wp_widget_tag_cloud a:focus {
|
||||
border-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
||||
}
|
||||
|
||||
.colors-custom thead th {
|
||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
||||
}
|
||||
|
||||
.colors-custom .entry-footer .cat-links .icon,
|
||||
.colors-custom .entry-footer .tags-links .icon {
|
||||
color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
||||
}
|
||||
|
||||
.colors-custom button.secondary,
|
||||
.colors-custom input[type="reset"],
|
||||
.colors-custom input[type="button"].secondary,
|
||||
.colors-custom input[type="reset"].secondary,
|
||||
.colors-custom input[type="submit"].secondary,
|
||||
.colors-custom .prev.page-numbers,
|
||||
.colors-custom .next.page-numbers {
|
||||
background-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
|
||||
}
|
||||
|
||||
.colors-custom .widget .tagcloud a,
|
||||
.colors-custom .widget.widget_tag_cloud a,
|
||||
.colors-custom .wp_widget_tag_cloud a {
|
||||
border-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
|
||||
}
|
||||
|
||||
.colors-custom.twentyseventeen-front-page article:not(.has-post-thumbnail):not(:first-child),
|
||||
.colors-custom .widget ul li {
|
||||
border-top-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
|
||||
}
|
||||
|
||||
.colors-custom .widget ul li {
|
||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
|
||||
}
|
||||
|
||||
.colors-custom pre,
|
||||
.colors-custom mark,
|
||||
.colors-custom ins {
|
||||
background: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
|
||||
}
|
||||
|
||||
.colors-custom .navigation-top,
|
||||
.colors-custom .main-navigation > div > ul,
|
||||
.colors-custom .pagination,
|
||||
.colors-custom .comments-pagination,
|
||||
.colors-custom .entry-footer,
|
||||
.colors-custom .site-footer {
|
||||
border-top-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
|
||||
}
|
||||
|
||||
.colors-custom .navigation-top,
|
||||
.colors-custom .main-navigation li,
|
||||
.colors-custom .entry-footer,
|
||||
.colors-custom .single-featured-image-header,
|
||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item,
|
||||
.colors-custom tr {
|
||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
|
||||
}
|
||||
|
||||
.colors-custom .site-content .wp-playlist-light {
|
||||
border-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
|
||||
}
|
||||
|
||||
.colors-custom .site-header,
|
||||
.colors-custom .single-featured-image-header {
|
||||
background-color: hsl( ' . $hue . ', ' . $saturation . ', 98% ); /* base: #fafafa; */
|
||||
}
|
||||
|
||||
.colors-custom button,
|
||||
.colors-custom input[type="button"],
|
||||
.colors-custom input[type="submit"],
|
||||
.colors-custom .entry-footer .edit-link a.post-edit-link,
|
||||
.colors-custom .social-navigation a,
|
||||
.colors-custom .site-content .wp-playlist-light a.wp-playlist-caption:hover,
|
||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover a,
|
||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus a,
|
||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover,
|
||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus,
|
||||
.colors-custom .prev.page-numbers:focus,
|
||||
.colors-custom .prev.page-numbers:hover,
|
||||
.colors-custom .next.page-numbers:focus,
|
||||
.colors-custom .next.page-numbers:hover,
|
||||
.colors-custom.has-header-image .site-title,
|
||||
.colors-custom.has-header-video .site-title,
|
||||
.colors-custom.has-header-image .site-title a,
|
||||
.colors-custom.has-header-video .site-title a,
|
||||
.colors-custom.has-header-image .site-description,
|
||||
.colors-custom.has-header-video .site-description {
|
||||
color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
|
||||
}
|
||||
|
||||
body.colors-custom,
|
||||
.colors-custom .navigation-top,
|
||||
.colors-custom .main-navigation ul {
|
||||
background: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
|
||||
}
|
||||
|
||||
.colors-custom .widget ul li a,
|
||||
.colors-custom .site-footer .widget-area ul li a {
|
||||
-webkit-box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: rgba(255, 255, 255, 1); */
|
||||
box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: rgba(255, 255, 255, 1); */
|
||||
}
|
||||
|
||||
.colors-custom .menu-toggle,
|
||||
.colors-custom .menu-toggle:hover,
|
||||
.colors-custom .menu-toggle:focus,
|
||||
.colors-custom .menu .dropdown-toggle,
|
||||
.colors-custom .menu-scroll-down,
|
||||
.colors-custom .menu-scroll-down:hover,
|
||||
.colors-custom .menu-scroll-down:focus {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.colors-custom .widget .tagcloud a,
|
||||
.colors-custom .widget .tagcloud a:focus,
|
||||
.colors-custom .widget .tagcloud a:hover,
|
||||
.colors-custom .widget.widget_tag_cloud a,
|
||||
.colors-custom .widget.widget_tag_cloud a:focus,
|
||||
.colors-custom .widget.widget_tag_cloud a:hover,
|
||||
.colors-custom .wp_widget_tag_cloud a,
|
||||
.colors-custom .wp_widget_tag_cloud a:focus,
|
||||
.colors-custom .wp_widget_tag_cloud a:hover,
|
||||
.colors-custom .entry-footer .edit-link a.post-edit-link:focus,
|
||||
.colors-custom .entry-footer .edit-link a.post-edit-link:hover {
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Reset non-customizable hover styling for links */
|
||||
.colors-custom .entry-content a:hover,
|
||||
.colors-custom .entry-content a:focus,
|
||||
.colors-custom .entry-summary a:hover,
|
||||
.colors-custom .entry-summary a:focus,
|
||||
.colors-custom .comment-content a:focus,
|
||||
.colors-custom .comment-content a:hover,
|
||||
.colors-custom .widget a:hover,
|
||||
.colors-custom .widget a:focus,
|
||||
.colors-custom .site-footer .widget-area a:hover,
|
||||
.colors-custom .site-footer .widget-area a:focus,
|
||||
.colors-custom .posts-navigation a:hover,
|
||||
.colors-custom .posts-navigation a:focus,
|
||||
.colors-custom .widget_authors a:hover strong,
|
||||
.colors-custom .widget_authors a:focus strong {
|
||||
-webkit-box-shadow: inset 0 0 0 rgba(0, 0, 0, 0), 0 3px 0 rgba(0, 0, 0, 1);
|
||||
box-shadow: inset 0 0 0 rgba(0, 0, 0, 0), 0 3px 0 rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.colors-custom .gallery-item a,
|
||||
.colors-custom .gallery-item a:hover,
|
||||
.colors-custom .gallery-item a:focus {
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 48em) {
|
||||
|
||||
.colors-custom .nav-links .nav-previous .nav-title .icon,
|
||||
.colors-custom .nav-links .nav-next .nav-title .icon {
|
||||
color: hsl( ' . $hue . ', ' . $saturation . ', 20% ); /* base: #222; */
|
||||
}
|
||||
|
||||
.colors-custom .main-navigation li li:hover,
|
||||
.colors-custom .main-navigation li li.focus {
|
||||
background: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */
|
||||
}
|
||||
|
||||
.colors-custom .navigation-top .menu-scroll-down {
|
||||
color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */;
|
||||
}
|
||||
|
||||
.colors-custom abbr[title] {
|
||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */;
|
||||
}
|
||||
|
||||
.colors-custom .main-navigation ul ul {
|
||||
border-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
||||
background: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
|
||||
}
|
||||
|
||||
.colors-custom .main-navigation ul li.menu-item-has-children:before,
|
||||
.colors-custom .main-navigation ul li.page_item_has_children:before {
|
||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
||||
}
|
||||
|
||||
.colors-custom .main-navigation ul li.menu-item-has-children:after,
|
||||
.colors-custom .main-navigation ul li.page_item_has_children:after {
|
||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
|
||||
}
|
||||
|
||||
.colors-custom .main-navigation li li.focus > a,
|
||||
.colors-custom .main-navigation li li:focus > a,
|
||||
.colors-custom .main-navigation li li:hover > a,
|
||||
.colors-custom .main-navigation li li a:hover,
|
||||
.colors-custom .main-navigation li li a:focus,
|
||||
.colors-custom .main-navigation li li.current_page_item a:hover,
|
||||
.colors-custom .main-navigation li li.current-menu-item a:hover,
|
||||
.colors-custom .main-navigation li li.current_page_item a:focus,
|
||||
.colors-custom .main-navigation li li.current-menu-item a:focus {
|
||||
color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
|
||||
}
|
||||
}
|
||||
`
|
@ -1,48 +0,0 @@
|
||||
package twentyseventeen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
)
|
||||
|
||||
func customHeader(h *wp.Handle) (r string) {
|
||||
themeMods := h.CommonThemeMods()
|
||||
headerTextColor := themeMods.HeaderTextcolor
|
||||
if headerTextColor == "" || headerTextColor == themeMods.ThemeSupport.CustomHeader.DefaultTextColor {
|
||||
return
|
||||
}
|
||||
css := `
|
||||
.site-title,
|
||||
.site-description {
|
||||
position: absolute;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
}`
|
||||
if headerTextColor != "blank" {
|
||||
css = fmt.Sprintf(customHeaderCss, headerTextColor)
|
||||
}
|
||||
r = fmt.Sprintf(`<style id="twentyseventeen-custom-header-styles" type="text/css">%s</style>`, css)
|
||||
return
|
||||
}
|
||||
|
||||
var customHeaderCss = `
|
||||
.site-title a,
|
||||
.colors-dark .site-title a,
|
||||
.colors-custom .site-title a,
|
||||
body.has-header-image .site-title a,
|
||||
body.has-header-video .site-title a,
|
||||
body.has-header-image.colors-dark .site-title a,
|
||||
body.has-header-video.colors-dark .site-title a,
|
||||
body.has-header-image.colors-custom .site-title a,
|
||||
body.has-header-video.colors-custom .site-title a,
|
||||
.site-description,
|
||||
.colors-dark .site-description,
|
||||
.colors-custom .site-description,
|
||||
body.has-header-image .site-description,
|
||||
body.has-header-video .site-description,
|
||||
body.has-header-image.colors-dark .site-description,
|
||||
body.has-header-video.colors-dark .site-description,
|
||||
body.has-header-image.colors-custom .site-description,
|
||||
body.has-header-video.colors-custom .site-description {
|
||||
color: #%s;
|
||||
}
|
||||
`
|
@ -1,5 +0,0 @@
|
||||
{{define "layout/footer"}}
|
||||
{{template "common/footer" .}}
|
||||
{{ block "footer" .}}
|
||||
{{end}}
|
||||
{{end}}
|
@ -1,3 +0,0 @@
|
||||
{{define "layout/sidebar" }}
|
||||
{{template "common/sidebarWidget" .}}
|
||||
{{end}}
|
@ -1,16 +0,0 @@
|
||||
{{template "layout/base" .}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="site-content-contain">
|
||||
<div id="content" class="site-content">
|
||||
<div class="wrap">
|
||||
<div id="primary" class="content-area">
|
||||
<main id="main" class="site-main">
|
||||
{{template "layout/empty"}}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
@ -1,43 +0,0 @@
|
||||
package twentyseventeen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
)
|
||||
|
||||
func pushScripts(h *wp.Handle) {
|
||||
h.PushCacheGroupHeadScript(constraints.AllScene, "{theme}.head", 30, func(h *wp.Handle) string {
|
||||
head := headScript
|
||||
if "dark" == wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light") {
|
||||
head = fmt.Sprintf("%s\n%s", headScript, ` <link rel="stylesheet" id="twentyseventeen-colors-dark-css" href="/wp-content/themes/twentyseventeen/assets/css/colors-dark.css?ver=20191025" media="all">`)
|
||||
}
|
||||
return head
|
||||
})
|
||||
h.PushGroupFooterScript(constraints.AllScene, "{theme}.footer", 20.005, footerScript)
|
||||
|
||||
}
|
||||
|
||||
var headScript = `<link rel='stylesheet' id='twentyseventeen-style-css' href='/wp-content/themes/twentyseventeen/style.css?ver=20221101' media='all' />
|
||||
<link rel='stylesheet' id='twentyseventeen-block-style-css' href='/wp-content/themes/twentyseventeen/assets/css/blocks.css?ver=20220912' media='all' />
|
||||
<!--[if lt IE 9]>
|
||||
<link rel='stylesheet' id='twentyseventeen-ie8-css' href='/wp-content/themes/twentyseventeen/assets/css/ie8.css?ver=20161202' media='all' />
|
||||
<![endif]-->
|
||||
<!--[if lt IE 9]>
|
||||
<script src='/wp-content/themes/twentyseventeen/assets/js/html5.js?ver=20161020' id='html5-js'></script>
|
||||
<![endif]-->
|
||||
<script src='/wp-includes/js/jquery/jquery.min.js?ver=3.6.0' id='jquery-core-js'></script>
|
||||
<script src='/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.3.2' id='jquery-migrate-js'></script>
|
||||
<link rel="https://api.w.org/" href="/wp-json/" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="/xmlrpc.php?rsd" />
|
||||
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="/wp-includes/wlwmanifest.xml" />
|
||||
<meta name="generator" content="WordPress 6.1.1" />
|
||||
<style>.recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}</style>`
|
||||
|
||||
var footerScript = `<script id="twentyseventeen-skip-link-focus-fix-js-extra">
|
||||
var twentyseventeenScreenReaderText = {"quote":"<svg class=\"icon icon-quote-right\" aria-hidden=\"true\" role=\"img\"> <use href=\"#icon-quote-right\" xlink:href=\"#icon-quote-right\"><\/use> <\/svg>"};
|
||||
</script>
|
||||
|
||||
<script src="/wp-content/themes/twentyseventeen/assets/js/skip-link-focus-fix.js?ver=20161114" id="twentyseventeen-skip-link-focus-fix-js"></script>
|
||||
<script src="/wp-content/themes/twentyseventeen/assets/js/global.js?ver=20211130" id="twentyseventeen-global-js"></script>
|
||||
<script src="/wp-content/themes/twentyseventeen/assets/js/jquery.scrollTo.js?ver=2.1.3" id="jquery-scrollto-js"></script>`
|
@ -1,212 +0,0 @@
|
||||
package twentyseventeen
|
||||
|
||||
import "github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
|
||||
type themeSupport struct {
|
||||
CustomLineHeight bool `json:"custom-line-height"`
|
||||
StarterContent StarterContent `json:"starter-content"`
|
||||
}
|
||||
|
||||
type Widgets struct {
|
||||
Sidebar1 []string `json:"sidebar-1"`
|
||||
Sidebar2 []string `json:"sidebar-2"`
|
||||
Sidebar3 []string `json:"sidebar-3"`
|
||||
}
|
||||
type About struct {
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
}
|
||||
type Contact struct {
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
}
|
||||
type Blog struct {
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
}
|
||||
type HomepageSection struct {
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
}
|
||||
|
||||
type ImageEspresso struct {
|
||||
PostTitle string `json:"post_title"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
type ImageSandwich struct {
|
||||
PostTitle string `json:"post_title"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
type ImageCoffee struct {
|
||||
PostTitle string `json:"post_title"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
type Attachments struct {
|
||||
ImageEspresso ImageEspresso `json:"image-espresso"`
|
||||
ImageSandwich ImageSandwich `json:"image-sandwich"`
|
||||
ImageCoffee ImageCoffee `json:"image-coffee"`
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
PostTitle string `json:"post_title"`
|
||||
File string `json:"file"`
|
||||
}
|
||||
type Options struct {
|
||||
ShowOnFront string `json:"show_on_front"`
|
||||
PageOnFront string `json:"page_on_front"`
|
||||
PageForPosts string `json:"page_for_posts"`
|
||||
}
|
||||
type ThemeMods struct {
|
||||
Panel1 string `json:"panel_1"`
|
||||
Panel2 string `json:"panel_2"`
|
||||
Panel3 string `json:"panel_3"`
|
||||
Panel4 string `json:"panel_4"`
|
||||
}
|
||||
type Menus struct {
|
||||
Name string `json:"name"`
|
||||
Items []string `json:"items"`
|
||||
}
|
||||
|
||||
type NavMenus struct {
|
||||
Top Menus `json:"top"`
|
||||
Social Menus `json:"social"`
|
||||
}
|
||||
type StarterContent struct {
|
||||
Widgets Widgets `json:"widgets"`
|
||||
Posts map[string]map[string]string `json:"posts"`
|
||||
Attachments map[string]Image `json:"attachments"`
|
||||
Options Options `json:"options"`
|
||||
ThemeMods ThemeMods `json:"theme_mods"`
|
||||
NavMenus NavMenus `json:"nav_menus"`
|
||||
}
|
||||
|
||||
var themesupport = themeSupport{
|
||||
CustomLineHeight: true,
|
||||
StarterContent: StarterContent{
|
||||
Widgets: Widgets{
|
||||
Sidebar1: []string{"text_business_info", "search", "text_about"},
|
||||
Sidebar2: []string{"text_business_info"},
|
||||
Sidebar3: []string{"text_about", "search"},
|
||||
},
|
||||
Posts: map[string]map[string]string{
|
||||
"0": {
|
||||
"home": "home",
|
||||
},
|
||||
"about": {
|
||||
"thumbnail": "{{image-sandwich}}",
|
||||
},
|
||||
"contact": {
|
||||
"thumbnail": "{{image-espresso}}",
|
||||
},
|
||||
"blog": {
|
||||
"thumbnail": "{{image-coffee}}",
|
||||
},
|
||||
"homepage-section": {
|
||||
"thumbnail": "{{image-espresso}}",
|
||||
},
|
||||
},
|
||||
Attachments: map[string]Image{
|
||||
"image-espresso": {
|
||||
PostTitle: "浓缩咖啡",
|
||||
File: "assets/images/espresso.jpg",
|
||||
},
|
||||
"image-sandwich": {
|
||||
PostTitle: "三明治",
|
||||
File: "assets/images/sandwich.jpg",
|
||||
},
|
||||
"image-coffee": {
|
||||
PostTitle: "咖啡",
|
||||
File: "assets/images/coffee.jpg",
|
||||
},
|
||||
},
|
||||
Options: Options{
|
||||
ShowOnFront: "page",
|
||||
PageOnFront: "{{home}}",
|
||||
PageForPosts: "{{blog}}",
|
||||
},
|
||||
ThemeMods: ThemeMods{
|
||||
Panel1: "{{homepage-section}}",
|
||||
Panel2: "{{about}}",
|
||||
Panel3: "{{blog}}",
|
||||
Panel4: "{{contact}}",
|
||||
},
|
||||
NavMenus: NavMenus{
|
||||
Top: Menus{
|
||||
Name: "顶部菜单",
|
||||
Items: []string{
|
||||
"link_home",
|
||||
"page_about",
|
||||
"page_blog",
|
||||
"page_contact",
|
||||
},
|
||||
},
|
||||
Social: Menus{
|
||||
Name: "社交网络链接菜单",
|
||||
Items: []string{
|
||||
"link_yelp",
|
||||
"link_facebook",
|
||||
"link_twitter",
|
||||
"link_instagram",
|
||||
"link_email",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var _ = func() struct{} {
|
||||
v := wpconfig.ThemeSupport{
|
||||
CoreBlockPatterns: true,
|
||||
WidgetsBlockEditor: true,
|
||||
AutomaticFeedLinks: true,
|
||||
TitleTag: true,
|
||||
PostThumbnails: true,
|
||||
Menus: true,
|
||||
HTML5: []string{
|
||||
"comment-form",
|
||||
"comment-list",
|
||||
"gallery",
|
||||
"caption",
|
||||
"script",
|
||||
"style",
|
||||
"navigation-widgets",
|
||||
},
|
||||
PostFormats: []string{
|
||||
"aside",
|
||||
"image",
|
||||
"video",
|
||||
"quote",
|
||||
"link",
|
||||
"gallery",
|
||||
"audio",
|
||||
},
|
||||
CustomLogo: wpconfig.CustomLogo{
|
||||
Width: 250,
|
||||
Height: 250,
|
||||
FlexWidth: true,
|
||||
FlexHeight: false,
|
||||
HeaderText: "",
|
||||
UnlinkHomepageLogo: false,
|
||||
},
|
||||
CustomizeSelectiveRefreshWidgets: true,
|
||||
EditorStyle: true,
|
||||
EditorStyles: true,
|
||||
WpBlockStyles: true,
|
||||
ResponsiveEmbeds: true,
|
||||
CustomHeader: wpconfig.CustomHeader{
|
||||
DefaultImage: "http://wp.test/wp-content/themes/twentyseventeen/assets/images/header.jpg",
|
||||
RandomDefault: false,
|
||||
Width: 2000,
|
||||
Height: 1200,
|
||||
FlexHeight: true,
|
||||
FlexWidth: false,
|
||||
DefaultTextColor: "",
|
||||
HeaderText: true,
|
||||
Uploads: true,
|
||||
WpHeadCallback: "twentyseventeen_header_style",
|
||||
AdminHeadCallback: "",
|
||||
AdminPreviewCallback: "",
|
||||
Video: true,
|
||||
VideoActiveCallback: "is_front_page",
|
||||
},
|
||||
Widgets: true,
|
||||
}
|
||||
wpconfig.SetThemeSupport(ThemeName, v)
|
||||
return struct{}{}
|
||||
}()
|
@ -1,229 +0,0 @@
|
||||
package twentyseventeen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/plugins"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/components"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/middleware"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/plugin/pagination"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ThemeName = "twentyseventeen"
|
||||
|
||||
var paginate = func() plugins.PageEle {
|
||||
p := plugins.TwentyFifteenPagination()
|
||||
p.PrevEle = `<a class="prev page-numbers" href="%s"><svg class="icon icon-arrow-left" aria-hidden="true" role="img"> <use href="#icon-arrow-left" xlink:href="#icon-arrow-left"></use> </svg>
|
||||
<span class="screen-reader-text">上一页</span></a>`
|
||||
p.NextEle = strings.Replace(p.NextEle, "下一页", `<span class="screen-reader-text">下一页</span>
|
||||
<svg class="icon icon-arrow-right" aria-hidden="true" role="img"> <use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use>
|
||||
</svg>`, 1)
|
||||
commentPageEle = plugins.PaginationNav{
|
||||
Currents: p.Current,
|
||||
Prevs: p.Prev,
|
||||
Nexts: p.Next,
|
||||
Dotss: p.Dots,
|
||||
Middles: p.Middle,
|
||||
Urlss: plugins.TwentyFifteenCommentPagination().Urls,
|
||||
}
|
||||
|
||||
return p
|
||||
}()
|
||||
|
||||
var commentPageEle pagination.Render
|
||||
|
||||
func Hook(h *wp.Handle) {
|
||||
wp.Run(h, configs)
|
||||
}
|
||||
|
||||
func configs(h *wp.Handle) {
|
||||
wp.InitPipe(h)
|
||||
middleware.CommonMiddleware(h)
|
||||
h.AddActionFilter("bodyClass", calClass)
|
||||
h.PushCacheGroupHeadScript(constraints.AllScene, "colorScheme-customHeader", 10, colorScheme, customHeader)
|
||||
components.WidgetArea(h)
|
||||
pushScripts(h)
|
||||
h.PushRender(constraints.AllStats, wp.NewHandleFn(calCustomHeader, 10.005, "calCustomHeader"))
|
||||
wp.SetComponentsArgs(widgets.Widget, map[string]string{
|
||||
"{$before_widget}": `<section id="%s" class="%s">`,
|
||||
"{$after_widget}": `</section>`,
|
||||
})
|
||||
h.PushRender(constraints.AllStats,
|
||||
wp.NewHandleFn(wp.PreTemplate, 70.005, "wp.PreTemplate"),
|
||||
wp.NewHandleFn(errorsHandle, 80.005, "errorsHandle"),
|
||||
)
|
||||
videoHeader(h)
|
||||
h.SetData("colophon", colophon)
|
||||
setPaginationAndRender(h)
|
||||
h.CommonComponents()
|
||||
h.PushPostPlugin(postThumbnail)
|
||||
wp.SetComponentsArgsForMap(widgets.Search, "{$form}", searchForm)
|
||||
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 10.005, "wp.IndexRender"))
|
||||
h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 10.005, "wp.DetailRender"))
|
||||
h.PushDataHandler(constraints.Detail, wp.NewHandleFn(wp.Detail, 100.005, "wp.Detail"), wp.NewHandleFn(postThumb, 90.005, "{theme}.postThumb"))
|
||||
wp.PushIndexHandler(constraints.PipeData, h, wp.NewHandleFn(wp.Index, 100.005, "wp.Index"))
|
||||
h.PushDataHandler(constraints.AllScene, wp.NewHandleFn(wp.PreCodeAndStats, 90.005, "wp.PreCodeAndStats"))
|
||||
}
|
||||
|
||||
func setPaginationAndRender(h *wp.Handle) {
|
||||
h.PushHandler(constraints.PipeRender, constraints.Detail, wp.NewHandleFn(func(hh *wp.Handle) {
|
||||
d := hh.GetDetailHandle()
|
||||
d.CommentRender = commentFormat
|
||||
d.CommentPageEle = commentPageEle
|
||||
d.CommentStep = 2
|
||||
}, 150, "setPaginationAndRender"))
|
||||
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(func(hh *wp.Handle) {
|
||||
i := hh.GetIndexHandle()
|
||||
i.SetPageEle(paginate)
|
||||
}, 150, "setPaginationAndRender"))
|
||||
}
|
||||
|
||||
var searchForm = `<form role="search" method="get" class="search-form" action="/">
|
||||
<label for="search-form-1">
|
||||
<span class="screen-reader-text">{$label}:</span>
|
||||
</label>
|
||||
<input type="search" id="search-form-1" class="search-field" placeholder="{$placeholder}…" value="{$value}" name="s">
|
||||
<button type="submit" class="search-submit">
|
||||
<svg class="icon icon-search" aria-hidden="true" role="img"> <use href="#icon-search" xlink:href="#icon-search"></use> </svg>
|
||||
<span class="screen-reader-text">{$button}</span>
|
||||
</button>
|
||||
</form>`
|
||||
|
||||
func errorsHandle(h *wp.Handle) {
|
||||
switch h.Stats {
|
||||
case constraints.Error404, constraints.InternalErr, constraints.ParamError:
|
||||
logs.IfError(h.Err(), "报错:")
|
||||
h.SetTempl("twentyseventeen/posts/error.gohtml")
|
||||
}
|
||||
}
|
||||
|
||||
func postThumb(h *wp.Handle) {
|
||||
d := h.GetDetailHandle()
|
||||
if d.Post.Thumbnail.Path != "" {
|
||||
img := wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "full", "", "thumbnail", "post-thumbnail")
|
||||
img.Sizes = "100vw"
|
||||
img.Srcset = fmt.Sprintf("%s %dw, %s", img.Path, img.Width, img.Srcset)
|
||||
d.Post.Thumbnail = img
|
||||
}
|
||||
}
|
||||
|
||||
var commentFormat = comment{}
|
||||
|
||||
type comment struct {
|
||||
plugins.CommonCommentFormat
|
||||
}
|
||||
|
||||
var commentLi = plugins.CommonLi()
|
||||
|
||||
var respondFn = plugins.Responds(respondStr)
|
||||
|
||||
func (c comment) FormatLi(_ context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
|
||||
return plugins.FormatLi(commentLi, m, respondFn, depth, maxDepth, page, isTls, isThreadComments, eo, parent)
|
||||
}
|
||||
|
||||
var colophon = `<footer id="colophon" class="site-footer">
|
||||
<div class="wrap">
|
||||
<div class="site-info">
|
||||
<a href="https://github.com/fthvgb1/wp-go" class="imprint">自豪地采用 wp-go</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>`
|
||||
|
||||
var respondStr = `<a rel="nofollow" class="comment-reply-link"
|
||||
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
|
||||
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond"
|
||||
data-replyto="回复给{{CommentAuthor}}"
|
||||
aria-label="回复给{{CommentAuthor}}"><svg class="icon icon-mail-reply" aria-hidden="true" role="img"> <use href="#icon-mail-reply" xlink:href="#icon-mail-reply"></use> </svg>回复</a>`
|
||||
|
||||
func postThumbnail(h *wp.Handle, posts *models.Posts) {
|
||||
if posts.Thumbnail.Path != "" {
|
||||
posts.Thumbnail.Sizes = "(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px"
|
||||
if h.Scene() == constraints.Detail {
|
||||
posts.Thumbnail.Sizes = "100vw"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var header = reload.Vars(models.PostThumbnail{}, "twentyseventeen-headerImage")
|
||||
|
||||
func calCustomHeader(h *wp.Handle) {
|
||||
h.SetData("HeaderImage", getHeaderImage(h))
|
||||
}
|
||||
|
||||
func getHeaderImage(h *wp.Handle) (r models.PostThumbnail) {
|
||||
img := header.Load()
|
||||
if img.Path != "" {
|
||||
return img
|
||||
}
|
||||
image, rand := h.GetCustomHeaderImg()
|
||||
if image.Path != "" {
|
||||
r = image
|
||||
r.Sizes = "100vw"
|
||||
if !rand {
|
||||
header.Store(r)
|
||||
}
|
||||
return
|
||||
}
|
||||
r.Path = helper.CutUrlHost(h.CommonThemeMods().ThemeSupport.CustomHeader.DefaultImage)
|
||||
r.Width = 2000
|
||||
r.Height = 1200
|
||||
header.Store(r)
|
||||
return
|
||||
}
|
||||
|
||||
func calClass(h *wp.Handle, s string, _ ...any) string {
|
||||
class := strings.Split(s, " ")
|
||||
themeMods := h.CommonThemeMods()
|
||||
u := wpconfig.GetThemeModsVal(ThemeName, "header_image", themeMods.ThemeSupport.CustomHeader.DefaultImage)
|
||||
if u != "" && u != "remove-header" {
|
||||
class = append(class, "has-header-image")
|
||||
}
|
||||
if len(themeMods.SidebarsWidgets.Data.Sidebar1) > 0 {
|
||||
class = append(class, "has-sidebar")
|
||||
}
|
||||
if themeMods.HeaderTextcolor == "blank" {
|
||||
class = append(class, "title-tagline-hidden")
|
||||
}
|
||||
class = append(class, "hfeed")
|
||||
class = append(class, str.Join("colors-", wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light")))
|
||||
if h.Scene() == constraints.Archive {
|
||||
if "one-column" == wpconfig.GetThemeModsVal(ThemeName, "page_layout", "") {
|
||||
class = append(class, "page-one-column")
|
||||
} else {
|
||||
class = append(class, "page-two-column")
|
||||
}
|
||||
}
|
||||
return strings.Join(class, " ")
|
||||
}
|
||||
|
||||
func videoHeader(h *wp.Handle) {
|
||||
h.AddActionFilter("videoSetting", videoPlay)
|
||||
wp.CustomVideo(h, constraints.Home)
|
||||
}
|
||||
|
||||
func videoPlay(h *wp.Handle, _ string, a ...any) string {
|
||||
if len(a) < 1 {
|
||||
return ""
|
||||
}
|
||||
v, ok := a[0].(*wp.VideoSetting)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
img := getHeaderImage(h)
|
||||
v.Width = img.Width
|
||||
v.Height = img.Height
|
||||
v.PosterUrl = img.Path
|
||||
v.L10n.Play = `<span class="screen-reader-text">播放背景视频</span><svg class="icon icon-play" aria-hidden="true" role="img"> <use href="#icon-play" xlink:href="#icon-play"></use> </svg>`
|
||||
v.L10n.Pause = `<span class="screen-reader-text">暂停背景视频</span><svg class="icon icon-pause" aria-hidden="true" role="img"> <use href="#icon-pause" xlink:href="#icon-pause"></use> </svg>`
|
||||
return ""
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func bodyClass(h *Handle) func() string {
|
||||
return func() string {
|
||||
return h.BodyClass()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handle) BodyClass() string {
|
||||
var class []string
|
||||
if constraints.Ok != h.Stats {
|
||||
class = append(class, "error404")
|
||||
}
|
||||
switch h.scene {
|
||||
case constraints.Home:
|
||||
class = append(class, "home", "blog")
|
||||
|
||||
case constraints.Archive:
|
||||
class = append(class, "archive", "date")
|
||||
|
||||
case constraints.Search:
|
||||
s := "search-no-results"
|
||||
if len(h.GetIndexHandle().Posts) > 0 {
|
||||
s = "search-results"
|
||||
}
|
||||
class = append(class, "search", s)
|
||||
|
||||
case constraints.Category, constraints.Tag:
|
||||
class = append(class, "archive", "category")
|
||||
cat := h.GetIndexHandle().Param.Category
|
||||
if cat == "" {
|
||||
break
|
||||
}
|
||||
_, cate := slice.SearchFirst(cache.CategoriesTags(h.C, h.scene), func(my models.TermsMy) bool {
|
||||
return my.Name == cat
|
||||
})
|
||||
if cate.Slug[0] != '%' {
|
||||
class = append(class, str.Join("category-", cate.Slug))
|
||||
}
|
||||
class = append(class, str.Join("category-", number.IntToString(cate.Terms.TermId)))
|
||||
|
||||
case constraints.Author:
|
||||
class = append(class, "archive", "author")
|
||||
author := h.GetIndexHandle().Param.Author
|
||||
user, _ := cache.GetUserByName(h.C, author)
|
||||
class = append(class, str.Join("author-", number.IntToString(user.Id)))
|
||||
if user.DisplayName[0] != '%' {
|
||||
class = append(class, str.Join("author-", user.DisplayName))
|
||||
}
|
||||
|
||||
case constraints.Detail:
|
||||
class = append(class, "post-template-default", "single", "single-post")
|
||||
class = append(class, str.Join("postid-", number.IntToString(h.GetDetailHandle().Post.Id)))
|
||||
if len(h.themeMods.ThemeSupport.PostFormats) > 0 {
|
||||
class = append(class, "single-format-standard")
|
||||
}
|
||||
}
|
||||
if wpconfig.IsCustomBackground(h.theme) {
|
||||
class = append(class, "custom-background")
|
||||
}
|
||||
if h.themeMods.CustomLogo > 0 || str.ToInteger(wpconfig.GetOption("site_logo"), 0) > 0 {
|
||||
class = append(class, "wp-custom-logo")
|
||||
}
|
||||
if h.themeMods.ThemeSupport.ResponsiveEmbeds {
|
||||
class = append(class, "wp-embed-responsive")
|
||||
}
|
||||
return h.DoActionFilter("bodyClass", strings.Join(class, " "))
|
||||
}
|
||||
|
||||
func postClass(h *Handle) func(posts models.Posts) string {
|
||||
return func(posts models.Posts) string {
|
||||
return h.PostClass(posts)
|
||||
}
|
||||
}
|
||||
func (h *Handle) PostClass(posts models.Posts) string {
|
||||
var class []string
|
||||
class = append(class, fmt.Sprintf("post-%d", posts.Id), posts.PostType,
|
||||
str.Join("type-", posts.PostType), str.Join("status-", posts.PostStatus),
|
||||
"hentry", "format-standard")
|
||||
if h.CommonThemeMods().ThemeSupport.PostThumbnails && posts.Thumbnail.Path != "" {
|
||||
class = append(class, "has-post-thumbnail")
|
||||
}
|
||||
|
||||
if posts.PostPassword != "" {
|
||||
if h.GetPassword() != posts.PostPassword {
|
||||
class = append(class, "post-password-required")
|
||||
} else {
|
||||
class = append(class, "post-password-projected")
|
||||
}
|
||||
}
|
||||
|
||||
if h.scene == constraints.Home && h.IsStick(posts.Id) {
|
||||
class = append(class, "sticky")
|
||||
}
|
||||
for _, id := range posts.TermIds {
|
||||
term, ok := wpconfig.GetTermMy(id)
|
||||
if !ok || term.Slug == "" {
|
||||
continue
|
||||
}
|
||||
class = append(class, TermClass(term))
|
||||
}
|
||||
|
||||
return h.DoActionFilter("postClass", strings.Join(class, " "))
|
||||
}
|
||||
|
||||
func TermClass(term models.TermsMy) string {
|
||||
termClass := term.Slug
|
||||
if strings.Contains(term.Slug, "%") {
|
||||
termClass = strconv.FormatUint(term.TermTaxonomy.TermId, 10)
|
||||
}
|
||||
switch term.Taxonomy {
|
||||
case "post_tag":
|
||||
return str.Join("tag-", termClass)
|
||||
case "post_format":
|
||||
return fmt.Sprintf("format-%s", strings.ReplaceAll(term.Slug, "post-format-", ""))
|
||||
}
|
||||
return str.Join(term.Taxonomy, "-", termClass)
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/plugins"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func RenderComment(ctx context.Context, page int, render plugins.CommentHtml, ids []uint64, timeout time.Duration, isTLS bool) (string, error) {
|
||||
ca, _ := cachemanager.GetMapCache[uint64, models.Comments]("postCommentData")
|
||||
children, _ := cachemanager.GetMapCache[uint64, []uint64]("commentChildren")
|
||||
h := CommentHandle{
|
||||
maxDepth: str.ToInteger(wpconfig.GetOption("thread_comments_depth"), 5),
|
||||
depth: 1,
|
||||
isTls: isTLS,
|
||||
html: render,
|
||||
order: wpconfig.GetOption("comment_order"),
|
||||
ca: ca,
|
||||
children: children,
|
||||
threadComments: wpconfig.GetOption("thread_comments") == "1",
|
||||
page: page,
|
||||
}
|
||||
return h.formatComments(ctx, ids, timeout)
|
||||
}
|
||||
|
||||
type CommentHandle struct {
|
||||
maxDepth int
|
||||
depth int
|
||||
isTls bool
|
||||
html plugins.CommentHtml
|
||||
order string
|
||||
page int
|
||||
ca *cache.MapCache[uint64, models.Comments]
|
||||
children *cache.MapCache[uint64, []uint64]
|
||||
threadComments bool
|
||||
}
|
||||
|
||||
func (c CommentHandle) findGrandchildComments(ctx context.Context, timeout time.Duration, comments []models.Comments) ([]models.Comments, error) {
|
||||
parentIds := slice.Map(comments, func(t models.Comments) uint64 {
|
||||
return t.CommentId
|
||||
})
|
||||
children, err := c.children.GetCacheBatch(ctx, parentIds, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rr := slice.FilterAndMap(children, func(t []uint64) ([]uint64, bool) {
|
||||
return t, len(t) > 0
|
||||
})
|
||||
if len(rr) < 1 {
|
||||
return comments, nil
|
||||
}
|
||||
ids := slice.Decompress(rr)
|
||||
r, err := c.ca.GetCacheBatch(ctx, ids, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rrr, err := c.findGrandchildComments(ctx, timeout, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
comments = append(comments, rrr...)
|
||||
slice.Sort(comments, func(i, j models.Comments) bool {
|
||||
return c.html.FloorOrder(i, j)
|
||||
})
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func (c CommentHandle) formatComments(ctx context.Context, ids []uint64, timeout time.Duration) (html string, err error) {
|
||||
comments, err := c.ca.GetCacheBatch(ctx, ids, timeout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if c.depth > 1 && c.depth < c.maxDepth {
|
||||
slice.Sort(comments, func(i, j models.Comments) bool {
|
||||
return c.html.FloorOrder(i, j)
|
||||
})
|
||||
}
|
||||
fixChildren := false
|
||||
if c.depth >= c.maxDepth {
|
||||
comments, err = c.findGrandchildComments(ctx, timeout, comments)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fixChildren = true
|
||||
}
|
||||
s := str.NewBuilder()
|
||||
for i, comment := range comments {
|
||||
eo := "even"
|
||||
if (i+1)%2 == 0 {
|
||||
eo = "odd"
|
||||
}
|
||||
parent := ""
|
||||
fl := false
|
||||
var children []uint64
|
||||
if !fixChildren {
|
||||
children, err = c.children.GetCache(ctx, comment.CommentId, timeout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if c.threadComments && len(children) > 0 && c.depth < c.maxDepth+1 {
|
||||
parent = "parent"
|
||||
fl = true
|
||||
}
|
||||
s.WriteString(c.html.FormatLi(ctx, comment, c.depth, c.maxDepth, c.page, c.isTls, c.threadComments, eo, parent))
|
||||
if fl {
|
||||
c.depth++
|
||||
ss, err := c.formatComments(ctx, children, timeout)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s.WriteString(`<ol class="children">`, ss, `</ol>`)
|
||||
c.depth--
|
||||
}
|
||||
s.WriteString("</li><!-- #comment-## -->")
|
||||
}
|
||||
|
||||
html = s.String()
|
||||
return
|
||||
}
|
@ -1,256 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var handleComponents = safety.NewMap[string, map[string][]Components[string]]()
|
||||
var handleComponentHook = safety.NewMap[string, map[string][]func(Components[string]) (Components[string], bool)]()
|
||||
|
||||
var componentsArgs = safety.NewMap[string, any]()
|
||||
var componentFilterFns = safety.NewMap[string, []func(*Handle, string, ...any) string]()
|
||||
|
||||
func (h *Handle) DeleteComponents(scene, componentKey, componentName string) {
|
||||
v, ok := handleComponentHook.Load(scene)
|
||||
if !ok {
|
||||
v = make(map[string][]func(Components[string]) (Components[string], bool))
|
||||
}
|
||||
|
||||
v[componentKey] = append(v[componentKey], func(c Components[string]) (Components[string], bool) {
|
||||
return c, c.Name != componentName
|
||||
})
|
||||
handleComponentHook.Store(scene, v)
|
||||
}
|
||||
func (h *Handle) ReplaceComponents(scene, componentKey, componentName string, components Components[string]) {
|
||||
v, ok := handleComponentHook.Load(scene)
|
||||
if !ok {
|
||||
v = make(map[string][]func(Components[string]) (Components[string], bool))
|
||||
}
|
||||
v[componentKey] = append(v[componentKey], func(c Components[string]) (Components[string], bool) {
|
||||
if c.Name == componentName {
|
||||
c = components
|
||||
}
|
||||
return c, true
|
||||
})
|
||||
handleComponentHook.Store(scene, v)
|
||||
}
|
||||
func (h *Handle) PushComponentHooks(scene, componentKey string, fn func(Components[string]) (Components[string], bool)) {
|
||||
v, ok := handleComponentHook.Load(scene)
|
||||
if !ok {
|
||||
v = make(map[string][]func(Components[string]) (Components[string], bool))
|
||||
}
|
||||
v[componentKey] = append(v[componentKey], fn)
|
||||
handleComponentHook.Store(scene, v)
|
||||
}
|
||||
|
||||
var GetAndHookComponents = reload.BuildMapFnWithAnyParams[string]("calComponents", HookComponent)
|
||||
|
||||
func HookComponent(a ...any) []Components[string] {
|
||||
componentKey := a[0].(string)
|
||||
scene := a[1].(string)
|
||||
mut := reload.GetGlobeMutex()
|
||||
mut.Lock()
|
||||
defer mut.Unlock()
|
||||
components := GetComponents(scene, componentKey)
|
||||
r := slice.FilterAndMap(components, func(component Components[string]) (Components[string], bool) {
|
||||
keyHooks, ok := handleComponentHook.Load(scene)
|
||||
if !ok {
|
||||
return component, true
|
||||
}
|
||||
hooks := keyHooks[componentKey]
|
||||
for _, fn := range hooks {
|
||||
hookedComponent, ok := fn(component)
|
||||
if !ok { // DeleteComponents fn
|
||||
return hookedComponent, false
|
||||
}
|
||||
component = hookedComponent // ReplaceComponents fn
|
||||
}
|
||||
return component, true
|
||||
})
|
||||
slice.SimpleSort(r, slice.DESC, func(t Components[string]) float64 {
|
||||
return t.Order
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func GetComponents(scene, key string) (r []Components[string]) {
|
||||
sceneComponents, _ := handleComponents.Load(scene)
|
||||
allSceneComponents, _ := handleComponents.Load(constraints.AllScene)
|
||||
r = append(sceneComponents[key], allSceneComponents[key]...)
|
||||
return
|
||||
}
|
||||
|
||||
var CacheComponent = reload.BuildMapFnWithAnyParams[string]("cacheComponents", cacheComponentFn)
|
||||
|
||||
func cacheComponentFn(a ...any) string {
|
||||
return a[0].(Components[string]).Fn(a[1].(*Handle))
|
||||
}
|
||||
|
||||
func CalComponent(h *Handle) func(string) string {
|
||||
return func(componentKey string) string {
|
||||
cacheKey := str.Join("get-hook-Components-", h.scene, "-", componentKey)
|
||||
hookedComponents := GetAndHookComponents(cacheKey, componentKey, h.scene)
|
||||
var s = make([]string, 0, len(hookedComponents))
|
||||
for _, component := range hookedComponents {
|
||||
if component.Val != "" {
|
||||
s = append(s, component.Val)
|
||||
continue
|
||||
}
|
||||
if component.Fn != nil {
|
||||
v := ""
|
||||
if component.Cached {
|
||||
key := str.Join(h.scene, "-", componentKey, "-", component.Name)
|
||||
v = CacheComponent(key, component, h)
|
||||
} else {
|
||||
v = component.Fn(h)
|
||||
}
|
||||
if v != "" {
|
||||
s = append(s, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(s, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handle) PushComponents(scene, componentType string, components ...Components[string]) {
|
||||
c, ok := handleComponents.Load(scene)
|
||||
if !ok {
|
||||
c = make(map[string][]Components[string])
|
||||
}
|
||||
c[componentType] = append(c[componentType], components...)
|
||||
handleComponents.Store(scene, c)
|
||||
}
|
||||
|
||||
func (h *Handle) PushGroupComponentStr(scene, componentType, name string, order float64, strs ...string) {
|
||||
var component = Components[string]{
|
||||
Val: strings.Join(slice.FilterAndMap(strs, func(t string) (string, bool) {
|
||||
t = strings.Trim(t, " \n\r\t\v\x00")
|
||||
if t == "" {
|
||||
return "", false
|
||||
}
|
||||
return t, true
|
||||
}), "\n"),
|
||||
Order: order,
|
||||
Name: name,
|
||||
}
|
||||
h.PushComponents(scene, componentType, component)
|
||||
}
|
||||
|
||||
func (h *Handle) PushCacheGroupHeadScript(scene, name string, order float64, fns ...func(*Handle) string) {
|
||||
h.PushGroupCacheComponentFn(scene, constraints.HeadScript, name, order, fns...)
|
||||
}
|
||||
|
||||
func (h *Handle) PushFooterScript(scene string, components ...Components[string]) {
|
||||
h.PushComponents(scene, constraints.FooterScript, components...)
|
||||
}
|
||||
|
||||
func (h *Handle) PushGroupFooterScript(scene, name string, order float64, strs ...string) {
|
||||
h.PushGroupComponentStr(scene, constraints.FooterScript, name, order, strs...)
|
||||
}
|
||||
|
||||
func (h *Handle) PushCacheGroupFooterScript(scene, name string, order float64, fns ...func(*Handle) string) {
|
||||
h.PushGroupCacheComponentFn(scene, constraints.FooterScript, name, order, fns...)
|
||||
}
|
||||
func (h *Handle) PushGroupCacheComponentFn(scene, componentType, name string, order float64, fns ...func(*Handle) string) {
|
||||
h.PushComponents(scene, componentType, NewComponent(name, "", true, order, func(h *Handle) string {
|
||||
return strings.Join(slice.Map(fns, func(t func(*Handle) string) string {
|
||||
return t(h)
|
||||
}), "\n")
|
||||
}))
|
||||
}
|
||||
|
||||
func NewComponent(name, val string, cached bool, order float64, fn func(handle *Handle) string) Components[string] {
|
||||
return Components[string]{Fn: fn, Name: name, Cached: cached, Order: order, Val: val}
|
||||
}
|
||||
|
||||
func (h *Handle) AddCacheComponent(scene, componentType, name string, order float64, fn func(*Handle) string) {
|
||||
h.PushComponents(scene, componentType, NewComponent(name, "", true, order, fn))
|
||||
}
|
||||
|
||||
func (h *Handle) PushHeadScript(scene string, components ...Components[string]) {
|
||||
h.PushComponents(scene, constraints.HeadScript, components...)
|
||||
}
|
||||
func (h *Handle) PushGroupHeadScript(scene, name string, order float64, str ...string) {
|
||||
h.PushGroupComponentStr(scene, constraints.HeadScript, name, order, str...)
|
||||
}
|
||||
|
||||
func GetComponentsArgs[T any](k string, defaults T) T {
|
||||
v, ok := componentsArgs.Load(k)
|
||||
if ok {
|
||||
vv, ok := v.(T)
|
||||
if ok {
|
||||
return vv
|
||||
}
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
|
||||
func PushComponentsArgsForSlice[T any](name string, v ...T) {
|
||||
val, ok := componentsArgs.Load(name)
|
||||
if !ok {
|
||||
var vv []T
|
||||
vv = append(vv, v...)
|
||||
componentsArgs.Store(name, vv)
|
||||
return
|
||||
}
|
||||
vv, ok := val.([]T)
|
||||
if ok {
|
||||
vv = append(vv, v...)
|
||||
componentsArgs.Store(name, vv)
|
||||
}
|
||||
}
|
||||
func SetComponentsArgsForMap[K comparable, V any](name string, key K, v V) {
|
||||
val, ok := componentsArgs.Load(name)
|
||||
if !ok {
|
||||
vv := make(map[K]V)
|
||||
vv[key] = v
|
||||
componentsArgs.Store(name, vv)
|
||||
return
|
||||
}
|
||||
vv, ok := val.(map[K]V)
|
||||
if ok {
|
||||
vv[key] = v
|
||||
componentsArgs.Store(name, vv)
|
||||
}
|
||||
}
|
||||
func MergeComponentsArgsForMap[K comparable, V any](name string, m map[K]V) {
|
||||
val, ok := componentsArgs.Load(name)
|
||||
if !ok {
|
||||
componentsArgs.Store(name, m)
|
||||
return
|
||||
}
|
||||
vv, ok := val.(map[K]V)
|
||||
if ok {
|
||||
componentsArgs.Store(name, maps.Merge(vv, m))
|
||||
}
|
||||
}
|
||||
|
||||
func SetComponentsArgs(key string, value any) {
|
||||
componentsArgs.Store(key, value)
|
||||
}
|
||||
|
||||
func (h *Handle) GetComponentFilterFn(name string) ([]func(*Handle, string, ...any) string, bool) {
|
||||
return componentFilterFns.Load(name)
|
||||
}
|
||||
|
||||
func (h *Handle) AddActionFilter(name string, fns ...func(*Handle, string, ...any) string) {
|
||||
v, _ := componentFilterFns.Load(name)
|
||||
v = append(v, fns...)
|
||||
componentFilterFns.Store(name, v)
|
||||
}
|
||||
func (h *Handle) DoActionFilter(name, s string, args ...any) string {
|
||||
calls, ok := componentFilterFns.Load(name)
|
||||
if ok {
|
||||
return slice.Reduce(calls, func(fn func(*Handle, string, ...any) string, r string) string {
|
||||
return fn(h, r, args...)
|
||||
}, s)
|
||||
}
|
||||
return s
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/components/block"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var blockFn = map[string]func(*wp.Handle, string, block.ParserBlock) (func() string, error){
|
||||
"core/categories": block.Category,
|
||||
}
|
||||
|
||||
func Block(id string) (func(*wp.Handle) string, string) {
|
||||
content := wpconfig.GetPHPArrayVal("widget_block", "", str.ToInteger[int64](id, 0), "content")
|
||||
if content == "" {
|
||||
return nil, ""
|
||||
}
|
||||
var name string
|
||||
v := block.ParseBlock(content)
|
||||
if len(v.Output) > 0 {
|
||||
name = v.Output[0].Name
|
||||
}
|
||||
return func(h *wp.Handle) string {
|
||||
var out []string
|
||||
for _, parserBlock := range v.Output {
|
||||
fn, ok := blockFn[parserBlock.Name]
|
||||
if ok {
|
||||
s, err := fn(h, id, parserBlock)
|
||||
if err != nil {
|
||||
logs.Error(err, str.Join("parse block", parserBlock.Name, " fail "), parserBlock)
|
||||
continue
|
||||
}
|
||||
out = append(out, s())
|
||||
|
||||
}
|
||||
}
|
||||
return strings.Join(out, "\n")
|
||||
}, name
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"github.com/dlclark/regexp2"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
)
|
||||
|
||||
type Block struct {
|
||||
Name string
|
||||
Attrs string
|
||||
Len int
|
||||
StartOffset int
|
||||
Type string
|
||||
}
|
||||
|
||||
type BockParser struct {
|
||||
Document string
|
||||
Offset int
|
||||
Output []ParserBlock
|
||||
}
|
||||
|
||||
type ParserBlock struct {
|
||||
Name string
|
||||
Attrs string
|
||||
InnerBlocks string
|
||||
InnerHtml string
|
||||
InnerContent string
|
||||
}
|
||||
|
||||
var block = regexp2.MustCompile(`<!--\s+(?<closer>/)?wp:(?<namespace>[a-z][a-z0-9_-]*\/)?(?<name>[a-z][a-z0-9_-]*)\s+(?<attrs>{(?:(?:[^}]+|}+(?=})|(?!}\s+\/?-->).)*)?}\s+)?(?<void>\/)?-->`, regexp2.IgnoreCase|regexp2.Singleline)
|
||||
|
||||
func ParseBlock(content string) (r BockParser) {
|
||||
m, err := block.FindStringMatch(content)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
r.Document = content
|
||||
for m != nil {
|
||||
if m.GroupCount() < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
b := token(m.Groups())
|
||||
bb := ParserBlock{}
|
||||
bb.Name = b.Name
|
||||
bb.Attrs = b.Attrs
|
||||
r.Output = append(r.Output, bb)
|
||||
m, _ = block.FindNextMatch(m)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func token(g []regexp2.Group) (b Block) {
|
||||
if len(g) < 1 {
|
||||
b.Type = "no-more-tokens"
|
||||
return
|
||||
}
|
||||
var closer, name, void, nameSpace = "", "", "", ""
|
||||
for i, group := range g {
|
||||
v := group.String()
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
switch i {
|
||||
case 0:
|
||||
b.Len = group.Length
|
||||
b.StartOffset = group.Index
|
||||
case 1:
|
||||
closer = v
|
||||
case 2:
|
||||
nameSpace = v
|
||||
case 3:
|
||||
name = v
|
||||
case 4:
|
||||
b.Attrs = v
|
||||
case 5:
|
||||
void = v
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
if nameSpace == "" {
|
||||
nameSpace = "core/"
|
||||
}
|
||||
b.Name = str.Join(nameSpace, name)
|
||||
if void != "" {
|
||||
b.Type = "void-block"
|
||||
return
|
||||
}
|
||||
if closer != "" {
|
||||
b.Type = "block-closer"
|
||||
return
|
||||
}
|
||||
b.Type = "block-opener"
|
||||
return b
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package block
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseBlock(t *testing.T) {
|
||||
type args struct {
|
||||
s string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "t1",
|
||||
args: args{
|
||||
s: `<!-- wp:categories {"showPostCounts":true,"showEmpty":true} /-->`,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ParseBlock(tt.args.s)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
constraints2 "github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func categoryConf() map[any]any {
|
||||
return map[any]any{
|
||||
"count": int64(0),
|
||||
"dropdown": int64(0),
|
||||
"hierarchical": int64(0),
|
||||
"title": "分类",
|
||||
}
|
||||
}
|
||||
|
||||
func categoryDefaultArgs() map[string]string {
|
||||
return map[string]string{
|
||||
"{$before_widget}": `<aside id="%s" class="%s">`,
|
||||
"{$after_widget}": `</aside>`,
|
||||
"{$name}": "cat",
|
||||
"{$class}": "postform",
|
||||
"{$selectId}": "cat",
|
||||
"{$required}": "",
|
||||
"{$show_option_none}": "选择分类",
|
||||
"{$title}": "",
|
||||
}
|
||||
}
|
||||
|
||||
func parseAttr(attr map[any]any) string {
|
||||
var attrs []string
|
||||
class := maps.GetAnyAnyValWithDefaults(attr, "", "className")
|
||||
classes := strings.Split(class, " ")
|
||||
fontsize := maps.GetAnyAnyValWithDefaults(attr, "", "fontSize")
|
||||
if fontsize != "" {
|
||||
classes = append(classes, fmt.Sprintf("has-%s-font-size", fontsize))
|
||||
}
|
||||
style := maps.GetAnyAnyValWithDefaults[map[any]any](attr, nil, "style", "typography")
|
||||
if len(style) > 0 {
|
||||
styless := maps.AnyAnyMapTo(style, func(k, v any) (string, string, bool) {
|
||||
kk, ok := k.(string)
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
vv, ok := v.(string)
|
||||
if !ok {
|
||||
return "", "", false
|
||||
}
|
||||
return kk, vv, true
|
||||
})
|
||||
styles := maps.FilterToSlice(styless, func(k string, v string) (string, bool) {
|
||||
k = str.CamelCaseTo(k, '-')
|
||||
return str.Join(k, ":", v), true
|
||||
})
|
||||
attrs = append(attrs, fmt.Sprintf(`style="%s;"`, strings.Join(styles, ";")))
|
||||
}
|
||||
attrs = append(attrs, fmt.Sprintf(`class="%s"`, strings.Join(classes, " ")))
|
||||
return strings.Join(attrs, " ")
|
||||
}
|
||||
|
||||
var GetCategoryAttr = reload.BuildValFn("block-category-attr", parseAttr)
|
||||
|
||||
var GetCategoryConf = reload.BuildValFnWithConfirm("block-category-conf", categoryConfFn, 5)
|
||||
|
||||
func categoryConfFn(blockParser ParserBlock) (map[any]any, bool) {
|
||||
var con any
|
||||
err := json.Unmarshal([]byte(blockParser.Attrs), &con)
|
||||
if err != nil {
|
||||
logs.Error(err, "解析category attr错误", blockParser.Attrs)
|
||||
return nil, false
|
||||
}
|
||||
var conf map[any]any
|
||||
switch con.(type) {
|
||||
case map[any]any:
|
||||
conf = con.(map[any]any)
|
||||
case map[string]any:
|
||||
conf = maps.StrAnyToAnyAny(con.(map[string]any))
|
||||
}
|
||||
conf = maps.FilterZeroMerge(categoryConf(), conf)
|
||||
|
||||
if maps.GetAnyAnyValWithDefaults(conf, false, "showPostCounts") {
|
||||
conf["count"] = int64(1)
|
||||
}
|
||||
|
||||
if maps.GetAnyAnyValWithDefaults(conf, false, "displayAsDropdown") {
|
||||
conf["dropdown"] = int64(1)
|
||||
}
|
||||
if maps.GetAnyAnyValWithDefaults(conf, false, "showHierarchy") {
|
||||
conf["hierarchical"] = int64(1)
|
||||
}
|
||||
|
||||
class := maps.GetAnyAnyValWithDefaults(conf, "", "className")
|
||||
classes := strings.Split(class, " ")
|
||||
classes = append(classes, "wp-block-categories")
|
||||
if conf["dropdown"].(int64) == 1 {
|
||||
classes = append(classes, "wp-block-categories-dropdown")
|
||||
conf["className"] = strings.Join(classes, " ")
|
||||
} else {
|
||||
classes = append(classes, "wp-block-categories-list")
|
||||
conf["className"] = strings.Join(classes, " ")
|
||||
}
|
||||
return conf, true
|
||||
}
|
||||
|
||||
var GetCategoryArgs = reload.BuildValFnWithAnyParams("block-category-args", categoryArgs)
|
||||
|
||||
func categoryArgs(_ ...any) map[string]string {
|
||||
args := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
|
||||
return maps.FilterZeroMerge(categoryDefaultArgs(), args)
|
||||
}
|
||||
|
||||
func Category(h *wp.Handle, id string, blockParser ParserBlock) (func() string, error) {
|
||||
counter := number.Counters[int]()
|
||||
var err error
|
||||
conf := GetCategoryConf(blockParser)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if conf == nil {
|
||||
return nil, errors.New("解析block-category配置错误")
|
||||
}
|
||||
|
||||
if maps.GetAnyAnyValWithDefaults(conf, false, "showEmpty") {
|
||||
h.C.Set("showEmpty", true)
|
||||
}
|
||||
if maps.GetAnyAnyValWithDefaults(conf, false, "showOnlyTopLevel") {
|
||||
h.C.Set("showOnlyTopLevel", true)
|
||||
}
|
||||
args := GetCategoryArgs()
|
||||
|
||||
return func() string {
|
||||
return category(h, id, counter, args, conf)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func category(h *wp.Handle, id string, counter number.Counter[int], args map[string]string, conf map[any]any) string {
|
||||
var out = ""
|
||||
categories := cache.CategoriesTags(h.C, constraints2.Category)
|
||||
class := []string{"widget", "widget_block", "widget_categories"}
|
||||
if conf["dropdown"].(int64) == 1 {
|
||||
out = dropdown(h, categories, counter(), args, conf)
|
||||
} else {
|
||||
out = categoryUl(h, categories, conf)
|
||||
}
|
||||
before := fmt.Sprintf(args["{$before_widget}"], str.Join("block-", id), strings.Join(class, " "))
|
||||
return str.Join(before, out, args["{$after_widget}"])
|
||||
}
|
||||
|
||||
func categoryUl(h *wp.Handle, categories []models.TermsMy, confAttr map[any]any) string {
|
||||
s := str.NewBuilder()
|
||||
li := widget.CategoryLi(h, confAttr, categories)
|
||||
attrs := GetCategoryAttr(confAttr)
|
||||
s.Sprintf(`<ul %s>%s</ul>`, attrs, li)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func dropdown(h *wp.Handle, categories []models.TermsMy, id int, args map[string]string, confAttr map[any]any) string {
|
||||
s := str.NewBuilder()
|
||||
ids := fmt.Sprintf(`wp-block-categories-%v`, id)
|
||||
args = maps.Copy(args)
|
||||
args["{$selectId}"] = ids
|
||||
attrs := GetCategoryAttr(confAttr)
|
||||
selects := widget.DropdownCategories(h, args, confAttr, categories)
|
||||
s.Sprintf(`<div %s><label class="screen-reader-text" for="%s">%s</label>%s%s</div>`, attrs, ids, args["{$title}"], selects, strings.ReplaceAll(categoryDropdownScript, "{$id}", ids))
|
||||
return s.String()
|
||||
}
|
||||
|
||||
var categoryDropdownScript = `
|
||||
<script type='text/javascript'>
|
||||
/* <![CDATA[ */
|
||||
( function() {
|
||||
const dropdown = document.getElementById( '{$id}' );
|
||||
function onCatChange() {
|
||||
if ( dropdown.options[ dropdown.selectedIndex ].value > 0 ) {
|
||||
location.href = "/?cat=" + dropdown.options[ dropdown.selectedIndex ].value;
|
||||
}
|
||||
}
|
||||
dropdown.onchange = onCatChange;
|
||||
})();
|
||||
/* ]]> */
|
||||
</script>
|
||||
`
|
@ -1,128 +0,0 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var archiveTemplate = `{$before_widget}
|
||||
{$title}
|
||||
{$nav}
|
||||
{$html}
|
||||
{$navCloser}
|
||||
{$after_widget}
|
||||
`
|
||||
|
||||
func archiveArgs() map[string]string {
|
||||
return map[string]string{
|
||||
"{$before_sidebar}": "",
|
||||
"{$after_sidebar}": "",
|
||||
"{$nav}": "",
|
||||
"{$navCloser}": "",
|
||||
"{$title}": "",
|
||||
"{$dropdown_id}": "archives-dropdown-2",
|
||||
"{$dropdown_type}": "monthly",
|
||||
"{$dropdown_label}": "选择月份",
|
||||
}
|
||||
}
|
||||
|
||||
var archivesConfig = map[any]any{
|
||||
"count": int64(0),
|
||||
"dropdown": int64(0),
|
||||
"title": "归档",
|
||||
}
|
||||
|
||||
var GetArchiveConf = BuildconfigFn(archivesConfig, "widget_archives", int64(2))
|
||||
var GetArchiveArgs = reload.BuildValFnWithAnyParams("widget_archive-args", archiveArgsFn)
|
||||
|
||||
func archiveArgsFn(a ...any) map[string]string {
|
||||
h := a[0].(*wp.Handle)
|
||||
conf := a[1].(map[any]any)
|
||||
id := a[2].(string)
|
||||
archiveArgs := archiveArgs()
|
||||
commonArgs := wp.GetComponentsArgs(widgets.Widget, CommonArgs())
|
||||
args := wp.GetComponentsArgs(widgets.Archive, archiveArgs)
|
||||
args = maps.FilterZeroMerge(archiveArgs, CommonArgs(), commonArgs, args)
|
||||
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("archives-", id), str.Join("widget widget_", "archive"))
|
||||
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
|
||||
if conf["dropdown"].(int64) == 0 && slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
|
||||
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, conf["title"].(string))
|
||||
args["{$navCloser}"] = "</nav>"
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func Archive(h *wp.Handle, id string) string {
|
||||
conf := GetArchiveConf()
|
||||
args := GetArchiveArgs(h, conf, id)
|
||||
|
||||
s := archiveTemplate
|
||||
if int64(1) == conf["dropdown"].(int64) {
|
||||
s = strings.ReplaceAll(s, "{$html}", archiveDropDown(h, conf, args, cache.Archives(h.C)))
|
||||
} else {
|
||||
s = strings.ReplaceAll(s, "{$html}", archiveUl(h, conf, args, cache.Archives(h.C)))
|
||||
}
|
||||
return h.DoActionFilter(widgets.Archive, str.Replace(s, args))
|
||||
}
|
||||
|
||||
var dropdownScript = `
|
||||
<script>
|
||||
/* <![CDATA[ */
|
||||
(function() {
|
||||
const dropdown = document.getElementById("archives-dropdown-2");
|
||||
function onSelectChange() {
|
||||
if ( dropdown.options[ dropdown.selectedIndex ].value !== '' ) {
|
||||
document.location.href = this.options[ this.selectedIndex ].value;
|
||||
}
|
||||
}
|
||||
dropdown.onchange = onSelectChange;
|
||||
})();
|
||||
/* ]]> */
|
||||
</script>`
|
||||
|
||||
func archiveDropDown(h *wp.Handle, conf map[any]any, args map[string]string, archives []models.PostArchive) string {
|
||||
option := str.NewBuilder()
|
||||
option.Sprintf(`<option value="">%s</option>`, args["{$dropdown_label}"])
|
||||
i := h.GetIndexHandle()
|
||||
month := strings.TrimLeft(i.Param.Month, "0")
|
||||
showCount := conf["count"].(int64)
|
||||
for _, archive := range archives {
|
||||
sel := ""
|
||||
if i.Param.Year == archive.Year && month == archive.Month {
|
||||
sel = "selected"
|
||||
}
|
||||
count := ""
|
||||
if showCount == int64(1) {
|
||||
count = fmt.Sprintf("(%v)", archive.Posts)
|
||||
}
|
||||
option.Sprintf(`<option %s value="/p/date/%s/%02s" >%s年%s月 %s</option>
|
||||
`, sel, archive.Year, archive.Month, archive.Year, archive.Month, count)
|
||||
}
|
||||
s := str.NewBuilder()
|
||||
s.Sprintf(`<label class="screen-reader-text" for="%s">%s</label>
|
||||
<select id="%s" name="archive-dropdown">%s</select>%s
|
||||
`, args["{$dropdown_id}"], args["{$title}"], args["{$dropdown_id}"], option.String(), dropdownScript)
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func archiveUl(h *wp.Handle, conf map[any]any, args map[string]string, archives []models.PostArchive) string {
|
||||
s := str.NewBuilder()
|
||||
s.WriteString(`<ul>`)
|
||||
showCount := conf["count"].(int64)
|
||||
for _, archive := range archives {
|
||||
count := ""
|
||||
if showCount == 1 {
|
||||
count = fmt.Sprintf("(%v)", archive.Posts)
|
||||
}
|
||||
s.Sprintf(`<li><a href="/p/date/%[1]s/%02[2]s">%[1]s年%[2]s月%[3]s</a></li>`, archive.Year, archive.Month, count)
|
||||
}
|
||||
return s.String()
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package widget
|
||||
|
||||
func CommonArgs() map[string]string {
|
||||
return map[string]string{
|
||||
"{$before_widget}": `<aside id="%s" class="%s">`,
|
||||
"{$after_widget}": "</aside>",
|
||||
"{$before_title}": `<h2 class="widget-title">`,
|
||||
"{$after_title}": "</h2>",
|
||||
}
|
||||
}
|
@ -1,277 +0,0 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/helper/tree"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var categoryTemplate = `{$before_widget}
|
||||
{$title}
|
||||
{$nav}
|
||||
{$html}
|
||||
{$navCloser}
|
||||
{$after_widget}
|
||||
`
|
||||
var categoryConfig = map[any]any{
|
||||
"count": int64(0),
|
||||
"dropdown": int64(0),
|
||||
"hierarchical": int64(0),
|
||||
"title": "分类",
|
||||
}
|
||||
|
||||
func categoryArgs() map[string]string {
|
||||
return map[string]string{
|
||||
"{$before_sidebar}": "",
|
||||
"{$after_sidebar}": "",
|
||||
"{$class}": "postform",
|
||||
"{$show_option_none}": "选择分类",
|
||||
"{$name}": "cat",
|
||||
"{$selectId}": "cat",
|
||||
"{$required}": "",
|
||||
"{$nav}": "",
|
||||
"{$navCloser}": "",
|
||||
"{$title}": "",
|
||||
}
|
||||
}
|
||||
|
||||
var GetCategoryConf = BuildconfigFn(categoryConfig, "widget_categories", int64(2))
|
||||
|
||||
var GetCategoryArgs = reload.BuildValFnWithAnyParams("widget-category-args", categoryArgsFn)
|
||||
|
||||
func categoryArgsFn(a ...any) map[string]string {
|
||||
h := a[0].(*wp.Handle)
|
||||
conf := a[1].(map[any]any)
|
||||
id := a[2].(string)
|
||||
commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
|
||||
args := wp.GetComponentsArgs(widgets.Categories, categoryArgs())
|
||||
args = maps.FilterZeroMerge(categoryArgs(), CommonArgs(), commonArgs, args)
|
||||
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("categories-", id), str.Join("widget widget_", "categories"))
|
||||
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
|
||||
if conf["dropdown"].(int64) == 0 && slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
|
||||
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, args["{title}"])
|
||||
args["{$navCloser}"] = "</nav>"
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func Category(h *wp.Handle, id string) string {
|
||||
conf := GetCategoryConf()
|
||||
args := GetCategoryArgs(h, conf, id)
|
||||
t := categoryTemplate
|
||||
dropdown := conf["dropdown"].(int64)
|
||||
categories := cache.CategoriesTags(h.C, constraints.Category)
|
||||
if dropdown == 1 {
|
||||
t = strings.ReplaceAll(t, "{$html}", CategoryDropdown(h, args, conf, categories))
|
||||
} else {
|
||||
t = strings.ReplaceAll(t, "{$html}", categoryUL(h, args, conf, categories))
|
||||
}
|
||||
return h.DoActionFilter(widgets.Categories, str.Replace(t, args))
|
||||
}
|
||||
|
||||
func CategoryLi(h *wp.Handle, conf map[any]any, categories []models.TermsMy) string {
|
||||
s := str.NewBuilder()
|
||||
isCount := conf["count"].(int64)
|
||||
currentCate := models.TermsMy{}
|
||||
if h.Scene() == constraints.Category {
|
||||
cat := h.C.Param("category")
|
||||
_, currentCate = slice.SearchFirst(categories, func(my models.TermsMy) bool {
|
||||
return cat == my.Name
|
||||
})
|
||||
}
|
||||
if conf["hierarchical"].(int64) == 0 {
|
||||
for _, category := range categories {
|
||||
count := ""
|
||||
if isCount != 0 {
|
||||
count = fmt.Sprintf("(%d)", category.Count)
|
||||
}
|
||||
current := ""
|
||||
if category.TermTaxonomyId == currentCate.TermTaxonomyId {
|
||||
current = "current-cat"
|
||||
}
|
||||
s.Sprintf(` <li class="cat-item cat-item-%d %s">
|
||||
<a href="/p/category/%s">%s %s</a>
|
||||
</li>
|
||||
`, category.Terms.TermId, current, category.Name, category.Name, count)
|
||||
}
|
||||
} else {
|
||||
|
||||
m := tree.Roots(categories, 0, func(cate models.TermsMy) (child, parent uint64) {
|
||||
return cate.TermTaxonomyId, cate.Parent
|
||||
})
|
||||
cate := &tree.Node[models.TermsMy, uint64]{Data: models.TermsMy{}}
|
||||
if currentCate.TermTaxonomyId > 0 {
|
||||
cate = m[currentCate.TermTaxonomyId]
|
||||
}
|
||||
r := m[0]
|
||||
categoryLi(r, cate, tree.Ancestor(m, 0, cate), isCount, s)
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func categoryUL(h *wp.Handle, args map[string]string, conf map[any]any, categories []models.TermsMy) string {
|
||||
s := str.NewBuilder()
|
||||
s.WriteString("<ul>\n")
|
||||
s.WriteString(CategoryLi(h, conf, categories))
|
||||
s.WriteString("</ul>")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func categoryLi(root *tree.Node[models.TermsMy, uint64], cate, roots *tree.Node[models.TermsMy, uint64], isCount int64, s *str.Builder) {
|
||||
for _, child := range *root.Children {
|
||||
category := child.Data
|
||||
count := ""
|
||||
if isCount != 0 {
|
||||
count = fmt.Sprintf("(%d)", category.Count)
|
||||
}
|
||||
var class []string
|
||||
|
||||
if len(*child.Children) > 0 && cate.Data.TermTaxonomyId > 0 {
|
||||
if category.TermTaxonomyId == cate.Parent {
|
||||
class = append(class, "current-cat-parent")
|
||||
}
|
||||
|
||||
if cate.Parent > 0 && category.TermTaxonomyId == roots.Data.TermTaxonomyId {
|
||||
class = append(class, "current-cat-ancestor")
|
||||
}
|
||||
}
|
||||
aria := ""
|
||||
if category.TermTaxonomyId == cate.Data.TermTaxonomyId {
|
||||
class = append(class, "current-cat")
|
||||
aria = `aria-current="page"`
|
||||
}
|
||||
s.Sprintf(` <li class="cat-item cat-item-%d %s">
|
||||
<a %s href="/p/category/%s">%s %s</a>
|
||||
|
||||
`, category.Terms.TermId, strings.Join(class, " "), aria, category.Name, category.Name, count)
|
||||
|
||||
if len(*child.Children) > 0 {
|
||||
s.WriteString(` <ul class="children">
|
||||
`)
|
||||
categoryLi(&child, cate, roots, isCount, s)
|
||||
s.WriteString(`</ul>
|
||||
`)
|
||||
}
|
||||
s.Sprintf(`</li>`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var categoryDropdownJs = `/* <![CDATA[ */
|
||||
(function() {
|
||||
var dropdown = document.getElementById( "%s" );
|
||||
function onCatChange() {
|
||||
if ( dropdown.options[ dropdown.selectedIndex ].value > 0 ) {
|
||||
dropdown.parentNode.submit();
|
||||
}
|
||||
}
|
||||
dropdown.onchange = onCatChange;
|
||||
})();
|
||||
/* ]]> */
|
||||
`
|
||||
|
||||
func CategoryDropdown(h *wp.Handle, args map[string]string, conf map[any]any, categories []models.TermsMy) string {
|
||||
s := str.NewBuilder()
|
||||
s.WriteString(`<form action="/" method="get">
|
||||
`)
|
||||
s.Sprintf(` <label class="screen-reader-text" for="%s">%s</label>
|
||||
`, args["{$selectId}"], args["{$title}"])
|
||||
s.WriteString(DropdownCategories(h, args, conf, categories))
|
||||
s.WriteString("</form>\n")
|
||||
attr := ""
|
||||
if !slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "script") {
|
||||
attr = ` type="text/javascript"`
|
||||
}
|
||||
s.Sprintf(`<script%s>
|
||||
`, attr)
|
||||
s.Sprintf(categoryDropdownJs, args["{$selectId}"])
|
||||
s.WriteString("</script>\n")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func DropdownCategories(h *wp.Handle, args map[string]string, conf map[any]any, categories []models.TermsMy) string {
|
||||
if len(categories) < 1 {
|
||||
return ""
|
||||
}
|
||||
s := str.NewBuilder()
|
||||
s.Sprintf(` <select %s name="%s" id="%s" class="%s">
|
||||
`, args["{$required}"], args["{$name}"], args["{$selectId}"], args["{$class}"])
|
||||
s.Sprintf(` <option value="-1">%s</option>
|
||||
`, args["{$show_option_none}"])
|
||||
currentCategory := ""
|
||||
i := h.GetIndexHandle()
|
||||
if h.Scene() == constraints.Category {
|
||||
currentCategory = i.Param.Category
|
||||
}
|
||||
showCount := conf["count"].(int64)
|
||||
fn := func(category models.TermsMy, deep int) {
|
||||
lv := fmt.Sprintf("level-%d", deep+1)
|
||||
sep := strings.Repeat(" ", deep*2)
|
||||
selected := ""
|
||||
if category.Name == currentCategory {
|
||||
selected = "selected"
|
||||
}
|
||||
count := ""
|
||||
if showCount != 0 {
|
||||
count = fmt.Sprintf("(%d)", category.Count)
|
||||
}
|
||||
s.Sprintf(` <option class="%s" %s value="%d">%s%s %s</option>
|
||||
`, lv, selected, category.Terms.TermId, sep, category.Name, count)
|
||||
}
|
||||
if conf["hierarchical"].(int64) == 0 {
|
||||
for _, category := range categories {
|
||||
fn(category, 0)
|
||||
}
|
||||
} else {
|
||||
tree.Root(categories, 0, func(t models.TermsMy) (child, parent uint64) {
|
||||
return t.TermTaxonomyId, t.Parent
|
||||
}).Loop(func(category models.TermsMy, deep int) {
|
||||
fn(category, deep)
|
||||
})
|
||||
}
|
||||
s.WriteString(" </select>\n")
|
||||
return h.DoActionFilter("wp_dropdown_cats", s.String())
|
||||
}
|
||||
|
||||
func IsCategory(h *wp.Handle) (category models.TermsMy, r bool) {
|
||||
cate := wp.GetComponentsArgs[map[string]string](widgets.Categories, categoryArgs())
|
||||
name, ok := cate["{$name}"]
|
||||
if !ok || name == "" {
|
||||
return
|
||||
}
|
||||
cat := h.C.Query(name)
|
||||
if cat == "" {
|
||||
return
|
||||
}
|
||||
id := str.ToInteger[uint64](cat, 0)
|
||||
if id < 1 {
|
||||
return
|
||||
}
|
||||
i, cc := slice.SearchFirst(cache.CategoriesTags(h.C, constraints.Category), func(my models.TermsMy) bool {
|
||||
return id == my.Terms.TermId
|
||||
})
|
||||
if i < 0 {
|
||||
return
|
||||
}
|
||||
r = true
|
||||
category = cc
|
||||
return
|
||||
}
|
||||
|
||||
func CategoryQueryName(h *wp.Handle) string {
|
||||
cate := wp.GetComponentsArgs[map[string]string](widgets.Categories, categoryArgs())
|
||||
name, ok := cate["{$name}"]
|
||||
if ok {
|
||||
return name
|
||||
}
|
||||
return ""
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
)
|
||||
|
||||
func Fn(id string, fn func(*wp.Handle, string) string) func(h *wp.Handle) string {
|
||||
return func(h *wp.Handle) string {
|
||||
return fn(h, id)
|
||||
}
|
||||
}
|
||||
|
||||
func configFns[K comparable, V any](m map[K]V, key string, a ...any) func(_ ...any) map[K]V {
|
||||
return func(_ ...any) map[K]V {
|
||||
c := wpconfig.GetPHPArrayVal[map[K]V](key, nil, a...)
|
||||
return maps.FilterZeroMerge(maps.Copy(m), c)
|
||||
}
|
||||
}
|
||||
|
||||
func BuildconfigFn[K comparable, V any](m map[K]V, key string, a ...any) func(_ ...any) map[K]V {
|
||||
return reload.BuildValFnWithAnyParams(str.Join("widget-config-", key), configFns(m, key, a...))
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var metaTemplate = `{$before_widget}
|
||||
{$h2title}
|
||||
{$nav}
|
||||
<ul>
|
||||
{$li}
|
||||
</ul>
|
||||
{$navCloser}
|
||||
{$after_widget}`
|
||||
|
||||
func defaultMetaArgs() map[string]string {
|
||||
return map[string]string{
|
||||
"{$aria_label}": "",
|
||||
"{$title}": "",
|
||||
}
|
||||
}
|
||||
|
||||
var GetMetaArgs = reload.BuildValFnWithAnyParams("widget-meta-args", ParseMetaArgs)
|
||||
|
||||
func ParseMetaArgs(a ...any) map[string]string {
|
||||
h := a[0].(*wp.Handle)
|
||||
id := a[1].(string)
|
||||
commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
|
||||
metaArgs := defaultMetaArgs()
|
||||
args := wp.GetComponentsArgs(widgets.Meta, metaArgs)
|
||||
args = maps.FilterZeroMerge(metaArgs, CommonArgs(), commonArgs, args)
|
||||
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("meta-", id), str.Join("widget widget_", "meta"))
|
||||
args["{$title}"] = wpconfig.GetPHPArrayVal("widget_meta", "其它操作", int64(2), "title")
|
||||
if args["{$title}"] == "" {
|
||||
args["{$title}"] = "其他操作"
|
||||
}
|
||||
if args["{$title}"] != "" {
|
||||
args["{$h2title}"] = str.Join(args["{$before_title}"], args["{$title}"], args["{$after_title}"])
|
||||
}
|
||||
if slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
|
||||
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, args["{$title}"])
|
||||
args["{$navCloser}"] = "</nav>"
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func Meta(h *wp.Handle, id string) string {
|
||||
args := GetMetaArgs(h, id)
|
||||
ss := str.NewBuilder()
|
||||
if str.ToInteger(wpconfig.GetOption("users_can_register"), 0) > 0 {
|
||||
ss.Sprintf(`<li><a href="/wp-login.php?action=register">注册</li>`)
|
||||
}
|
||||
ss.Sprintf(`<li><a href="%s">登录</a></li>`, "/wp-login.php")
|
||||
ss.Sprintf(`<li><a href="%s">条目feed</a></li>`, "/feed")
|
||||
ss.Sprintf(`<li><a href="%s">评论feed</a></li>`, "/comments/feed")
|
||||
s := strings.ReplaceAll(metaTemplate, "{$li}", ss.String())
|
||||
return h.DoActionFilter(widgets.Meta, str.Replace(s, args))
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func recentCommentsArgs() map[string]string {
|
||||
return map[string]string{
|
||||
"{$before_sidebar}": "",
|
||||
"{$after_sidebar}": "",
|
||||
"{$nav}": "",
|
||||
"{$navCloser}": "",
|
||||
"{$title}": "",
|
||||
"{$recent_comments_id}": "recentcomments",
|
||||
}
|
||||
}
|
||||
|
||||
var recentCommentConf = map[any]any{
|
||||
"number": int64(5),
|
||||
"title": "近期评论",
|
||||
}
|
||||
|
||||
var recentCommentsTemplate = `{$before_widget}
|
||||
{$nav}
|
||||
{$title}
|
||||
<ul id="{$recent_comments_id}">
|
||||
{$li}
|
||||
</ul>
|
||||
{$navCloser}
|
||||
{$after_widget}
|
||||
`
|
||||
|
||||
var GetRecentCommentConf = BuildconfigFn(recentCommentConf, "widget_recent-comments", int64(2))
|
||||
|
||||
var GetRecentCommentArgs = reload.BuildValFnWithAnyParams("widget-recent-comment-args", RecentCommentArgs)
|
||||
|
||||
func RecentCommentArgs(a ...any) map[string]string {
|
||||
h := a[0].(*wp.Handle)
|
||||
conf := a[1].(map[any]any)
|
||||
id := a[2].(string)
|
||||
commentsArgs := recentCommentsArgs()
|
||||
commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
|
||||
args := wp.GetComponentsArgs(widgets.RecentComments, commentsArgs)
|
||||
args = maps.FilterZeroMerge(commentsArgs, CommonArgs(), commonArgs, args)
|
||||
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("recent-comments-", id), str.Join("widget widget_", "recent_comments"))
|
||||
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
|
||||
if slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
|
||||
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, conf["title"])
|
||||
args["{$navCloser}"] = "</nav>"
|
||||
}
|
||||
return args
|
||||
}
|
||||
func RecentComments(h *wp.Handle, id string) string {
|
||||
conf := GetRecentCommentConf()
|
||||
args := GetRecentCommentArgs(h, conf, id)
|
||||
|
||||
comments := slice.Map(cache.RecentComments(h.C, int(conf["number"].(int64))), func(t models.Comments) string {
|
||||
return fmt.Sprintf(` <li>
|
||||
<span class="comment-author-link">%s</span>发表在《
|
||||
<a href="%s">%s</a>
|
||||
》
|
||||
</li>`, t.CommentAuthor, t.CommentAuthorUrl, t.PostTitle)
|
||||
})
|
||||
s := strings.ReplaceAll(recentCommentsTemplate, "{$li}", strings.Join(comments, "\n"))
|
||||
return h.DoActionFilter(widgets.RecentComments, str.Replace(s, args))
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var recentPostsTemplate = `{$before_widget}
|
||||
{$nav}
|
||||
{$title}
|
||||
<ul>
|
||||
{$li}
|
||||
</ul>
|
||||
{$navCloser}
|
||||
{$after_widget}
|
||||
`
|
||||
|
||||
func DefaultRecentPostsArgs() map[string]string {
|
||||
return map[string]string{
|
||||
"{$before_sidebar}": "",
|
||||
"{$after_sidebar}": "",
|
||||
"{$nav}": "",
|
||||
"{$navCloser}": "",
|
||||
"{$title}": "",
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultRecentConf() map[any]any {
|
||||
return map[any]any{
|
||||
"number": int64(5),
|
||||
"show_date": false,
|
||||
"title": "近期文章",
|
||||
}
|
||||
}
|
||||
|
||||
var GetRecentPostConf = reload.BuildValFnWithAnyParams("widget-recent-posts-conf", RecentPostConf)
|
||||
|
||||
func RecentPostConf(_ ...any) map[any]any {
|
||||
recent := DefaultRecentConf()
|
||||
conf := wpconfig.GetPHPArrayVal[map[any]any]("widget_recent-posts", recent, int64(2))
|
||||
conf = maps.FilterZeroMerge(recent, conf)
|
||||
return conf
|
||||
}
|
||||
|
||||
var GetRecentPostArgs = reload.BuildValFnWithAnyParams("widget-recent-posts-args", ParseRecentPostArgs)
|
||||
|
||||
func ParseRecentPostArgs(a ...any) map[string]string {
|
||||
h := a[0].(*wp.Handle)
|
||||
conf := a[1].(map[any]any)
|
||||
id := a[2].(string)
|
||||
recent := DefaultRecentPostsArgs()
|
||||
commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
|
||||
args := wp.GetComponentsArgs(widgets.RecentPosts, recent)
|
||||
args = maps.FilterZeroMerge(recent, CommonArgs(), commonArgs, args)
|
||||
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("recent-posts-", id), str.Join("widget widget_", "recent_entries"))
|
||||
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
|
||||
if slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
|
||||
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, conf["title"])
|
||||
args["{$navCloser}"] = "</nav>"
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func RecentPosts(h *wp.Handle, id string) string {
|
||||
conf := GetRecentPostConf()
|
||||
args := GetRecentPostArgs(h, conf, id)
|
||||
currentPostId := uint64(0)
|
||||
if h.Scene() == constraints.Detail {
|
||||
currentPostId = str.ToInteger(h.C.Param("id"), uint64(0))
|
||||
}
|
||||
posts := slice.Map(cache.RecentPosts(h.C, int(conf["number"].(int64))), func(t models.Posts) string {
|
||||
t = wp.ProjectTitle(t)
|
||||
date := ""
|
||||
if v, ok := conf["show_date"].(bool); ok && v {
|
||||
date = fmt.Sprintf(`<span class="post-date">%s</span>`, t.PostDateGmt.Format("2006年01月02日"))
|
||||
}
|
||||
ariaCurrent := ""
|
||||
if currentPostId == t.Id {
|
||||
ariaCurrent = ` aria-current="page"`
|
||||
}
|
||||
return fmt.Sprintf(` <li>
|
||||
<a href="/p/%v"%s>%s</a>
|
||||
%s
|
||||
</li>`, t.Id, ariaCurrent, t.PostTitle, date)
|
||||
})
|
||||
s := strings.ReplaceAll(recentPostsTemplate, "{$li}", strings.Join(posts, "\n"))
|
||||
return h.DoActionFilter(widgets.RecentPosts, str.Replace(s, args))
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/html"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var searchTemplate = `{$before_widget}
|
||||
{$title}
|
||||
{$form}
|
||||
{$after_widget}`
|
||||
|
||||
var html5SearchForm = `<form role="search" {$aria_label} method="get" class="search-form" action="/">
|
||||
<label>
|
||||
<span class="screen-reader-text">{$label}</span>
|
||||
<input type="search" class="search-field" placeholder="{$placeholder}" value="{$value}" name="s" />
|
||||
</label>
|
||||
<input type="submit" class="search-submit" value="{$button}" />
|
||||
</form>`
|
||||
var xmlSearchForm = `<form role="search" {$aria_label} method="get" id="searchform" class="searchform" action="/">
|
||||
<div>
|
||||
<label class="screen-reader-text" for="s">{$label}</label>
|
||||
<input type="text" value="{$value}" name="s" id="s" />
|
||||
<input type="submit" id="searchsubmit" value="{$button}" />
|
||||
</div>
|
||||
</form>`
|
||||
|
||||
func searchArgs() map[string]string {
|
||||
return map[string]string{
|
||||
"{$aria_label}": "",
|
||||
"{$title}": "",
|
||||
"{$form}": "",
|
||||
"{$button}": "搜索",
|
||||
"{$placeholder}": "搜索…",
|
||||
"{$label}": "搜索:",
|
||||
}
|
||||
}
|
||||
|
||||
var form = html5SearchForm
|
||||
|
||||
var GetSearchArgs = reload.BuildValFnWithAnyParams("widget-search-args", ParseSearchArgs)
|
||||
|
||||
func ParseSearchArgs(a ...any) map[string]string {
|
||||
h := a[0].(*wp.Handle)
|
||||
id := a[1].(string)
|
||||
search := searchArgs()
|
||||
commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
|
||||
args := wp.GetComponentsArgs(widgets.Search, search)
|
||||
args = maps.FilterZeroMerge(search, CommonArgs(), commonArgs, args)
|
||||
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("search-", id), str.Join("widget widget_", "search"))
|
||||
if args["{$title}"] == "" {
|
||||
args["{$title}"] = wpconfig.GetPHPArrayVal("widget_search", "", int64(2), "title")
|
||||
}
|
||||
|
||||
if args["{$title}"] != "" {
|
||||
args["{$title}"] = str.Join(args["{$before_title}"], args["{$title}"], args["{$after_title}"])
|
||||
}
|
||||
if args["{$form}"] != "" {
|
||||
form = args["{$form}"]
|
||||
delete(args, "{$form}")
|
||||
}
|
||||
if !slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
|
||||
form = xmlSearchForm
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func Search(h *wp.Handle, id string) string {
|
||||
args := GetSearchArgs(h, id)
|
||||
s := strings.ReplaceAll(searchTemplate, "{$form}", form)
|
||||
val := ""
|
||||
if h.Scene() == constraints.Search {
|
||||
val = html.SpecialChars(h.GetIndexHandle().Param.Search)
|
||||
}
|
||||
s = strings.ReplaceAll(s, "{$value}", val)
|
||||
return h.DoActionFilter(widgets.Search, str.Replace(s, args))
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var widgetFn = map[string]widgetComponent{
|
||||
"search": {fn: widget.Search, name: "widget.search"},
|
||||
"recent-posts": {fn: widget.RecentPosts, name: "widget.recent-posts"},
|
||||
"recent-comments": {fn: widget.RecentComments, name: "widget.recent-comments"},
|
||||
"archives": {fn: widget.Archive, name: "widget.archives"},
|
||||
"categories": {fn: widget.Category, name: "widget.categories"},
|
||||
"meta": {fn: widget.Meta, name: "widget.meta", cached: true},
|
||||
}
|
||||
|
||||
type widgetComponent struct {
|
||||
fn func(h *wp.Handle, id string) string
|
||||
cached bool
|
||||
name string
|
||||
}
|
||||
|
||||
func WidgetArea(h *wp.Handle) {
|
||||
h.PushComponents(constraints.AllScene, constraints.SidebarsWidgets, sidebars()...)
|
||||
}
|
||||
|
||||
func sidebars() []wp.Components[string] {
|
||||
v := wpconfig.GetPHPArrayVal("sidebars_widgets", []any{}, "sidebar-1")
|
||||
var i = 10.5
|
||||
return slice.FilterAndMap(v, func(t any) (wp.Components[string], bool) {
|
||||
vv := t.(string)
|
||||
ss := strings.Split(vv, "-")
|
||||
id := ss[len(ss)-1]
|
||||
name := strings.Join(ss[0:len(ss)-1], "-")
|
||||
widgetComponents, ok := widgetFn[name]
|
||||
if name != "block" && !ok {
|
||||
return wp.Components[string]{}, false
|
||||
}
|
||||
var component wp.Components[string]
|
||||
if name == "block" {
|
||||
fn, fnName := Block(id)
|
||||
if fn == nil {
|
||||
return component, false
|
||||
}
|
||||
component.Fn = fn
|
||||
component.Name = str.Join("block.", fnName)
|
||||
} else {
|
||||
component.Fn = widget.Fn(id, widgetComponents.fn)
|
||||
component.Name = widgetComponents.name
|
||||
component.Cached = widgetComponents.cached
|
||||
}
|
||||
i -= 0.001
|
||||
component.Order = i
|
||||
return component, true
|
||||
})
|
||||
}
|
@ -1,239 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (h *Handle) DisplayHeaderText() bool {
|
||||
return h.themeMods.ThemeSupport.CustomHeader.HeaderText && "blank" != h.themeMods.HeaderTextcolor
|
||||
}
|
||||
|
||||
var GetCustomHeaderImgFn = reload.BuildValFnWithConfirm("headerImages", customHeadImag, 5)
|
||||
|
||||
func customHeadImag(h *Handle) ([]models.PostThumbnail, bool) {
|
||||
hs, err := h.GetHeaderImages(h.theme)
|
||||
if err != nil {
|
||||
h.SetErr(fmt.Errorf("get customheadimage err: %v", err), Low)
|
||||
return nil, false
|
||||
}
|
||||
return hs, true
|
||||
}
|
||||
|
||||
func (h *Handle) GetCustomHeaderImg() (r models.PostThumbnail, isRand bool) {
|
||||
var err error
|
||||
img := GetCustomHeaderImgFn(h)
|
||||
err = h.Err()
|
||||
if err != nil && strings.Contains(err.Error(), "get customheadimage err") {
|
||||
logs.Error(err, "获取页眉背景图失败")
|
||||
return
|
||||
}
|
||||
hs := slice.Copy(img)
|
||||
|
||||
if len(hs) < 1 {
|
||||
return
|
||||
}
|
||||
if len(hs) > 1 {
|
||||
isRand = true
|
||||
}
|
||||
r, _ = slice.RandPop(&hs)
|
||||
return
|
||||
}
|
||||
|
||||
type VideoPlay struct {
|
||||
Pause string `json:"pause,omitempty"`
|
||||
Play string `json:"play,omitempty"`
|
||||
PauseSpeak string `json:"pauseSpeak,omitempty"`
|
||||
PlaySpeak string `json:"playSpeak,omitempty"`
|
||||
}
|
||||
|
||||
type VideoSetting struct {
|
||||
MimeType string `json:"mimeType,omitempty"`
|
||||
PosterUrl string `json:"posterUrl,omitempty"`
|
||||
VideoUrl string `json:"videoUrl,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
MinWidth int `json:"minWidth,omitempty"`
|
||||
MinHeight int `json:"minHeight,omitempty"`
|
||||
L10n VideoPlay `json:"l10n"`
|
||||
}
|
||||
|
||||
var videoReg = regexp.MustCompile(`^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)`)
|
||||
|
||||
func GetVideoSetting(h *Handle, u string) (string, error) {
|
||||
|
||||
img, _ := h.GetCustomHeaderImg()
|
||||
v := VideoSetting{
|
||||
MimeType: GetMimeType(u),
|
||||
PosterUrl: img.Path,
|
||||
VideoUrl: u,
|
||||
Width: img.Width,
|
||||
Height: img.Height,
|
||||
MinWidth: 900,
|
||||
MinHeight: 500,
|
||||
L10n: VideoPlay{
|
||||
Pause: "暂停",
|
||||
Play: "播放",
|
||||
PauseSpeak: "视频已暂停",
|
||||
PlaySpeak: "视频正在播放",
|
||||
},
|
||||
}
|
||||
if is := videoReg.FindString(u); is != "" {
|
||||
v.MimeType = "video/x-youtube"
|
||||
}
|
||||
_ = h.DoActionFilter("videoSetting", "", &v)
|
||||
s, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
setting := fmt.Sprintf(`var %s = %s`, "_wpCustomHeaderSettings", string(s))
|
||||
script := str.Join(`<script id="wp-custom-header-js-extra">`, setting, "</script>\n")
|
||||
return script, nil
|
||||
}
|
||||
|
||||
func CustomVideo(h *Handle, scene ...string) (ok bool) {
|
||||
mod, err := wpconfig.GetThemeMods(h.theme)
|
||||
if err != nil {
|
||||
logs.Error(err, "getThemeMods fail", h.theme)
|
||||
return
|
||||
}
|
||||
if !mod.ThemeSupport.CustomHeader.Video || (mod.HeaderVideo < 1 && mod.ExternalHeaderVideo == "") {
|
||||
return
|
||||
}
|
||||
u := ""
|
||||
if mod.HeaderVideo > 0 {
|
||||
post, err := cache.GetPostById(h.C, uint64(mod.HeaderVideo))
|
||||
if err != nil {
|
||||
logs.Error(err, "get headerVideo fail", mod.HeaderVideo)
|
||||
return
|
||||
}
|
||||
u = post.Metas["_wp_attached_file"].(string)
|
||||
u = str.Join("/wp-content/uploads/", u)
|
||||
} else {
|
||||
u = mod.ExternalHeaderVideo
|
||||
}
|
||||
|
||||
hs, err := GetVideoSetting(h, u)
|
||||
if err != nil {
|
||||
logs.Error(err, "get headerVideo fail", mod.HeaderVideo)
|
||||
return
|
||||
}
|
||||
scripts := []string{
|
||||
"/wp-includes/js/dist/vendor/wp-polyfill-inert.min.js",
|
||||
"/wp-includes/js/dist/vendor/regenerator-runtime.min.js",
|
||||
"/wp-includes/js/dist/vendor/wp-polyfill.min.js",
|
||||
"/wp-includes/js/dist/dom-ready.min.js",
|
||||
"/wp-includes/js/dist/hooks.min.js",
|
||||
"/wp-includes/js/dist/i18n.min.js",
|
||||
"/wp-includes/js/dist/a11y.min.js",
|
||||
"/wp-includes/js/wp-custom-header.min.js",
|
||||
}
|
||||
scripts = slice.Map(scripts, func(t string) string {
|
||||
return fmt.Sprintf(`<script src="%s" id="wp-%s-js"></script>
|
||||
`, t, str.Replaces(t, []string{
|
||||
"/wp-includes/js/dist/vendor/",
|
||||
"/wp-includes/js/dist/",
|
||||
"/wp-includes/js/",
|
||||
".min.js",
|
||||
".js",
|
||||
"wp-",
|
||||
"",
|
||||
}))
|
||||
})
|
||||
|
||||
var tr = `<script id="wp-i18n-js-after">
|
||||
wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ 'ltr' ] } );
|
||||
</script>
|
||||
<script id='wp-a11y-js-translations'>
|
||||
( function( domain, translations ) {
|
||||
var localeData = translations.locale_data[ domain ] || translations.locale_data.messages;
|
||||
localeData[""].domain = domain;
|
||||
wp.i18n.setLocaleData( localeData, domain );
|
||||
} )( "default", {"translation-revision-date":"2023-04-23 22:48:55+0000","generator":"GlotPress/4.0.0-alpha.4","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","plural-forms":"nplurals=1; plural=0;","lang":"zh_CN"},"Notifications":["u901au77e5"]}},"comment":{"reference":"wp-includes/js/dist/a11y.js"}} );
|
||||
</script>
|
||||
<script src='/wp-includes/js/dist/a11y.min.js?ver=ecce20f002eda4c19664' id='wp-a11y-js'></script>
|
||||
`
|
||||
c := []Components[string]{
|
||||
NewComponent("wp-a11y-js-translations", tr, true, 10.0065, nil),
|
||||
NewComponent("VideoSetting", hs, true, 10.0064, nil),
|
||||
NewComponent("header-script", scripts[len(scripts)-1], true, 10.0063, nil),
|
||||
}
|
||||
for _, s := range scene {
|
||||
h.PushGroupFooterScript(s, "wp-custom-header", 10.0066, scripts[0:len(scripts)-2]...)
|
||||
h.PushFooterScript(s, c...)
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Handle) GetHeaderImages(theme string) (r []models.PostThumbnail, err error) {
|
||||
meta, err := wpconfig.GetThemeMods(theme)
|
||||
if err != nil || meta.HeaderImage == "" {
|
||||
return
|
||||
}
|
||||
if "random-uploaded-image" != meta.HeaderImage {
|
||||
m, er := cache.GetPostById(h.C, uint64(meta.HeaderImagData.AttachmentId))
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
m.Thumbnail = thumb(m, theme)
|
||||
r = []models.PostThumbnail{m.Thumbnail}
|
||||
return
|
||||
}
|
||||
|
||||
headers, er := model.Finds[models.Posts](h.C, model.Conditions(
|
||||
model.Where(model.SqlBuilder{
|
||||
{"post_type", "attachment"},
|
||||
{"post_status", "inherit"},
|
||||
{"meta_value", theme},
|
||||
{"meta_key", "_wp_attachment_is_custom_header"},
|
||||
}),
|
||||
model.Fields("a.ID"),
|
||||
model.Group("a.ID"),
|
||||
model.Join(model.SqlBuilder{
|
||||
{" a", "left join", "wp_postmeta b", "a.ID=b.post_id"},
|
||||
}),
|
||||
))
|
||||
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
if len(headers) > 0 {
|
||||
posts, er := cache.GetPostsByIds(h.C, slice.Map(headers, func(t models.Posts) uint64 {
|
||||
return t.Id
|
||||
}))
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
r = slice.Map(posts, func(m models.Posts) models.PostThumbnail {
|
||||
return thumb(m, theme)
|
||||
})
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func thumb(m models.Posts, theme string) models.PostThumbnail {
|
||||
m.Thumbnail = wpconfig.Thumbnail(m.AttachmentMetadata, "full", "", "thumbnail", "post-thumbnail", fmt.Sprintf("%s-thumbnail-avatar", theme))
|
||||
m.Thumbnail.Width = m.AttachmentMetadata.Width
|
||||
m.Thumbnail.Height = m.AttachmentMetadata.Height
|
||||
if m.Thumbnail.Path != "" {
|
||||
if len(m.AttachmentMetadata.Sizes) > 0 {
|
||||
m.Thumbnail.Srcset = str.Join(m.Thumbnail.Path, " 2000w, ", m.Thumbnail.Srcset)
|
||||
}
|
||||
}
|
||||
return m.Thumbnail
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/plugins"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wpposts"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/plugin/pagination"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DetailHandle struct {
|
||||
*Handle
|
||||
CommentRender plugins.CommentHtml
|
||||
Comments []uint64
|
||||
Page int
|
||||
Limit int
|
||||
Post models.Posts
|
||||
CommentPageEle pagination.Render
|
||||
TotalRaw int
|
||||
TotalPage int
|
||||
CommentStep int
|
||||
}
|
||||
|
||||
func NewDetailHandle(handle *Handle) *DetailHandle {
|
||||
return &DetailHandle{
|
||||
Handle: handle,
|
||||
Page: 1,
|
||||
Limit: 5,
|
||||
CommentStep: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DetailHandle) BuildDetailData() (err error) {
|
||||
d.ginH["title"] = wpconfig.GetOption("blogname")
|
||||
err = d.CheckAndGetPost()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.CommentData()
|
||||
d.ContextPost()
|
||||
return
|
||||
}
|
||||
|
||||
func (d *DetailHandle) CheckAndGetPost() (err error) {
|
||||
id := str.ToInteger[uint64](d.C.Param("id"), 0)
|
||||
maxId, err := cache.GetMaxPostId(d.C)
|
||||
logs.IfError(err, "get max post id")
|
||||
if id > maxId || id <= 0 {
|
||||
d.Stats = constraints.ParamError
|
||||
err = errors.New("无效的文档id")
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
post, err := cache.GetPostById(d.C, id)
|
||||
if post.Id == 0 || err != nil || post.PostStatus != "publish" {
|
||||
d.Stats = constraints.Error404
|
||||
logs.IfError(err, "获取id失败")
|
||||
err = errors.New(str.Join("无效的文档id "))
|
||||
return
|
||||
}
|
||||
|
||||
d.Post = post
|
||||
d.ginH["user"] = cache.GetUserById(d.C, post.PostAuthor)
|
||||
d.ginH["title"] = fmt.Sprintf("%s-%s", post.PostTitle, wpconfig.GetOption("blogname"))
|
||||
return
|
||||
}
|
||||
|
||||
func (d *DetailHandle) PasswordProject() {
|
||||
if d.Post.PostPassword != "" {
|
||||
wpposts.PasswordProjectTitle(&d.Post)
|
||||
if d.GetPassword() != d.Post.PostPassword {
|
||||
wpposts.PasswdProjectContent(&d.Post)
|
||||
}
|
||||
}
|
||||
}
|
||||
func (h *Handle) GetDetailHandle() *DetailHandle {
|
||||
v, ok := h.C.Get("detailHandle")
|
||||
if !ok {
|
||||
vv := NewDetailHandle(h)
|
||||
h.C.Set("detailHandle", vv)
|
||||
return vv
|
||||
}
|
||||
return v.(*DetailHandle)
|
||||
}
|
||||
func (d *DetailHandle) CommentData() {
|
||||
d.ginH["totalCommentNum"] = 0
|
||||
d.ginH["totalCommentPage"] = 1
|
||||
d.ginH["commentPageNav"] = ""
|
||||
order := wpconfig.GetOption("comment_order")
|
||||
d.ginH["commentOrder"] = order
|
||||
d.Limit = str.ToInteger(wpconfig.GetOption("comments_per_page"), 5)
|
||||
pageComments := wpconfig.GetOption("page_comments")
|
||||
num, err := cachemanager.GetBy[int]("commentNumber", d.C, d.Post.Id, time.Second)
|
||||
if err != nil {
|
||||
d.SetErr(err, Low)
|
||||
return
|
||||
}
|
||||
if num < 1 {
|
||||
return
|
||||
}
|
||||
topNum, err := cachemanager.GetBy[int]("postTopCommentsNum", d.C, d.Post.Id, time.Second)
|
||||
if err != nil {
|
||||
d.SetErr(err, Low)
|
||||
return
|
||||
}
|
||||
d.TotalPage = number.DivideCeil(topNum, d.Limit)
|
||||
if !strings.Contains(d.C.Request.URL.Path, "comment-page") {
|
||||
defaultCommentsPage := wpconfig.GetOption("default_comments_page")
|
||||
if order == "desc" && defaultCommentsPage == "oldest" || order == "asc" && defaultCommentsPage == "newest" {
|
||||
d.C.AddParam("page", number.IntToString(d.TotalPage))
|
||||
}
|
||||
}
|
||||
d.Page = str.ToInteger(d.C.Param("page"), 1)
|
||||
d.ginH["currentPage"] = d.Page
|
||||
var key string
|
||||
if pageComments != "1" {
|
||||
key = number.IntToString(d.Post.Id)
|
||||
d.Limit = 0
|
||||
} else {
|
||||
key = fmt.Sprintf("%d-%d-%d", d.Post.Id, d.Page, d.Limit)
|
||||
}
|
||||
d.ginH["page_comments"] = pageComments
|
||||
d.ginH["totalCommentPage"] = d.TotalPage
|
||||
if d.TotalPage < d.Page {
|
||||
d.SetErr(errors.New("curren page above total page"), High)
|
||||
return
|
||||
}
|
||||
data, err := cache.PostTopLevelCommentIds(d.C, d.Post.Id, d.Page, d.Limit, topNum, order, key)
|
||||
if err != nil {
|
||||
d.SetErr(err, Low)
|
||||
return
|
||||
}
|
||||
d.TotalRaw = topNum
|
||||
d.ginH["totalCommentNum"] = num
|
||||
d.Comments = data
|
||||
}
|
||||
|
||||
func (d *DetailHandle) RenderComment() {
|
||||
if d.CommentRender == nil {
|
||||
d.CommentRender = plugins.CommentRender()
|
||||
}
|
||||
ableComment := true
|
||||
if d.Post.CommentStatus != "open" ||
|
||||
(d.Post.PostPassword != "" && d.GetPassword() != d.Post.PostPassword) {
|
||||
ableComment = false
|
||||
}
|
||||
d.ginH["showComment"] = ableComment
|
||||
d.ginH["comments"] = ""
|
||||
if len(d.Comments) < 0 || !ableComment {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
d.ginH["comments"], err = RenderComment(d.C, d.Page, d.CommentRender, d.Comments, 2*time.Second, d.IsHttps())
|
||||
if err != nil {
|
||||
d.SetErr(err, High)
|
||||
return
|
||||
}
|
||||
if d.CommentPageEle == nil {
|
||||
d.CommentPageEle = plugins.TwentyFifteenCommentPagination()
|
||||
}
|
||||
if wpconfig.GetOption("page_comments") == "1" && d.TotalPage > 1 {
|
||||
d.ginH["commentPageNav"] = pagination.Paginate(d.CommentPageEle, d.TotalRaw, d.Limit, d.Page, d.CommentStep, *d.C.Request.URL, d.IsHttps())
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DetailHandle) ContextPost() {
|
||||
prev, next, err := cache.GetContextPost(d.C, d.Post.Id, d.Post.PostDate)
|
||||
logs.IfError(err, "get pre and next post", d.Post.Id, d.Post.PostDate)
|
||||
d.ginH["next"] = next
|
||||
d.ginH["prev"] = prev
|
||||
}
|
||||
|
||||
func DetailRender(h *Handle) {
|
||||
if h.Stats != constraints.Ok {
|
||||
return
|
||||
}
|
||||
d := h.GetDetailHandle()
|
||||
d.PasswordProject()
|
||||
d.RenderComment()
|
||||
d.ginH["post"] = d.Post
|
||||
}
|
||||
|
||||
func Detail(h *Handle) {
|
||||
d := h.GetDetailHandle()
|
||||
err := d.BuildDetailData()
|
||||
if err != nil {
|
||||
d.SetErr(err, High)
|
||||
}
|
||||
h.SetData("scene", h.Scene())
|
||||
}
|
||||
|
||||
func ReplyCommentJs(h *Handle) {
|
||||
h.PushFooterScript(constraints.Detail, NewComponent("comment-reply.js", "", false, 10, func(h *Handle) string {
|
||||
reply := ""
|
||||
if h.GetDetailHandle().Post.CommentStatus == "open" && wpconfig.GetOption("thread_comments") == "1" {
|
||||
reply = `<script src='/wp-includes/js/comment-reply.min.js' id='comment-reply-js'></script>`
|
||||
}
|
||||
return reply
|
||||
}))
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package wp
|
||||
|
||||
const (
|
||||
None = iota
|
||||
Low
|
||||
High
|
||||
Fatal
|
||||
)
|
@ -1,121 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var exts = map[string]string{
|
||||
"jpg|jpeg|jpe": "image/jpeg",
|
||||
"gif": "image/gif",
|
||||
"png": "image/png",
|
||||
"bmp": "image/bmp",
|
||||
"tiff|tif": "image/tiff",
|
||||
"webp": "image/webp",
|
||||
"ico": "image/x-icon",
|
||||
"heic": "image/heic",
|
||||
"asf|asx": "video/x-ms-asf",
|
||||
"wmv": "video/x-ms-wmv",
|
||||
"wmx": "video/x-ms-wmx",
|
||||
"wm": "video/x-ms-wm",
|
||||
"avi": "video/avi",
|
||||
"divx": "video/divx",
|
||||
"flv": "video/x-flv",
|
||||
"mov|qt": "video/quicktime",
|
||||
"mpeg|mpg|mpe": "video/mpeg",
|
||||
"mp4|m4v": "video/mp4",
|
||||
"ogv": "video/ogg",
|
||||
"webm": "video/webm",
|
||||
"mkv": "video/x-matroska",
|
||||
"3gp|3gpp": "video/3gpp",
|
||||
"3g2|3gp2": "video/3gpp2",
|
||||
"txt|asc|c|cc|h|srt": "text/plain",
|
||||
"csv": "text/csv",
|
||||
"tsv": "text/tab-separated-values",
|
||||
"ics": "text/calendar",
|
||||
"rtx": "text/richtext",
|
||||
"css": "text/css",
|
||||
"htm|html": "text/html",
|
||||
"vtt": "text/vtt",
|
||||
"dfxp": "application/ttaf+xml",
|
||||
"mp3|m4a|m4b": "audio/mpeg",
|
||||
"aac": "audio/aac",
|
||||
"ra|ram": "audio/x-realaudio",
|
||||
"wav": "audio/wav",
|
||||
"ogg|oga": "audio/ogg",
|
||||
"flac": "audio/flac",
|
||||
"mid|midi": "audio/midi",
|
||||
"wma": "audio/x-ms-wma",
|
||||
"wax": "audio/x-ms-wax",
|
||||
"mka": "audio/x-matroska",
|
||||
"rtf": "application/rtf",
|
||||
"js": "application/javascript",
|
||||
"pdf": "application/pdf",
|
||||
"swf": "application/x-shockwave-flash",
|
||||
"class": "application/java",
|
||||
"tar": "application/x-tar",
|
||||
"zip": "application/zip",
|
||||
"gz|gzip": "application/x-gzip",
|
||||
"rar": "application/rar",
|
||||
"7z": "application/x-7z-compressed",
|
||||
"exe": "application/x-msdownload",
|
||||
"psd": "application/octet-stream",
|
||||
"xcf": "application/octet-stream",
|
||||
"doc": "application/msword",
|
||||
"pot|pps|ppt": "application/vnd.ms-powerpoint",
|
||||
"wri": "application/vnd.ms-write",
|
||||
"xla|xls|xlt|xlw": "application/vnd.ms-excel",
|
||||
"mdb": "application/vnd.ms-access",
|
||||
"mpp": "application/vnd.ms-project",
|
||||
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"docm": "application/vnd.ms-word.document.macroEnabled.12",
|
||||
"dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
||||
"dotm": "application/vnd.ms-word.template.macroEnabled.12",
|
||||
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12",
|
||||
"xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
||||
"xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||
"xltm": "application/vnd.ms-excel.template.macroEnabled.12",
|
||||
"xlam": "application/vnd.ms-excel.addin.macroEnabled.12",
|
||||
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
||||
"ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||
"ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
|
||||
"potx": "application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||
"potm": "application/vnd.ms-powerpoint.template.macroEnabled.12",
|
||||
"ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12",
|
||||
"sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide",
|
||||
"sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12",
|
||||
"onetoc|onetoc2|onetmp|onepkg": "application/onenote",
|
||||
"oxps": "application/oxps",
|
||||
"xps": "application/vnd.ms-xpsdocument",
|
||||
"odt": "application/vnd.oasis.opendocument.text",
|
||||
"odp": "application/vnd.oasis.opendocument.presentation",
|
||||
"ods": "application/vnd.oasis.opendocument.spreadsheet",
|
||||
"odg": "application/vnd.oasis.opendocument.graphics",
|
||||
"odc": "application/vnd.oasis.opendocument.chart",
|
||||
"odb": "application/vnd.oasis.opendocument.database",
|
||||
"odf": "application/vnd.oasis.opendocument.formula",
|
||||
"wp|wpd": "application/wordperfect",
|
||||
"key": "application/vnd.apple.keynote",
|
||||
"numbers": "application/vnd.apple.numbers",
|
||||
"pages": "application/vnd.apple.pages",
|
||||
}
|
||||
|
||||
var regs = func() map[string]*regexp.Regexp {
|
||||
var r = make(map[string]*regexp.Regexp)
|
||||
for k, v := range exts {
|
||||
r[v] = regexp.MustCompile(`(?i:\.(` + k + `)$)`)
|
||||
}
|
||||
return r
|
||||
}()
|
||||
|
||||
func GetMimeType(file string) string {
|
||||
ext := filepath.Ext(file)
|
||||
for mime, reg := range regs {
|
||||
if reg.FindString(ext) != "" {
|
||||
return mime
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
)
|
||||
|
||||
var fnMap = safety.NewMap[string, map[string]any]()
|
||||
var fnHook = safety.NewMap[string, map[string]any]()
|
||||
|
||||
func GetFn[T any](fnType string, name string) []T {
|
||||
v, ok := fnMap.Load(fnType)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
vv, ok := v[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return vv.([]T)
|
||||
}
|
||||
func GetFnHook[T any](fnType string, name string) []T {
|
||||
v, ok := fnHook.Load(fnType)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
vv, ok := v[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return vv.([]T)
|
||||
}
|
||||
|
||||
func PushFn[T any](fnType string, name string, fns ...T) error {
|
||||
v, ok := fnMap.Load(fnType)
|
||||
if !ok {
|
||||
v = make(map[string]any)
|
||||
fnMap.Store(fnType, v)
|
||||
v[name] = fns
|
||||
return nil
|
||||
}
|
||||
vv, ok := v[name]
|
||||
if !ok || vv == nil {
|
||||
v[name] = fns
|
||||
return nil
|
||||
}
|
||||
s, ok := vv.([]T)
|
||||
if ok {
|
||||
s = append(s, fns...)
|
||||
v[name] = s
|
||||
}
|
||||
return errors.New("error fn type")
|
||||
}
|
||||
|
||||
func PushFnHook[T any](fnType string, name string, fns ...T) error {
|
||||
v, ok := fnHook.Load(fnType)
|
||||
if !ok {
|
||||
v = make(map[string]any)
|
||||
fnHook.Store(fnType, v)
|
||||
v[name] = fns
|
||||
return nil
|
||||
}
|
||||
vv, ok := v[name]
|
||||
if !ok || vv == nil {
|
||||
v[name] = fns
|
||||
return nil
|
||||
}
|
||||
s, ok := vv.([]T)
|
||||
if ok {
|
||||
s = append(s, fns...)
|
||||
v[name] = s
|
||||
}
|
||||
return errors.New("error fn type")
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/plugins"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
"github.com/fthvgb1/wp-go/plugin/pagination"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IndexHandle struct {
|
||||
*Handle
|
||||
Param *IndexParams
|
||||
Posts []models.Posts
|
||||
pageEle pagination.Render
|
||||
TotalRows int
|
||||
postsPlugin PostsPlugin
|
||||
}
|
||||
|
||||
func (h *Handle) GetIndexHandle() *IndexHandle {
|
||||
v, ok := h.C.Get("indexHandle")
|
||||
if !ok {
|
||||
vv := NewIndexHandle(h)
|
||||
h.C.Set("indexHandle", vv)
|
||||
return vv
|
||||
}
|
||||
return v.(*IndexHandle)
|
||||
}
|
||||
|
||||
func (i *IndexHandle) ListPlugin() func(*Handle, *models.Posts) {
|
||||
return i.postsPlugin
|
||||
}
|
||||
|
||||
func (i *IndexHandle) SetListPlugin(listPlugin func(*Handle, *models.Posts)) {
|
||||
i.postsPlugin = listPlugin
|
||||
}
|
||||
|
||||
func (i *IndexHandle) PageEle() pagination.Render {
|
||||
return i.pageEle
|
||||
}
|
||||
|
||||
func (i *IndexHandle) SetPageEle(pageEle pagination.Render) {
|
||||
i.pageEle = pageEle
|
||||
}
|
||||
|
||||
func NewIndexHandle(handle *Handle) *IndexHandle {
|
||||
return &IndexHandle{Handle: handle}
|
||||
}
|
||||
|
||||
func PushIndexHandler(pipeScene string, h *Handle, call HandleCall) {
|
||||
h.PushHandlers(pipeScene, call, constraints.Home,
|
||||
constraints.Category, constraints.Search, constraints.Tag,
|
||||
constraints.Archive, constraints.Author,
|
||||
)
|
||||
}
|
||||
|
||||
func (i *IndexHandle) ParseIndex(parm *IndexParams) (err error) {
|
||||
i.Param = parm
|
||||
switch i.scene {
|
||||
case constraints.Search:
|
||||
i.Param.ParseSearch()
|
||||
case constraints.Category:
|
||||
err = i.Param.ParseCategory()
|
||||
case constraints.Tag:
|
||||
err = i.Param.ParseTag()
|
||||
case constraints.Archive:
|
||||
err = i.Param.ParseArchive()
|
||||
case constraints.Author:
|
||||
err = i.Param.ParseAuthor()
|
||||
}
|
||||
if err != nil {
|
||||
i.Stats = constraints.ParamError
|
||||
return
|
||||
}
|
||||
i.Param.ParseParams()
|
||||
i.Param.CacheKey = i.Param.getSearchKey()
|
||||
i.ginH["title"] = i.Param.getTitle()
|
||||
i.ginH["search"] = i.Param.Search
|
||||
i.ginH["header"] = i.Param.Header
|
||||
return
|
||||
}
|
||||
|
||||
func (i *IndexHandle) GetIndexData() (posts []models.Posts, totalRaw int, err error) {
|
||||
|
||||
q := &model.QueryCondition{
|
||||
Where: i.Param.Where,
|
||||
Order: model.SqlBuilder{{i.Param.OrderBy, i.Param.Order}},
|
||||
Join: i.Param.Join,
|
||||
In: [][]any{i.Param.PostType, i.Param.PostStatus},
|
||||
}
|
||||
switch i.scene {
|
||||
case constraints.Home, constraints.Category, constraints.Tag, constraints.Author:
|
||||
|
||||
posts, totalRaw, err = cache.PostLists(i.C, i.Param.CacheKey, q, i.Param.Page, i.Param.PageSize)
|
||||
if i.scene == constraints.Home && i.Param.Page == 1 {
|
||||
i.MarkSticky(&posts)
|
||||
}
|
||||
|
||||
case constraints.Search:
|
||||
|
||||
posts, totalRaw, err = cache.SearchPost(i.C, i.Param.CacheKey, q, i.Param.Page, i.Param.PageSize)
|
||||
|
||||
case constraints.Archive:
|
||||
i.ginH["archiveYear"] = i.Param.Year
|
||||
i.ginH["archiveMonth"] = strings.TrimLeft(i.Param.Month, "0")
|
||||
posts, totalRaw, err = cache.GetMonthPostIds(i.C, i.Param.Year, i.Param.Month, i.Param.Page, i.Param.PageSize, i.Param.Order)
|
||||
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func (i *IndexHandle) Pagination() {
|
||||
if i.pageEle == nil {
|
||||
i.pageEle = plugins.TwentyFifteenPagination()
|
||||
}
|
||||
q := i.C.Request.URL.Query().Encode()
|
||||
if q != "" {
|
||||
q = fmt.Sprintf("?%s", q)
|
||||
}
|
||||
i.ginH["pagination"] = pagination.Paginate(i.pageEle, i.TotalRows, i.Param.PageSize, i.Param.Page, i.Param.PaginationStep, *i.C.Request.URL, i.IsHttps())
|
||||
|
||||
}
|
||||
|
||||
func (i *IndexHandle) BuildIndexData() (err error) {
|
||||
if i.Param == nil {
|
||||
i.Param = NewIndexParams(i.C)
|
||||
}
|
||||
err = i.ParseIndex(i.Param)
|
||||
if err != nil {
|
||||
i.Stats = constraints.ParamError
|
||||
return
|
||||
}
|
||||
posts, totalRows, err := i.GetIndexData()
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
i.Stats = constraints.Error404
|
||||
return
|
||||
}
|
||||
i.Posts = posts
|
||||
i.TotalRows = totalRows
|
||||
i.ginH["totalPage"] = number.DivideCeil(totalRows, i.Param.PageSize)
|
||||
return
|
||||
}
|
||||
|
||||
var GetPostsPlugin = reload.BuildValFnWithAnyParams("postPlugins", UsePostsPlugins)
|
||||
|
||||
func (i *IndexHandle) ExecPostsPlugin() {
|
||||
fn := i.postsPlugin
|
||||
if fn == nil {
|
||||
fn = GetPostsPlugin()
|
||||
}
|
||||
for j := range i.Posts {
|
||||
fn(i.Handle, &i.Posts[j])
|
||||
}
|
||||
}
|
||||
|
||||
func IndexRender(h *Handle) {
|
||||
i := h.GetIndexHandle()
|
||||
i.ExecPostsPlugin()
|
||||
i.Pagination()
|
||||
i.ginH["posts"] = i.Posts
|
||||
}
|
||||
|
||||
func Index(h *Handle) {
|
||||
i := h.GetIndexHandle()
|
||||
err := i.BuildIndexData()
|
||||
if err != nil {
|
||||
i.SetErr(err, High)
|
||||
}
|
||||
h.SetData("scene", h.Scene())
|
||||
}
|
||||
|
||||
func (i *IndexHandle) MarkSticky(posts *[]models.Posts) {
|
||||
a := GetStickPosts(i.Handle)
|
||||
if len(a) < 1 {
|
||||
return
|
||||
}
|
||||
m := GetStickMapPosts(i.Handle)
|
||||
*posts = append(a, slice.Filter(*posts, func(post models.Posts, _ int) bool {
|
||||
_, ok := m[post.Id]
|
||||
return !ok
|
||||
})...)
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/plugins"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wpposts"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
)
|
||||
|
||||
type PostsPlugin func(*Handle, *models.Posts)
|
||||
|
||||
func PostsPlugins(initial PostsPlugin, calls ...func(PostsPlugin, *Handle, *models.Posts)) PostsPlugin {
|
||||
return slice.ReverseReduce(calls, func(t func(PostsPlugin, *Handle, *models.Posts), r PostsPlugin) PostsPlugin {
|
||||
return func(handle *Handle, posts *models.Posts) {
|
||||
t(r, handle, posts)
|
||||
}
|
||||
}, initial)
|
||||
}
|
||||
|
||||
var pluginFns = reload.Vars(map[string]func(PostsPlugin, *Handle, *models.Posts){
|
||||
"passwordProject": PasswordProject,
|
||||
"digest": Digest,
|
||||
}, "list-post-plugins-fns")
|
||||
|
||||
func (h *Handle) PushPostsPlugin(name string, fn func(PostsPlugin, *Handle, *models.Posts)) {
|
||||
m := pluginFns.Load()
|
||||
m[name] = fn
|
||||
}
|
||||
|
||||
// PasswordProject 标题和内容密码保护
|
||||
func PasswordProject(next PostsPlugin, h *Handle, post *models.Posts) {
|
||||
r := post
|
||||
if post.PostPassword != "" {
|
||||
wpposts.PasswordProjectTitle(r)
|
||||
if h.GetPassword() != post.PostPassword {
|
||||
wpposts.PasswdProjectContent(r)
|
||||
return
|
||||
}
|
||||
}
|
||||
next(h, r)
|
||||
}
|
||||
|
||||
// Digest 生成摘要
|
||||
func Digest(next PostsPlugin, h *Handle, post *models.Posts) {
|
||||
if post.PostExcerpt != "" {
|
||||
plugins.PostExcerpt(post)
|
||||
} else {
|
||||
plugins.Digest(h.C, post, config.GetConfig().DigestWordCount)
|
||||
}
|
||||
next(h, post)
|
||||
}
|
||||
|
||||
var ordinaryPlugin = reload.Vars([]PostsPlugin{}, "ordinaryPlugin")
|
||||
|
||||
func (h *Handle) PushPostPlugin(plugin ...PostsPlugin) {
|
||||
p := ordinaryPlugin.Load()
|
||||
p = append(p, plugin...)
|
||||
ordinaryPlugin.Store(p)
|
||||
}
|
||||
|
||||
func PostPlugin(calls ...PostsPlugin) PostsPlugin {
|
||||
return func(h *Handle, posts *models.Posts) {
|
||||
for _, call := range calls {
|
||||
call(h, posts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UsePostsPlugins(_ ...any) PostsPlugin {
|
||||
m := pluginFns.Load()
|
||||
pluginss := slice.FilterAndMap(config.GetConfig().ListPagePlugins, func(t string) (func(PostsPlugin, *Handle, *models.Posts), bool) {
|
||||
f, ok := m[t]
|
||||
return f, ok
|
||||
})
|
||||
slice.Unshift(&pluginss, PasswordProject)
|
||||
return PostsPlugins(PostPlugin(ordinaryPlugin.Load()...), pluginss...)
|
||||
}
|
||||
|
||||
func ListPostPlugins() map[string]func(PostsPlugin, *Handle, *models.Posts) {
|
||||
return maps.Copy(pluginFns.Load())
|
||||
}
|
||||
|
||||
func ProjectTitle(t models.Posts) models.Posts {
|
||||
if t.PostPassword != "" {
|
||||
wpposts.PasswordProjectTitle(&t)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func GetListPostPlugins(name []string, m map[string]func(PostsPlugin, *Handle, *models.Posts)) []func(PostsPlugin, *Handle, *models.Posts) {
|
||||
return slice.FilterAndMap(name, func(t string) (func(PostsPlugin, *Handle, *models.Posts), bool) {
|
||||
v, ok := m[t]
|
||||
if ok {
|
||||
return v, true
|
||||
}
|
||||
return nil, false
|
||||
})
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/route"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var plainRouteParam = reload.Vars([]Plain{
|
||||
{
|
||||
Action: "p",
|
||||
Param: map[string]string{
|
||||
"p": "id",
|
||||
"cpage": "page",
|
||||
},
|
||||
Scene: constraints.Detail,
|
||||
},
|
||||
{
|
||||
Action: "s",
|
||||
Scene: constraints.Search,
|
||||
},
|
||||
{
|
||||
Scene: constraints.Category,
|
||||
Fn: func(h *wp.Handle) bool {
|
||||
c, ok := widget.IsCategory(h)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
h.C.AddParam("category", c.Name)
|
||||
h.C.AddParam("page", h.C.Query("paged"))
|
||||
return true
|
||||
},
|
||||
},
|
||||
{
|
||||
Scene: constraints.Tag,
|
||||
Action: "tag",
|
||||
Param: map[string]string{
|
||||
"tag": "tag",
|
||||
"paged": "page",
|
||||
},
|
||||
},
|
||||
{
|
||||
Scene: constraints.Archive,
|
||||
Fn: func(h *wp.Handle) bool {
|
||||
m := h.C.Query("m")
|
||||
if m == "" {
|
||||
return false
|
||||
}
|
||||
t, err := time.Parse("200601", m)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
h.C.AddParam("year", strconv.Itoa(t.Year()))
|
||||
h.C.AddParam("month", number.IntToString(t.Month()))
|
||||
h.C.AddParam("page", h.C.Query("paged"))
|
||||
return true
|
||||
},
|
||||
},
|
||||
{
|
||||
Scene: constraints.Author,
|
||||
Fn: func(h *wp.Handle) bool {
|
||||
u := h.C.Query("author")
|
||||
if u == "" {
|
||||
return false
|
||||
}
|
||||
users := GetUsersIds(h)
|
||||
name, ok := users[str.ToInteger[uint64](u, 0)]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
h.C.AddParam("author", name)
|
||||
h.C.AddParam("page", h.C.Query("paged"))
|
||||
return true
|
||||
},
|
||||
},
|
||||
}, "plainRouteParam")
|
||||
|
||||
var GetUsersIds = reload.BuildValFnWithConfirm("usersIds", func(h *wp.Handle) (map[uint64]string, bool) {
|
||||
users, err := cache.GetAllUsername(h.C)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return maps.Flip(users), true
|
||||
}, 10)
|
||||
|
||||
func SetExplainRouteParam(p []Plain) {
|
||||
plainRouteParam.Store(p)
|
||||
}
|
||||
func GetExplainRouteParam() []Plain {
|
||||
return plainRouteParam.Load()
|
||||
}
|
||||
func PushExplainRouteParam(explain ...Plain) {
|
||||
v := plainRouteParam.Load()
|
||||
v = append(v, explain...)
|
||||
plainRouteParam.Store(v)
|
||||
}
|
||||
|
||||
type Plain struct {
|
||||
Action string
|
||||
Param map[string]string
|
||||
Scene string
|
||||
Fn func(h *wp.Handle) bool
|
||||
}
|
||||
|
||||
func MixWithPlain(h *wp.Handle) {
|
||||
for _, explain := range plainRouteParam.Load() {
|
||||
if explain.Action == "" && explain.Fn == nil {
|
||||
continue
|
||||
}
|
||||
if explain.Fn != nil {
|
||||
if !explain.Fn(h) {
|
||||
continue
|
||||
}
|
||||
if explain.Scene != "" {
|
||||
h.SetScene(explain.Scene)
|
||||
}
|
||||
wp.Run(h, nil)
|
||||
h.Abort()
|
||||
return
|
||||
}
|
||||
if explain.Scene == "" {
|
||||
continue
|
||||
}
|
||||
q := h.C.Query(explain.Action)
|
||||
if q == "" {
|
||||
continue
|
||||
}
|
||||
h.SetScene(explain.Scene)
|
||||
for query, param := range explain.Param {
|
||||
h.C.AddParam(param, h.C.Query(query))
|
||||
}
|
||||
wp.Run(h, nil)
|
||||
h.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func ShowPreComment(h *wp.Handle) {
|
||||
v, ok := cache.NewCommentCache().Get(h.C, h.C.Request.URL.RawQuery)
|
||||
if ok {
|
||||
h.C.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
h.C.Writer.WriteHeader(http.StatusOK)
|
||||
_, _ = h.C.Writer.Write([]byte(v))
|
||||
h.Abort()
|
||||
}
|
||||
}
|
||||
|
||||
func CommonMiddleware(h *wp.Handle) {
|
||||
h.PushHandler(constraints.PipeMiddleware, constraints.Home,
|
||||
wp.NewHandleFn(MixWithPlain, 100, "middleware.MixWithPlain"),
|
||||
)
|
||||
h.PushHandler(constraints.PipeMiddleware, constraints.Detail,
|
||||
wp.NewHandleFn(ShowPreComment, 100, "middleware.ShowPreComment"),
|
||||
)
|
||||
h.PushHandler(constraints.PipeMiddleware, constraints.NoRoute,
|
||||
wp.NewHandleFn(route.ResolveRoute, 100, "route.ResolveRoute"),
|
||||
)
|
||||
}
|
@ -1,247 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
)
|
||||
|
||||
type HandlePipeFn[T any] func(HandleFn[T], T)
|
||||
|
||||
type Pipe struct {
|
||||
Name string
|
||||
Order float64
|
||||
Fn HandlePipeFn[*Handle]
|
||||
}
|
||||
|
||||
func NewPipe(name string, order float64, fn HandlePipeFn[*Handle]) Pipe {
|
||||
return Pipe{Name: name, Order: order, Fn: fn}
|
||||
}
|
||||
|
||||
// HandlePipe 方便把功能写在其它包里
|
||||
func HandlePipe[T any](initial func(T), fns ...HandlePipeFn[T]) HandleFn[T] {
|
||||
return slice.ReverseReduce(fns, func(next HandlePipeFn[T], f HandleFn[T]) HandleFn[T] {
|
||||
return func(t T) {
|
||||
next(f, t)
|
||||
}
|
||||
}, initial)
|
||||
}
|
||||
|
||||
func (h *Handle) PushPipe(scene string, pipes ...Pipe) error {
|
||||
return PushFn("pipe", scene, pipes...)
|
||||
}
|
||||
func (h *Handle) PushPipeHook(scene string, pipes ...func(Pipe) (Pipe, bool)) error {
|
||||
return PushFnHook("pipeHook", scene, pipes...)
|
||||
}
|
||||
|
||||
func (h *Handle) DeletePipe(scene, pipeName string) error {
|
||||
return PushFnHook("pipeHook", scene, func(pipe Pipe) (Pipe, bool) {
|
||||
return pipe, pipeName != pipe.Name
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handle) ReplacePipe(scene, pipeName string, pipe Pipe) error {
|
||||
return PushFnHook("pipeHook", scene, func(p Pipe) (Pipe, bool) {
|
||||
if pipeName == p.Name {
|
||||
p = pipe
|
||||
}
|
||||
return p, true
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handle) PushHandler(pipScene string, scene string, fns ...HandleCall) {
|
||||
v, ok := handlerss.Load(pipScene)
|
||||
if !ok {
|
||||
v = make(map[string][]HandleCall)
|
||||
}
|
||||
v[scene] = append(v[scene], fns...)
|
||||
handlerss.Store(pipScene, v)
|
||||
}
|
||||
|
||||
func (h *Handle) PushRender(statsOrScene string, fns ...HandleCall) {
|
||||
h.PushHandler(constraints.PipeRender, statsOrScene, fns...)
|
||||
}
|
||||
func (h *Handle) PushDataHandler(scene string, fns ...HandleCall) {
|
||||
h.PushHandler(constraints.PipeData, scene, fns...)
|
||||
}
|
||||
|
||||
func BuildHandlers(pipeScene string, keyFn func(*Handle, string) string,
|
||||
handleHookFn func(*Handle, string,
|
||||
map[string][]func(HandleCall) (HandleCall, bool),
|
||||
map[string][]HandleCall) []HandleCall) func(HandleFn[*Handle], *Handle) {
|
||||
|
||||
pipeHandlerFn := reload.BuildMapFn[string]("pipeHandlers", BuildHandler(pipeScene, keyFn, handleHookFn))
|
||||
|
||||
return func(next HandleFn[*Handle], h *Handle) {
|
||||
key := keyFn(h, pipeScene)
|
||||
handlers := pipeHandlerFn(key, h)
|
||||
for _, handler := range handlers {
|
||||
handler.Fn(h)
|
||||
if h.abort {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !h.stopPipe {
|
||||
next(h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BuildHandler(pipeScene string, keyFn func(*Handle, string) string,
|
||||
handleHook func(*Handle, string,
|
||||
map[string][]func(HandleCall) (HandleCall, bool),
|
||||
map[string][]HandleCall) []HandleCall) func(*Handle) []HandleCall {
|
||||
|
||||
return func(h *Handle) []HandleCall {
|
||||
key := keyFn(h, pipeScene)
|
||||
mut := reload.GetGlobeMutex()
|
||||
mut.Lock()
|
||||
pipeHookers, _ := handleHooks.Load(pipeScene)
|
||||
pipeHandlers, _ := handlerss.Load(pipeScene)
|
||||
mut.Unlock()
|
||||
calls := handleHook(h, key, pipeHookers, pipeHandlers)
|
||||
slice.SimpleSort(calls, slice.DESC, func(t HandleCall) float64 {
|
||||
return t.Order
|
||||
})
|
||||
return calls
|
||||
}
|
||||
}
|
||||
|
||||
func HookHandles(hooks map[string][]func(HandleCall) (HandleCall, bool),
|
||||
handlers map[string][]HandleCall, sceneOrStats ...string) []HandleCall {
|
||||
|
||||
hookedHandlers := slice.FilterAndMap(sceneOrStats, func(k string) ([]HandleCall, bool) {
|
||||
r := handlers[k]
|
||||
for _, hook := range hooks[k] {
|
||||
r = slice.FilterAndMap(r, hook)
|
||||
}
|
||||
return r, true
|
||||
})
|
||||
return slice.Decompress(hookedHandlers)
|
||||
}
|
||||
|
||||
func PipeKey(h *Handle, pipScene string) string {
|
||||
key := str.Join("pipekey", "-", pipScene, "-", h.scene, "-", h.Stats)
|
||||
return h.DoActionFilter("pipeKey", key, pipScene)
|
||||
}
|
||||
|
||||
var pipeInitFn = reload.BuildMapFn[string]("pipeInit", BuildPipe)
|
||||
|
||||
func Run(h *Handle, conf func(*Handle)) {
|
||||
if !h.isInited {
|
||||
InitHandle(conf, h)
|
||||
}
|
||||
pipeInitFn(h.scene, h.scene)(h)
|
||||
}
|
||||
|
||||
func BuildPipe(scene string) func(*Handle) {
|
||||
pipees := GetFn[Pipe]("pipe", constraints.AllScene)
|
||||
pipees = append(pipees, GetFn[Pipe]("pipe", scene)...)
|
||||
pipes := slice.FilterAndMap(pipees, func(pipe Pipe) (Pipe, bool) {
|
||||
var ok bool
|
||||
mut := reload.GetGlobeMutex()
|
||||
mut.Lock()
|
||||
hooks := GetFnHook[func(Pipe) (Pipe, bool)]("pipeHook", constraints.AllScene)
|
||||
hooks = append(hooks, GetFnHook[func(Pipe) (Pipe, bool)]("pipeHook", scene)...)
|
||||
mut.Unlock()
|
||||
for _, fn := range hooks {
|
||||
pipe, ok = fn(pipe)
|
||||
if !ok {
|
||||
return pipe, false
|
||||
}
|
||||
}
|
||||
return pipe, pipe.Fn != nil
|
||||
})
|
||||
slice.SimpleSort(pipes, slice.DESC, func(t Pipe) float64 {
|
||||
return t.Order
|
||||
})
|
||||
|
||||
arr := slice.Map(pipes, func(t Pipe) HandlePipeFn[*Handle] {
|
||||
return t.Fn
|
||||
})
|
||||
return HandlePipe(NothingToDo, arr...)
|
||||
}
|
||||
|
||||
func MiddlewareKey(h *Handle, pipScene string) string {
|
||||
return h.DoActionFilter("middleware", str.Join("pipe-middleware-", h.scene), pipScene)
|
||||
}
|
||||
|
||||
func PipeMiddlewareHandle(h *Handle, key string,
|
||||
hooks map[string][]func(HandleCall) (HandleCall, bool),
|
||||
handlers map[string][]HandleCall) []HandleCall {
|
||||
hookedHandles := HookHandles(hooks, handlers, h.scene, constraints.AllScene)
|
||||
return h.PipeHandleHook("PipeMiddlewareHandle", hookedHandles, handlers, key)
|
||||
}
|
||||
|
||||
func PipeDataHandle(h *Handle, key string,
|
||||
hooks map[string][]func(HandleCall) (HandleCall, bool),
|
||||
handlers map[string][]HandleCall) []HandleCall {
|
||||
hookedHandles := HookHandles(hooks, handlers, h.scene, constraints.AllScene)
|
||||
return h.PipeHandleHook("PipeDataHandle", hookedHandles, handlers, key)
|
||||
}
|
||||
|
||||
func PipeRender(h *Handle, key string,
|
||||
hooks map[string][]func(HandleCall) (HandleCall, bool),
|
||||
handlers map[string][]HandleCall) []HandleCall {
|
||||
hookedHandles := HookHandles(hooks, handlers, h.scene, h.Stats, constraints.AllScene, constraints.AllStats)
|
||||
return h.PipeHandleHook("PipeRender", hookedHandles, handlers, key)
|
||||
}
|
||||
|
||||
// DeleteHandle 写插件的时候用
|
||||
func (h *Handle) DeleteHandle(pipeScene, scene, name string) {
|
||||
v, ok := handleHooks.Load(pipeScene)
|
||||
if !ok {
|
||||
v = make(map[string][]func(HandleCall) (HandleCall, bool))
|
||||
}
|
||||
v[scene] = append(v[scene], func(call HandleCall) (HandleCall, bool) {
|
||||
return call, name != call.Name
|
||||
})
|
||||
handleHooks.Store(pipeScene, v)
|
||||
}
|
||||
|
||||
// ReplaceHandle 写插件的时候用
|
||||
func (h *Handle) ReplaceHandle(pipeScene, scene, name string, fn HandleFn[*Handle]) {
|
||||
v, ok := handleHooks.Load(pipeScene)
|
||||
if !ok {
|
||||
v = make(map[string][]func(HandleCall) (HandleCall, bool))
|
||||
}
|
||||
v[scene] = append(v[scene], func(call HandleCall) (HandleCall, bool) {
|
||||
if name == call.Name {
|
||||
call.Fn = fn
|
||||
}
|
||||
return call, true
|
||||
})
|
||||
handleHooks.Store(pipeScene, v)
|
||||
}
|
||||
|
||||
// HookHandle 写插件的时候用
|
||||
func (h *Handle) HookHandle(pipeScene, scene string, hook func(HandleCall) (HandleCall, bool)) {
|
||||
v, ok := handleHooks.Load(pipeScene)
|
||||
if !ok {
|
||||
v = make(map[string][]func(HandleCall) (HandleCall, bool))
|
||||
}
|
||||
v[scene] = append(v[scene], hook)
|
||||
handleHooks.Store(pipeScene, v)
|
||||
}
|
||||
|
||||
func (h *Handle) PushPipeHandleHook(name string, fn ...func([]HandleCall) []HandleCall) error {
|
||||
return PushFnHook("pipeHandleHook", name, fn...)
|
||||
}
|
||||
|
||||
func (h *Handle) PipeHandleHook(name string, calls []HandleCall, m map[string][]HandleCall, key string) []HandleCall {
|
||||
fn := GetFnHook[func(*Handle, []HandleCall, map[string][]HandleCall, string) []HandleCall]("pipeHandleHook", name)
|
||||
return slice.Reduce(fn, func(t func(*Handle, []HandleCall, map[string][]HandleCall, string) []HandleCall, r []HandleCall) []HandleCall {
|
||||
return t(h, r, m, key)
|
||||
}, calls)
|
||||
}
|
||||
|
||||
func InitPipe(h *Handle) {
|
||||
h.PushPipe(constraints.AllScene, NewPipe(constraints.PipeMiddleware, 300,
|
||||
BuildHandlers(constraints.PipeMiddleware, MiddlewareKey, PipeMiddlewareHandle)))
|
||||
|
||||
h.PushPipe(constraints.AllScene, NewPipe(constraints.PipeData, 200,
|
||||
BuildHandlers(constraints.PipeData, PipeKey, PipeDataHandle)))
|
||||
h.PushPipe(constraints.AllScene, NewPipe(constraints.PipeRender, 100,
|
||||
BuildHandlers(constraints.PipeRender, PipeKey, PipeRender)))
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Route
|
||||
// Type value equal const or reg
|
||||
type Route struct {
|
||||
Path string
|
||||
Scene string
|
||||
Method []string
|
||||
Type string
|
||||
}
|
||||
|
||||
var routeHook []func(Route) (Route, bool)
|
||||
|
||||
var regRoutes *safety.Map[string, *regexp.Regexp]
|
||||
var routes = func() *safety.Map[string, Route] {
|
||||
r := safety.NewMap[string, Route]()
|
||||
reload.Append(func() {
|
||||
r.Flush()
|
||||
regRoutes.Flush()
|
||||
}, "wp-routers")
|
||||
regRoutes = safety.NewMap[string, *regexp.Regexp]()
|
||||
return r
|
||||
}()
|
||||
|
||||
// PushRoute path can be const or regex string
|
||||
//
|
||||
// eg: `(?P<controller>\w+)/(?P<method>\w+)`, route.Route{
|
||||
// Path: `(?P<controller>\w+)/(?P<method>\w+)`,
|
||||
// Scene: constraints.Home,
|
||||
// Method: []string{"GET"},
|
||||
// Type: "reg",
|
||||
// }
|
||||
func PushRoute(path string, route Route) error {
|
||||
if route.Type == "const" {
|
||||
routes.Store(path, route)
|
||||
return nil
|
||||
}
|
||||
re, err := regexp.Compile(route.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
regRoutes.Store(path, re)
|
||||
routes.Store(path, route)
|
||||
return err
|
||||
}
|
||||
|
||||
func Delete(path string) {
|
||||
routeHook = append(routeHook, func(route Route) (Route, bool) {
|
||||
return route, route.Path != path
|
||||
})
|
||||
}
|
||||
|
||||
func Replace(path string, route Route) {
|
||||
routeHook = append(routeHook, func(r Route) (Route, bool) {
|
||||
return route, path == route.Path
|
||||
})
|
||||
}
|
||||
|
||||
func Hook(path string, fn func(Route) Route) {
|
||||
routeHook = append(routeHook, func(r Route) (Route, bool) {
|
||||
if path == r.Path {
|
||||
r = fn(r)
|
||||
}
|
||||
return r, path == r.Path
|
||||
})
|
||||
}
|
||||
|
||||
var RegRouteFn = reload.BuildValFnWithAnyParams("regexRoute", RegRouteHook)
|
||||
|
||||
func RegRouteHook(_ ...any) func() (map[string]Route, map[string]*regexp.Regexp) {
|
||||
m := map[string]Route{}
|
||||
rrs := map[string]*regexp.Regexp{}
|
||||
routes.Range(func(key string, value Route) bool {
|
||||
vv, _ := regRoutes.Load(key)
|
||||
if len(routeHook) > 0 {
|
||||
for _, fn := range routeHook {
|
||||
v, ok := fn(value)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
m[v.Path] = v
|
||||
if v.Type != "reg" {
|
||||
continue
|
||||
}
|
||||
if v.Path != key {
|
||||
vvv, err := regexp.Compile(v.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vv = vvv
|
||||
}
|
||||
rrs[v.Path] = vv
|
||||
}
|
||||
} else {
|
||||
m[key] = value
|
||||
rrs[key] = vv
|
||||
}
|
||||
return true
|
||||
})
|
||||
return func() (map[string]Route, map[string]*regexp.Regexp) {
|
||||
return m, rrs
|
||||
}
|
||||
}
|
||||
func ResolveRoute(h *wp.Handle) {
|
||||
requestURI := h.C.Request.RequestURI
|
||||
rs, rrs := RegRouteFn()()
|
||||
v, ok := rs[requestURI]
|
||||
if ok && slice.IsContained(v.Method, h.C.Request.Method) {
|
||||
h.SetScene(v.Scene)
|
||||
wp.Run(h, nil)
|
||||
h.Abort()
|
||||
return
|
||||
}
|
||||
for path, reg := range rrs {
|
||||
r := reg.FindStringSubmatch(requestURI)
|
||||
if len(r) < 1 {
|
||||
return
|
||||
}
|
||||
rr := rs[path]
|
||||
if slice.IsContained(rr.Method, h.C.Request.Method) {
|
||||
h.SetScene(rr.Scene)
|
||||
for i, name := range reg.SubexpNames() {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
h.C.AddParam(name, r[i])
|
||||
}
|
||||
h.C.Set("regRoute", reg)
|
||||
h.C.Set("regRouteRes", r)
|
||||
wp.Run(h, nil)
|
||||
h.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
h.C.Status(http.StatusNotFound)
|
||||
h.Abort()
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/elliotchance/phpserialize"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
)
|
||||
|
||||
var GetStickPosts = reload.BuildValFnWithConfirm("stickPostsSlice", ParseStickPosts)
|
||||
|
||||
func ParseStickPosts(h *Handle) (r []models.Posts, ok bool) {
|
||||
v := wpconfig.GetOption("sticky_posts")
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
array, err := phpserialize.UnmarshalIndexedArray([]byte(v))
|
||||
if err != nil {
|
||||
logs.Error(err, "解析option sticky_posts错误", v)
|
||||
return
|
||||
}
|
||||
r = slice.FilterAndMap(array, func(t any) (models.Posts, bool) {
|
||||
id := str.ToInt[uint64](fmt.Sprintf("%v", t))
|
||||
post, err := cache.GetPostById(h.C, id)
|
||||
post.IsSticky = true
|
||||
return post, err == nil
|
||||
})
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
var GetStickMapPosts = reload.BuildValFn("stickPostsMap", StickMapPosts)
|
||||
|
||||
func StickMapPosts(h *Handle) map[uint64]models.Posts {
|
||||
return slice.SimpleToMap(GetStickPosts(h), func(v models.Posts) uint64 {
|
||||
return v.Id
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handle) IsStick(id uint64) bool {
|
||||
_, ok := GetStickMapPosts(h)[id]
|
||||
return ok
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
{{define "common/head"}}
|
||||
|
||||
{{ callFuncString .calComponent "headScript"}}
|
||||
|
||||
{{if .externHead}}
|
||||
{{.externHead|unescaped}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "common/footer"}}
|
||||
{{ callFuncString .calComponent "footerScript"}}
|
||||
{{if .externFooter}}
|
||||
{{.externFooter|unescaped}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{define "common/sidebarWidget"}}
|
||||
{{ callFuncString .calComponent "sidebarsWidgets"}}
|
||||
{{end}}
|
||||
|
||||
{{define "common/colophon"}}
|
||||
{{if .colophon}}
|
||||
{{.colophon|unescaped}}
|
||||
{{else}}
|
||||
<footer id="colophon" class="site-footer">
|
||||
<div class="site-info">
|
||||
<a href="https://github.com/fthvgb1/wp-go" class="imprint">自豪地采用 wp-go</a>
|
||||
</div>
|
||||
</footer>
|
||||
{{end}}
|
||||
{{end}}
|
@ -1,305 +0,0 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle/apply"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Handle struct {
|
||||
C *gin.Context
|
||||
theme string
|
||||
isInited bool
|
||||
Session sessions.Session
|
||||
ginH gin.H
|
||||
password string
|
||||
scene string
|
||||
Code int
|
||||
Stats string
|
||||
templ string
|
||||
themeMods wpconfig.ThemeMods
|
||||
err error
|
||||
errLevel int8
|
||||
abort bool
|
||||
stopPipe bool
|
||||
}
|
||||
|
||||
var handlerss = safety.NewMap[string, map[string][]HandleCall]()
|
||||
var handleHooks = safety.NewMap[string, map[string][]func(HandleCall) (HandleCall, bool)]()
|
||||
|
||||
func (h *Handle) Theme() string {
|
||||
return h.theme
|
||||
}
|
||||
|
||||
func (h *Handle) GinH() gin.H {
|
||||
return h.ginH
|
||||
}
|
||||
|
||||
func (h *Handle) SetScene(scene string) {
|
||||
h.scene = scene
|
||||
}
|
||||
|
||||
func (h *Handle) Components() *safety.Map[string, map[string][]Components[string]] {
|
||||
return handleComponents
|
||||
}
|
||||
|
||||
func (h *Handle) ComponentHook() *safety.Map[string, map[string][]func(Components[string]) (Components[string], bool)] {
|
||||
return handleComponentHook
|
||||
}
|
||||
|
||||
func (h *Handle) Handlers() *safety.Map[string, map[string][]HandleCall] {
|
||||
return handlerss
|
||||
}
|
||||
|
||||
func (h *Handle) HandleHook() *safety.Map[string, map[string][]func(HandleCall) (HandleCall, bool)] {
|
||||
return handleHooks
|
||||
}
|
||||
|
||||
type HandlePlugins map[string]HandleFn[*Handle]
|
||||
|
||||
// Components Order 为执行顺序,降序执行
|
||||
type Components[T any] struct {
|
||||
Name string
|
||||
Val T
|
||||
Fn func(*Handle) T
|
||||
Order float64
|
||||
Cached bool
|
||||
}
|
||||
|
||||
type HandleFn[T any] func(T)
|
||||
|
||||
type HandleCall struct {
|
||||
Fn HandleFn[*Handle]
|
||||
Order float64
|
||||
Name string
|
||||
}
|
||||
|
||||
var isFirstRequest = true
|
||||
|
||||
func SetConfigHandle(a ...any) Handle {
|
||||
configFn := a[0].(func(*Handle))
|
||||
hh := a[1].(*Handle)
|
||||
h := &Handle{}
|
||||
handleComponents.Flush()
|
||||
componentsArgs.Flush()
|
||||
handleComponentHook.Flush()
|
||||
componentFilterFns.Flush()
|
||||
handlerss.Flush()
|
||||
handleHooks.Flush()
|
||||
h.ginH = gin.H{}
|
||||
fnMap.Flush()
|
||||
fnHook.Flush()
|
||||
if isFirstRequest {
|
||||
isFirstRequest = false
|
||||
} else {
|
||||
reload.Reload()
|
||||
}
|
||||
h.C = hh.C
|
||||
h.theme = hh.theme
|
||||
configFn(h)
|
||||
v := apply.GetPlugins()
|
||||
pluginFn, ok := v.(func(*Handle))
|
||||
if ok {
|
||||
pluginFn(h)
|
||||
}
|
||||
return *h
|
||||
}
|
||||
|
||||
var GetInitHandleFn = reload.BuildValFnWithAnyParams("themeArgAndConfig", SetConfigHandle, false)
|
||||
|
||||
func InitHandle(configFn func(*Handle), h *Handle) {
|
||||
hh := GetInitHandleFn(configFn, h)
|
||||
mods, err := wpconfig.GetThemeMods(h.theme)
|
||||
logs.IfError(err, "获取mods失败")
|
||||
h.themeMods = mods
|
||||
h.ginH = maps.Copy(hh.ginH)
|
||||
h.ginH["calPostClass"] = postClass(h)
|
||||
h.ginH["calBodyClass"] = bodyClass(h)
|
||||
h.ginH["customLogo"] = customLogo(h)
|
||||
h.ginH["calComponent"] = CalComponent(h)
|
||||
h.isInited = true
|
||||
}
|
||||
|
||||
func (h *Handle) Abort() {
|
||||
h.abort = true
|
||||
h.stopPipe = true
|
||||
}
|
||||
func (h *Handle) StopPipe() {
|
||||
h.stopPipe = true
|
||||
}
|
||||
func (h *Handle) StopHandle() {
|
||||
h.abort = true
|
||||
}
|
||||
|
||||
func (h *Handle) CommonThemeMods() wpconfig.ThemeMods {
|
||||
return h.themeMods
|
||||
}
|
||||
|
||||
func (h *Handle) Err() error {
|
||||
return h.err
|
||||
}
|
||||
|
||||
func (h *Handle) SetErr(err error, level int8) {
|
||||
h.err = errors.Join(err)
|
||||
h.errLevel = level
|
||||
}
|
||||
|
||||
func (h *Handle) ErrLevel() int8 {
|
||||
return h.errLevel
|
||||
}
|
||||
|
||||
func (h *Handle) SetErrLevel(errLevel int8) {
|
||||
h.errLevel = errLevel
|
||||
}
|
||||
|
||||
func (h *Handle) SetTempl(templ string) {
|
||||
h.templ = templ
|
||||
}
|
||||
func (h *Handle) GetTempl() string {
|
||||
return h.templ
|
||||
}
|
||||
|
||||
func (h *Handle) Scene() string {
|
||||
return h.scene
|
||||
}
|
||||
|
||||
func (h *Handle) SetDatas(GinH gin.H) {
|
||||
maps.Merge(h.ginH, GinH)
|
||||
}
|
||||
func (h *Handle) SetData(k string, v any) {
|
||||
h.ginH[k] = v
|
||||
}
|
||||
|
||||
func NewHandle(c *gin.Context, scene string, theme string) *Handle {
|
||||
return &Handle{
|
||||
C: c,
|
||||
theme: theme,
|
||||
Session: sessions.Default(c),
|
||||
scene: scene,
|
||||
Stats: constraints.Ok,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handle) GetPassword() string {
|
||||
if h.password != "" {
|
||||
return h.password
|
||||
}
|
||||
pw := h.Session.Get("post_password")
|
||||
if pw != nil {
|
||||
h.password = pw.(string)
|
||||
}
|
||||
return h.password
|
||||
}
|
||||
|
||||
func PreTemplate(h *Handle) {
|
||||
if h.templ == "" {
|
||||
h.templ = str.Join(h.theme, "/posts/index.gohtml")
|
||||
if h.scene == constraints.Detail {
|
||||
h.templ = str.Join(h.theme, "/posts/detail.gohtml")
|
||||
}
|
||||
}
|
||||
}
|
||||
func PreCodeAndStats(h *Handle) {
|
||||
if h.Stats != "" && h.Code != 0 {
|
||||
return
|
||||
}
|
||||
switch h.Stats {
|
||||
case constraints.Ok:
|
||||
h.Code = http.StatusOK
|
||||
case constraints.ParamError, constraints.Error404:
|
||||
h.Code = http.StatusNotFound
|
||||
case constraints.InternalErr:
|
||||
h.Code = http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
var htmlContentType = []string{"text/html; charset=utf-8"}
|
||||
|
||||
func (h *Handle) RenderHtml(t *template.Template, statsCode int, name string) {
|
||||
header := h.C.Writer.Header()
|
||||
if val := header["Content-Type"]; len(val) == 0 {
|
||||
header["Content-Type"] = htmlContentType
|
||||
}
|
||||
h.C.Status(statsCode)
|
||||
var err error
|
||||
if name == "" {
|
||||
err = t.Execute(h.C.Writer, h.ginH)
|
||||
} else {
|
||||
err = t.ExecuteTemplate(h.C.Writer, name, h.ginH)
|
||||
}
|
||||
h.Abort()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handle) PushHandlers(pipeScene string, call HandleCall, statsOrScene ...string) {
|
||||
for _, s := range statsOrScene {
|
||||
h.PushHandler(pipeScene, s, call)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handle) CommonComponents() {
|
||||
h.PushCacheGroupHeadScript(constraints.AllScene, "siteIconAndCustomCss", 0, CalSiteIcon, CalCustomCss)
|
||||
h.PushRender(constraints.AllStats, NewHandleFn(PreRenderTemplate, 0, "wp.PreRenderTemplate"))
|
||||
ReplyCommentJs(h)
|
||||
AdditionScript(h)
|
||||
}
|
||||
|
||||
func AdditionScript(h *Handle) {
|
||||
s := config.GetConfig().ExternScript
|
||||
if len(s) < 1 {
|
||||
return
|
||||
}
|
||||
fn := func(f, name string) {
|
||||
if f == "" {
|
||||
return
|
||||
}
|
||||
ss, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
logs.Error(err, str.Join("解析", name, "失败"), f)
|
||||
} else {
|
||||
h.PushComponents(constraints.AllScene, constraints.HeadScript, NewComponent(name, string(ss), false, 0, nil))
|
||||
}
|
||||
}
|
||||
switch len(s) {
|
||||
case 1:
|
||||
fn(s[0], "externHead")
|
||||
case 2:
|
||||
fn(s[0], "externHead")
|
||||
fn(s[1], "externFooter")
|
||||
}
|
||||
}
|
||||
|
||||
func PreRenderTemplate(h *Handle) {
|
||||
h.C.HTML(h.Code, h.templ, h.ginH)
|
||||
h.Abort()
|
||||
}
|
||||
|
||||
func NewHandleFn(fn HandleFn[*Handle], order float64, name string) HandleCall {
|
||||
return HandleCall{Fn: fn, Order: order, Name: name}
|
||||
}
|
||||
|
||||
func NothingToDo(h *Handle) {
|
||||
h.Abort()
|
||||
}
|
||||
|
||||
func (h *Handle) IsHttps() bool {
|
||||
if h.C.Request.TLS != nil {
|
||||
return true
|
||||
}
|
||||
return "https" == strings.ToLower(h.C.Request.Header.Get("X-Forwarded-Proto"))
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package wpconfig
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
)
|
||||
|
||||
var terms = safety.NewMap[uint64, models.Terms]()
|
||||
var termTaxonomies = safety.NewMap[uint64, models.TermTaxonomy]()
|
||||
|
||||
var my = safety.NewMap[uint64, models.TermsMy]()
|
||||
|
||||
func GetTerm(termId uint64) (models.Terms, bool) {
|
||||
return terms.Load(termId)
|
||||
}
|
||||
|
||||
func GetTermTaxonomy(termId uint64) (models.TermTaxonomy, bool) {
|
||||
return termTaxonomies.Load(termId)
|
||||
}
|
||||
func GetTermMy(termId uint64) (models.TermsMy, bool) {
|
||||
return my.Load(termId)
|
||||
}
|
||||
|
||||
func InitTerms() (err error) {
|
||||
terms.Flush()
|
||||
termTaxonomies.Flush()
|
||||
term, err := model.SimpleFind[models.Terms](ctx, nil, "*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, wpTerms := range term {
|
||||
terms.Store(wpTerms.TermId, wpTerms)
|
||||
}
|
||||
termTax, err := model.SimpleFind[models.TermTaxonomy](ctx, nil, "*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, taxonomy := range termTax {
|
||||
termTaxonomies.Store(taxonomy.TermTaxonomyId, taxonomy)
|
||||
if term, ok := terms.Load(taxonomy.TermId); ok {
|
||||
my.Store(taxonomy.TermId, models.TermsMy{
|
||||
Terms: term,
|
||||
TermTaxonomy: taxonomy,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
44
cache/cache.go
vendored
44
cache/cache.go
vendored
@ -2,49 +2,15 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Cache[K comparable, V any] interface {
|
||||
Get(ctx context.Context, key K) (V, bool)
|
||||
Set(ctx context.Context, key K, val V)
|
||||
GetExpireTime(ctx context.Context) time.Duration
|
||||
Ttl(ctx context.Context, key K) time.Duration
|
||||
Set(ctx context.Context, key K, val V, expire time.Duration)
|
||||
Ttl(ctx context.Context, key K, expire time.Duration) time.Duration
|
||||
Ver(ctx context.Context, key K) int
|
||||
Flush(ctx context.Context)
|
||||
Del(ctx context.Context, key ...K)
|
||||
ClearExpired(ctx context.Context)
|
||||
}
|
||||
|
||||
type Expend[K comparable, V any] interface {
|
||||
Gets(ctx context.Context, k []K) (map[K]V, error)
|
||||
Sets(ctx context.Context, m map[K]V)
|
||||
}
|
||||
|
||||
type SetTime interface {
|
||||
SetExpiredTime(func() time.Duration)
|
||||
}
|
||||
|
||||
type AnyCache[T any] interface {
|
||||
Get(ctx context.Context) (T, bool)
|
||||
Set(ctx context.Context, v T)
|
||||
Flush(ctx context.Context)
|
||||
GetLastSetTime(ctx context.Context) time.Time
|
||||
}
|
||||
|
||||
type Refresh[K comparable, V any] interface {
|
||||
Refresh(ctx context.Context, k K, a ...any)
|
||||
}
|
||||
type RefreshVar[T any] interface {
|
||||
Refresh(ctx context.Context, a ...any)
|
||||
}
|
||||
|
||||
type Lockss[K comparable] interface {
|
||||
GetLock(ctx context.Context, gMut *sync.Mutex, k ...K) *sync.Mutex
|
||||
}
|
||||
|
||||
type LockFn[K comparable] func(ctx context.Context, gMut *sync.Mutex, k ...K) *sync.Mutex
|
||||
|
||||
type LocksNum interface {
|
||||
SetLockNum(num int)
|
||||
Delete(ctx context.Context, key K)
|
||||
ClearExpired(ctx context.Context, expire time.Duration)
|
||||
}
|
||||
|
163
cache/cachemanager/manger.go
vendored
163
cache/cachemanager/manger.go
vendored
@ -1,163 +0,0 @@
|
||||
package cachemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/cache"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var mutex sync.Mutex
|
||||
|
||||
type Fn func(context.Context)
|
||||
|
||||
type clearExpired interface {
|
||||
ClearExpired(ctx context.Context)
|
||||
}
|
||||
|
||||
var clears = safety.NewVar(mockmap.Map[string, Fn]{})
|
||||
|
||||
var flushes = safety.NewVar(mockmap.Map[string, Fn]{})
|
||||
|
||||
func Flush() {
|
||||
ctx := context.WithValue(context.Background(), "execFlushBy", "mangerFlushFn")
|
||||
for _, f := range flushes.Load() {
|
||||
f.Value(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func Flushes(ctx context.Context, names ...string) {
|
||||
execute(ctx, flushes, names...)
|
||||
}
|
||||
|
||||
func execute(ctx context.Context, q *safety.Var[mockmap.Map[string, Fn]], names ...string) {
|
||||
queues := q.Load()
|
||||
for _, name := range names {
|
||||
queue := queues.Get(name)
|
||||
if queue.Value != nil {
|
||||
queue.Value(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseArgs(args ...any) (string, func() time.Duration) {
|
||||
var name string
|
||||
var fn func() time.Duration
|
||||
for _, arg := range args {
|
||||
v, ok := arg.(string)
|
||||
if ok {
|
||||
name = v
|
||||
continue
|
||||
}
|
||||
vv, ok := arg.(func() time.Duration)
|
||||
if ok {
|
||||
fn = vv
|
||||
}
|
||||
|
||||
}
|
||||
return name, fn
|
||||
}
|
||||
|
||||
func buildLockFn[K comparable](args ...any) cache.LockFn[K] {
|
||||
lockFn := helper.ParseArgs(cache.LockFn[K](nil), args...)
|
||||
name := helper.ParseArgs("", args...)
|
||||
num := helper.ParseArgs(runtime.NumCPU(), args...)
|
||||
loFn := func() int {
|
||||
return num
|
||||
}
|
||||
loFn = helper.ParseArgs(loFn, args...)
|
||||
if name != "" {
|
||||
loFn = reload.BuildFnVal(str.Join("cachesLocksNum-", name), num, loFn)
|
||||
}
|
||||
if lockFn == nil {
|
||||
looo := helper.ParseArgs(cache.Lockss[K](nil), args...)
|
||||
if looo != nil {
|
||||
lockFn = looo.GetLock
|
||||
loo, ok := any(looo).(cache.LocksNum)
|
||||
if ok && loo != nil {
|
||||
loo.SetLockNum(num)
|
||||
}
|
||||
} else {
|
||||
lo := cache.NewLocks[K](loFn)
|
||||
lockFn = lo.GetLock
|
||||
PushOrSetFlush(mockmap.Item[string, Fn]{
|
||||
Name: name,
|
||||
Value: lo.Flush,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
return lockFn
|
||||
}
|
||||
|
||||
func SetExpireTime(c cache.SetTime, name string, expireTime time.Duration, expireTimeFn func() time.Duration) {
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
fn := reload.BuildFnVal(str.Join("cacheManger-", name, "-expiredTime"), expireTime, expireTimeFn)
|
||||
c.SetExpiredTime(fn)
|
||||
}
|
||||
|
||||
func ChangeExpireTime(t time.Duration, coverConf bool, name ...string) {
|
||||
for _, s := range name {
|
||||
reload.SetFnVal(s, t, coverConf)
|
||||
}
|
||||
}
|
||||
func pushOrSet(q *safety.Var[mockmap.Map[string, Fn]], queues ...mockmap.Item[string, Fn]) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
qu := q.Load()
|
||||
for _, queue := range queues {
|
||||
v := qu.Get(queue.Name)
|
||||
if v.Value != nil {
|
||||
qu.Set(queue.Name, queue.Value)
|
||||
} else {
|
||||
qu = append(qu, queue)
|
||||
}
|
||||
}
|
||||
q.Store(qu)
|
||||
}
|
||||
|
||||
// PushOrSetFlush will execute flush func when call Flush or Flushes
|
||||
func PushOrSetFlush(queues ...mockmap.Item[string, Fn]) {
|
||||
pushOrSet(flushes, queues...)
|
||||
}
|
||||
|
||||
// PushOrSetClearExpired will execute clearExpired func when call ClearExpired or ClearExpireds
|
||||
func PushOrSetClearExpired(queues ...mockmap.Item[string, Fn]) {
|
||||
pushOrSet(clears, queues...)
|
||||
}
|
||||
|
||||
func del(q *safety.Var[mockmap.Map[string, Fn]], names ...string) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
queues := q.Load()
|
||||
for _, name := range names {
|
||||
queues.Del(name)
|
||||
}
|
||||
q.Store(queues)
|
||||
}
|
||||
func DelFlush(names ...string) {
|
||||
del(flushes, names...)
|
||||
}
|
||||
|
||||
func DelClearExpired(names ...string) {
|
||||
del(clears, names...)
|
||||
}
|
||||
|
||||
func ClearExpireds(ctx context.Context, names ...string) {
|
||||
execute(ctx, clears, names...)
|
||||
}
|
||||
|
||||
func ClearExpired() {
|
||||
ctx := context.WithValue(context.Background(), "execClearExpired", "mangerClearExpiredFn")
|
||||
for _, queue := range clears.Load() {
|
||||
queue.Value(ctx)
|
||||
}
|
||||
}
|
193
cache/cachemanager/manger_test.go
vendored
193
cache/cachemanager/manger_test.go
vendored
@ -1,193 +0,0 @@
|
||||
package cachemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/taskPools"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
func TestFlushMapVal(t *testing.T) {
|
||||
_ = number.Range(1, 5, 0)
|
||||
t.Run("t1", func(t *testing.T) {
|
||||
count := 0
|
||||
vv := NewMemoryMapCache(func(ctx2 context.Context, ks []int, a ...any) (map[int]int, error) {
|
||||
r := make(map[int]int)
|
||||
for _, k := range ks {
|
||||
r[k] = k * k
|
||||
}
|
||||
count++
|
||||
return r, nil
|
||||
}, nil, time.Second, "test")
|
||||
|
||||
gets, err := GetBatchBy[int]("test", ctx, number.Range(1, 10), time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(t, "err:", err)
|
||||
}
|
||||
p := taskPools.NewPools(10)
|
||||
for i := 0; i < 20; i++ {
|
||||
i := i
|
||||
p.Execute(func() {
|
||||
if i%2 == 0 {
|
||||
vv.Get(ctx, 5)
|
||||
} else {
|
||||
vv.Set(ctx, i, i)
|
||||
}
|
||||
})
|
||||
}
|
||||
p.Wait()
|
||||
fmt.Println(gets, count)
|
||||
DelMapCacheVal("test", 3, 4)
|
||||
fmt.Println(vv.Get(ctx, 3))
|
||||
fmt.Println(vv.Get(ctx, 4))
|
||||
get, err := GetBy[int]("test", ctx, 3, time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(t, "err", err)
|
||||
}
|
||||
fmt.Println(get, count)
|
||||
fmt.Println(vv.Get(ctx, 5))
|
||||
Flushes(ctx, "test")
|
||||
fmt.Println(vv.Get(ctx, 5))
|
||||
fmt.Println(vv.Get(ctx, 6))
|
||||
//fmt.Println(GetVarCache("test"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetExpireTime(t *testing.T) {
|
||||
t.Run("t1", func(t *testing.T) {
|
||||
c := NewMemoryMapCache[string, string](func(ctx2 context.Context, strings []string, a ...any) (map[string]string, error) {
|
||||
return slice.ToMap(strings, func(v string) (string, string) {
|
||||
return v, str.Join(v, "__", v)
|
||||
}, false), nil
|
||||
}, nil, time.Second, "xx")
|
||||
c.Set(ctx, "xx", "yy")
|
||||
fmt.Println(c.Get(ctx, "xx"))
|
||||
time.Sleep(time.Second)
|
||||
fmt.Println(c.Get(ctx, "xx"))
|
||||
ChangeExpireTime(3*time.Second, true, "xx")
|
||||
c.Set(ctx, "xx", "yyy")
|
||||
time.Sleep(time.Second)
|
||||
fmt.Println(c.Get(ctx, "xx"))
|
||||
time.Sleep(3 * time.Second)
|
||||
fmt.Println(c.Get(ctx, "xx"))
|
||||
cc, _ := GetMapCache[string, string]("xx")
|
||||
fmt.Println(reflect.DeepEqual(c, cc))
|
||||
cc.Set(ctx, "fff", "xxxx")
|
||||
cc.Set(ctx, "ffx", "eex")
|
||||
cc.Set(ctx, "ww", "vv")
|
||||
m, err := cc.GetBatchToMap(ctx, []string{"fff", "ffx", "ww", "kkkk"}, time.Second)
|
||||
fmt.Println(m, err)
|
||||
fmt.Println(GetBatchByToMap[string]("xx", ctx, []string{"fff", "ffx", "ww", "kkkk"}, time.Second))
|
||||
v := NewVarMemoryCache(func(ct context.Context, a ...any) (string, error) {
|
||||
return "ssss", nil
|
||||
}, 3*time.Second, "ff")
|
||||
vv, _ := GetVarCache[string]("ff")
|
||||
fmt.Println(reflect.DeepEqual(v, vv))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetMapCache(t *testing.T) {
|
||||
t.Run("t1", func(t *testing.T) {
|
||||
x := NewMemoryMapCache(nil, func(ctx2 context.Context, k string, a ...any) (string, error) {
|
||||
fmt.Println("memory cache")
|
||||
return strings.Repeat(k, 2), nil
|
||||
}, time.Hour, "test")
|
||||
fmt.Println(GetBy[string]("test", ctx, "test", time.Second))
|
||||
|
||||
NewMapCache[string, string](xx[string, string]{m: map[string]string{}}, nil, func(ctx2 context.Context, k string, a ...any) (string, error) {
|
||||
fmt.Println("other cache drives. eg: redis,file.....")
|
||||
return strings.Repeat(k, 2), nil
|
||||
}, "test", time.Hour)
|
||||
|
||||
if err := SetMapCache("kkk", x); err != nil {
|
||||
t.Errorf("SetMapCache() error = %v, wantErr %v", err, nil)
|
||||
}
|
||||
fmt.Println(GetBy[string]("test", ctx, "test", time.Second))
|
||||
})
|
||||
}
|
||||
|
||||
type xx[K comparable, V any] struct {
|
||||
m map[K]V
|
||||
}
|
||||
|
||||
func (x xx[K, V]) Get(ctx context.Context, key K) (V, bool) {
|
||||
v, ok := x.m[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (x xx[K, V]) Set(ctx context.Context, key K, val V) {
|
||||
x.m[key] = val
|
||||
}
|
||||
|
||||
func (x xx[K, V]) GetExpireTime(ctx context.Context) time.Duration {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (x xx[K, V]) Ttl(ctx context.Context, key K) time.Duration {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (x xx[K, V]) Flush(ctx context.Context) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (x xx[K, V]) Del(ctx context.Context, key ...K) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (x xx[K, V]) ClearExpired(ctx context.Context) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func TestSetVarCache(t *testing.T) {
|
||||
t.Run("t1", func(t *testing.T) {
|
||||
bak := NewVarMemoryCache(func(ctx2 context.Context, a ...any) (string, error) {
|
||||
fmt.Println("memory cache")
|
||||
return "xxx", nil
|
||||
}, time.Hour, "test")
|
||||
fmt.Println(GetVarVal[string]("test", ctx, time.Second))
|
||||
NewVarCache[string](oo[string]{}, func(ctx2 context.Context, a ...any) (string, error) {
|
||||
fmt.Println("other cache drives. eg: redis,file.....")
|
||||
return "ooo", nil
|
||||
}, "test")
|
||||
if err := SetVarCache("xx", bak); err != nil {
|
||||
t.Errorf("SetVarCache() error = %v, wantErr %v", err, nil)
|
||||
}
|
||||
fmt.Println(GetVarVal[string]("test", ctx, time.Second))
|
||||
})
|
||||
}
|
||||
|
||||
type oo[T any] struct {
|
||||
val T
|
||||
}
|
||||
|
||||
func (o oo[T]) Get(ctx context.Context) (T, bool) {
|
||||
return o.val, false
|
||||
}
|
||||
|
||||
func (o oo[T]) Set(ctx context.Context, v T) {
|
||||
o.val = v
|
||||
}
|
||||
|
||||
func (o oo[T]) Flush(ctx context.Context) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (o oo[T]) GetLastSetTime(ctx context.Context) time.Time {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
148
cache/cachemanager/mapcache.go
vendored
148
cache/cachemanager/mapcache.go
vendored
@ -1,148 +0,0 @@
|
||||
package cachemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/fthvgb1/wp-go/cache"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"time"
|
||||
)
|
||||
|
||||
var mapDelFuncs = safety.NewMap[string, func(any)]()
|
||||
|
||||
var mapCache = safety.NewMap[string, any]()
|
||||
|
||||
func SetMapCache[K comparable, V any](name string, ca *cache.MapCache[K, V]) error {
|
||||
v, ok := mapCache.Load(name)
|
||||
if !ok {
|
||||
mapCache.Store(name, ca)
|
||||
return nil
|
||||
}
|
||||
_, ok = v.(*cache.MapCache[K, V])
|
||||
if !ok {
|
||||
return errors.New(str.Join("cache ", name, " type err"))
|
||||
}
|
||||
mapCache.Store(name, ca)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushMangerMap will del mapCache val with name When call DelMapCacheVal
|
||||
func PushMangerMap[K comparable, V any](name string, m *cache.MapCache[K, V]) {
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
mapCache.Store(name, m)
|
||||
mapDelFuncs.Store(name, func(a any) {
|
||||
k, ok := a.([]K)
|
||||
if ok && len(k) > 0 {
|
||||
mm, ok := mapCache.Load(name)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
c, ok := mm.(*cache.MapCache[K, V])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(context.Background(), "ctx", "registerFlush")
|
||||
c.Del(ctx, k...)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func GetBy[T any, K comparable](name string, ct context.Context, key K, timeout time.Duration, params ...any) (r T, err error) {
|
||||
ct = context.WithValue(ct, "getCache", name)
|
||||
ca, err := getMap[K, T](name)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
vv, err := ca.GetCache(ct, key, timeout, params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r = vv
|
||||
return
|
||||
}
|
||||
|
||||
func getMap[K comparable, T any](name string) (*cache.MapCache[K, T], error) {
|
||||
m, ok := mapCache.Load(name)
|
||||
if !ok {
|
||||
return nil, errors.New(str.Join("cache ", name, " doesn't exist"))
|
||||
}
|
||||
vk, ok := m.(*cache.MapCache[K, T])
|
||||
if !ok {
|
||||
return nil, errors.New(str.Join("cache ", name, " type error"))
|
||||
}
|
||||
return vk, nil
|
||||
}
|
||||
func GetBatchBy[T any, K comparable](name string, ct context.Context, key []K, timeout time.Duration, params ...any) (r []T, err error) {
|
||||
ct = context.WithValue(ct, "getCache", name)
|
||||
ca, err := getMap[K, T](name)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
vv, err := ca.GetCacheBatch(ct, key, timeout, params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r = vv
|
||||
return
|
||||
}
|
||||
func GetBatchByToMap[T any, K comparable](name string, ct context.Context, key []K, timeout time.Duration, params ...any) (r map[K]T, err error) {
|
||||
ct = context.WithValue(ct, "getCache", name)
|
||||
ca, err := getMap[K, T](name)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
vv, err := ca.GetBatchToMap(ct, key, timeout, params...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
r = vv
|
||||
return
|
||||
}
|
||||
|
||||
func NewMapCache[K comparable, V any](data cache.Cache[K, V], batchFn cache.MapBatchFn[K, V], fn cache.MapSingleFn[K, V], args ...any) *cache.MapCache[K, V] {
|
||||
inc := helper.ParseArgs((*cache.IncreaseUpdate[K, V])(nil), args...)
|
||||
m := cache.NewMapCache[K, V](data, fn, batchFn, inc, buildLockFn[K](args...), args...)
|
||||
name, f := parseArgs(args...)
|
||||
if name != "" {
|
||||
PushMangerMap(name, m)
|
||||
}
|
||||
PushOrSetFlush(mockmap.Item[string, Fn]{
|
||||
Name: name,
|
||||
Value: m.Flush,
|
||||
})
|
||||
PushOrSetClearExpired(mockmap.Item[string, Fn]{
|
||||
Name: name,
|
||||
Value: m.ClearExpired,
|
||||
})
|
||||
if f != nil && name != "" {
|
||||
SetExpireTime(any(data).(cache.SetTime), name, 0, f)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func NewMemoryMapCache[K comparable, V any](batchFn cache.MapBatchFn[K, V],
|
||||
fn cache.MapSingleFn[K, V], expireTime time.Duration, args ...any) *cache.MapCache[K, V] {
|
||||
|
||||
c := NewMapCache[K, V](cache.NewMemoryMapCache[K, V](func() time.Duration {
|
||||
return expireTime
|
||||
}), batchFn, fn, args...)
|
||||
return c
|
||||
}
|
||||
|
||||
func GetMapCache[K comparable, V any](name string) (*cache.MapCache[K, V], bool) {
|
||||
vv, err := getMap[K, V](name)
|
||||
return vv, err == nil
|
||||
}
|
||||
|
||||
func DelMapCacheVal[T any](name string, keys ...T) {
|
||||
v, ok := mapDelFuncs.Load(name)
|
||||
if !ok || len(keys) < 1 {
|
||||
return
|
||||
}
|
||||
v(keys)
|
||||
}
|
57
cache/cachemanager/pagination.go
vendored
57
cache/cachemanager/pagination.go
vendored
@ -1,57 +0,0 @@
|
||||
package cachemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/fthvgb1/wp-go/cache"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewPaginationCache[K comparable, V any](m *cache.MapCache[string, helper.PaginationData[V]], maxNum int,
|
||||
dbFn cache.DbFn[K, V], localFn cache.LocalFn[K, V], dbKeyFn, localKeyFn func(K, ...any) string, fetchNum int, name string, a ...any) *cache.Pagination[K, V] {
|
||||
fn := helper.ParseArgs([]func() int(nil), a...)
|
||||
var ma, fet func() int
|
||||
if len(fn) > 0 {
|
||||
ma = fn[0]
|
||||
if len(fn) > 1 {
|
||||
fet = fn[1]
|
||||
}
|
||||
}
|
||||
if ma == nil {
|
||||
ma = reload.BuildFnVal(str.Join("paginationCache-", name, "-maxNum"), maxNum, nil)
|
||||
}
|
||||
if fet == nil {
|
||||
fet = reload.BuildFnVal(str.Join("paginationCache-", name, "-fetchNum"), fetchNum, nil)
|
||||
}
|
||||
p := cache.NewPagination(m, ma, dbFn, localFn, dbKeyFn, localKeyFn, fet, name)
|
||||
mapCache.Store(name, p)
|
||||
return p
|
||||
}
|
||||
|
||||
func GetPaginationCache[K comparable, V any](name string) (*cache.Pagination[K, V], bool) {
|
||||
v, err := getPagination[K, V](name)
|
||||
return v, err == nil
|
||||
}
|
||||
|
||||
func Pagination[V any, K comparable](name string, ctx context.Context, timeout time.Duration, k K, page, limit int, a ...any) ([]V, int, error) {
|
||||
v, err := getPagination[K, V](name)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return v.Pagination(ctx, timeout, k, page, limit, a...)
|
||||
}
|
||||
|
||||
func getPagination[K comparable, T any](name string) (*cache.Pagination[K, T], error) {
|
||||
m, ok := mapCache.Load(name)
|
||||
if !ok {
|
||||
return nil, errors.New(str.Join("cache ", name, " doesn't exist"))
|
||||
}
|
||||
vk, ok := m.(*cache.Pagination[K, T])
|
||||
if !ok {
|
||||
return nil, errors.New(str.Join("cache ", name, " type error"))
|
||||
}
|
||||
return vk, nil
|
||||
}
|
83
cache/cachemanager/varcache.go
vendored
83
cache/cachemanager/varcache.go
vendored
@ -1,83 +0,0 @@
|
||||
package cachemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/fthvgb1/wp-go/cache"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"time"
|
||||
)
|
||||
|
||||
var varCache = safety.NewMap[string, any]()
|
||||
|
||||
func SetVarCache[T any](name string, v *cache.VarCache[T]) error {
|
||||
vv, ok := varCache.Load(name)
|
||||
if !ok {
|
||||
varCache.Store(name, v)
|
||||
return nil
|
||||
}
|
||||
_, ok = vv.(*cache.VarCache[T])
|
||||
if ok {
|
||||
varCache.Store(name, v)
|
||||
return nil
|
||||
}
|
||||
return errors.New(str.Join("cache ", name, " type err"))
|
||||
}
|
||||
|
||||
func NewVarCache[T any](c cache.AnyCache[T], fn func(context.Context, ...any) (T, error), a ...any) *cache.VarCache[T] {
|
||||
inc := helper.ParseArgs((*cache.IncreaseUpdateVar[T])(nil), a...)
|
||||
ref := helper.ParseArgs(cache.RefreshVar[T](nil), a...)
|
||||
v := cache.NewVarCache(c, fn, inc, ref, a...)
|
||||
|
||||
name, _ := parseArgs(a...)
|
||||
if name != "" {
|
||||
varCache.Store(name, v)
|
||||
}
|
||||
PushOrSetFlush(mockmap.Item[string, Fn]{
|
||||
Name: name,
|
||||
Value: v.Flush,
|
||||
})
|
||||
cc, ok := any(c).(clearExpired)
|
||||
if ok {
|
||||
PushOrSetClearExpired(mockmap.Item[string, Fn]{
|
||||
Name: name,
|
||||
Value: cc.ClearExpired,
|
||||
})
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func GetVarVal[T any](name string, ctx context.Context, duration time.Duration, a ...any) (r T, err error) {
|
||||
ctx = context.WithValue(ctx, "getCache", name)
|
||||
ca, ok := GetVarCache[T](name)
|
||||
if !ok {
|
||||
err = errors.New(str.Join("cache ", name, " is not exist"))
|
||||
return
|
||||
}
|
||||
v, err := ca.GetCache(ctx, duration, a...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r = v
|
||||
return
|
||||
}
|
||||
|
||||
func NewVarMemoryCache[T any](fn func(context.Context, ...any) (T, error), expired time.Duration, a ...any) *cache.VarCache[T] {
|
||||
c := cache.NewVarMemoryCache[T](nil)
|
||||
name, e := parseArgs(a...)
|
||||
SetExpireTime(c, name, expired, e)
|
||||
v := NewVarCache[T](c, fn, a...)
|
||||
return v
|
||||
}
|
||||
|
||||
func GetVarCache[T any](name string) (*cache.VarCache[T], bool) {
|
||||
v, ok := varCache.Load(name)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
vv, ok := v.(*cache.VarCache[T])
|
||||
return vv, ok
|
||||
}
|
78
cache/locks.go
vendored
78
cache/locks.go
vendored
@ -1,78 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Locks[K comparable] struct {
|
||||
numFn func() int
|
||||
locks []*sync.Mutex
|
||||
m *safety.Map[K, *sync.Mutex]
|
||||
counter *int64
|
||||
}
|
||||
|
||||
func (l *Locks[K]) Flush(_ context.Context) {
|
||||
l.m.Flush()
|
||||
atomic.StoreInt64(l.counter, 0)
|
||||
}
|
||||
|
||||
func (l *Locks[K]) SetNumFn(numFn func() int) {
|
||||
l.numFn = numFn
|
||||
}
|
||||
|
||||
func NewLocks[K comparable](num func() int) *Locks[K] {
|
||||
var i int64
|
||||
return &Locks[K]{numFn: num, m: safety.NewMap[K, *sync.Mutex](), counter: &i}
|
||||
}
|
||||
|
||||
func (l *Locks[K]) SetLockNum(num int) {
|
||||
if num > 0 {
|
||||
l.locks = make([]*sync.Mutex, num)
|
||||
for i := 0; i < num; i++ {
|
||||
l.locks[i] = &sync.Mutex{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Locks[K]) GetLock(ctx context.Context, gMut *sync.Mutex, keys ...K) *sync.Mutex {
|
||||
k := keys[0]
|
||||
lo, ok := l.m.Load(k)
|
||||
if ok {
|
||||
return lo
|
||||
}
|
||||
num := l.numFn()
|
||||
if num == 1 {
|
||||
return gMut
|
||||
}
|
||||
gMut.Lock()
|
||||
defer gMut.Unlock()
|
||||
lo, ok = l.m.Load(k)
|
||||
if ok {
|
||||
return lo
|
||||
}
|
||||
if num <= 0 {
|
||||
lo = &sync.Mutex{}
|
||||
l.m.Store(k, lo)
|
||||
return lo
|
||||
}
|
||||
if len(l.locks) == 0 {
|
||||
l.SetLockNum(num)
|
||||
}
|
||||
counter := int(atomic.LoadInt64(l.counter))
|
||||
if counter > len(l.locks)-1 {
|
||||
atomic.StoreInt64(l.counter, 0)
|
||||
counter = 0
|
||||
}
|
||||
lo = l.locks[counter]
|
||||
l.m.Store(k, lo)
|
||||
atomic.AddInt64(l.counter, 1)
|
||||
if len(l.locks) < num {
|
||||
for i := 0; i < num-len(l.locks); i++ {
|
||||
l.locks = append(l.locks, &sync.Mutex{})
|
||||
}
|
||||
}
|
||||
return lo
|
||||
}
|
583
cache/map.go
vendored
583
cache/map.go
vendored
@ -4,562 +4,172 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MapCache[K comparable, V any] struct {
|
||||
Cache[K, V]
|
||||
mux *sync.Mutex
|
||||
muFn func(ctx context.Context, gMut *sync.Mutex, k ...K) *sync.Mutex
|
||||
cacheFunc MapSingleFn[K, V]
|
||||
batchCacheFn MapBatchFn[K, V]
|
||||
getCacheBatch func(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error)
|
||||
getCacheBatchToMap func(c context.Context, key []K, timeout time.Duration, params ...any) (map[K]V, error)
|
||||
increaseUpdate *IncreaseUpdate[K, V]
|
||||
refresh Refresh[K, V]
|
||||
gets func(ctx context.Context, key K) (V, bool)
|
||||
sets func(ctx context.Context, key K, val V)
|
||||
getExpireTimes func(ctx context.Context) time.Duration
|
||||
ttl func(ctx context.Context, key K) time.Duration
|
||||
flush func(ctx context.Context)
|
||||
del func(ctx context.Context, key ...K)
|
||||
clearExpired func(ctx context.Context)
|
||||
data Cache[K, V]
|
||||
mux sync.Mutex
|
||||
cacheFunc func(...any) (V, error)
|
||||
batchCacheFn func(...any) (map[K]V, error)
|
||||
expireTime time.Duration
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) Get(ctx context.Context, key K) (V, bool) {
|
||||
return m.gets(ctx, key)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) Set(ctx context.Context, key K, val V) {
|
||||
m.sets(ctx, key, val)
|
||||
}
|
||||
func (m *MapCache[K, V]) Ttl(ctx context.Context, key K) time.Duration {
|
||||
return m.ttl(ctx, key)
|
||||
}
|
||||
func (m *MapCache[K, V]) GetExpireTime(ctx context.Context) time.Duration {
|
||||
return m.getExpireTimes(ctx)
|
||||
}
|
||||
func (m *MapCache[K, V]) Del(ctx context.Context, key ...K) {
|
||||
m.del(ctx, key...)
|
||||
}
|
||||
func (m *MapCache[K, V]) ClearExpired(ctx context.Context) {
|
||||
m.clearExpired(ctx)
|
||||
}
|
||||
|
||||
type IncreaseUpdate[K comparable, V any] struct {
|
||||
CycleTime func() time.Duration
|
||||
Fn IncreaseFn[K, V]
|
||||
}
|
||||
|
||||
func NewIncreaseUpdate[K comparable, V any](name string, fn IncreaseFn[K, V], cycleTime time.Duration, tFn func() time.Duration) *IncreaseUpdate[K, V] {
|
||||
tFn = reload.BuildFnVal(name, cycleTime, tFn)
|
||||
return &IncreaseUpdate[K, V]{CycleTime: tFn, Fn: fn}
|
||||
}
|
||||
|
||||
type MapSingleFn[K, V any] func(context.Context, K, ...any) (V, error)
|
||||
type MapBatchFn[K comparable, V any] func(context.Context, []K, ...any) (map[K]V, error)
|
||||
type IncreaseFn[K comparable, V any] func(c context.Context, currentData V, k K, t time.Time, a ...any) (data V, save bool, refresh bool, err error)
|
||||
|
||||
func NewMapCache[K comparable, V any](ca Cache[K, V], cacheFunc MapSingleFn[K, V], batchCacheFn MapBatchFn[K, V], inc *IncreaseUpdate[K, V], lockFn LockFn[K], a ...any) *MapCache[K, V] {
|
||||
r := &MapCache[K, V]{
|
||||
Cache: ca,
|
||||
mux: &sync.Mutex{},
|
||||
cacheFunc: cacheFunc,
|
||||
batchCacheFn: batchCacheFn,
|
||||
increaseUpdate: inc,
|
||||
muFn: lockFn,
|
||||
}
|
||||
if cacheFunc == nil && batchCacheFn != nil {
|
||||
r.setDefaultCacheFn(batchCacheFn)
|
||||
} else if batchCacheFn == nil && cacheFunc != nil {
|
||||
r.SetDefaultBatchFunc(cacheFunc)
|
||||
}
|
||||
ex, ok := any(ca).(Expend[K, V])
|
||||
if !ok {
|
||||
r.getCacheBatch = r.getCacheBatchs
|
||||
r.getCacheBatchToMap = r.getBatchToMapes
|
||||
} else {
|
||||
r.getCacheBatch = r.getBatches(ex)
|
||||
r.getCacheBatchToMap = r.getBatchToMap(ex)
|
||||
}
|
||||
re, ok := any(ca).(Refresh[K, V])
|
||||
if ok {
|
||||
r.refresh = re
|
||||
}
|
||||
initCache(r, a...)
|
||||
return r
|
||||
}
|
||||
|
||||
func initCache[K comparable, V any](r *MapCache[K, V], a ...any) {
|
||||
gets := helper.ParseArgs[func(Cache[K, V], context.Context, K) (V, bool)](nil, a...)
|
||||
if gets == nil {
|
||||
r.gets = r.Cache.Get
|
||||
} else {
|
||||
r.gets = func(ctx context.Context, key K) (V, bool) {
|
||||
return gets(r.Cache, ctx, key)
|
||||
}
|
||||
}
|
||||
|
||||
sets := helper.ParseArgs[func(Cache[K, V], context.Context, K, V)](nil, a...)
|
||||
if sets == nil {
|
||||
r.sets = r.Cache.Set
|
||||
} else {
|
||||
r.sets = func(ctx context.Context, key K, val V) {
|
||||
sets(r.Cache, ctx, key, val)
|
||||
}
|
||||
}
|
||||
|
||||
getExpireTimes := helper.ParseArgs[func(Cache[K, V], context.Context) time.Duration](nil, a...)
|
||||
if getExpireTimes == nil {
|
||||
r.getExpireTimes = r.Cache.GetExpireTime
|
||||
} else {
|
||||
r.getExpireTimes = func(ctx context.Context) time.Duration {
|
||||
return getExpireTimes(r.Cache, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
ttl := helper.ParseArgs[func(Cache[K, V], context.Context, K) time.Duration](nil, a...)
|
||||
if ttl == nil {
|
||||
r.ttl = r.Cache.Ttl
|
||||
} else {
|
||||
r.ttl = func(ctx context.Context, k K) time.Duration {
|
||||
return ttl(r.Cache, ctx, k)
|
||||
}
|
||||
}
|
||||
|
||||
del := helper.ParseArgs[func(Cache[K, V], context.Context, ...K)](nil, a...)
|
||||
if del == nil {
|
||||
r.del = r.Cache.Del
|
||||
} else {
|
||||
r.del = func(ctx context.Context, key ...K) {
|
||||
del(r.Cache, ctx, key...)
|
||||
}
|
||||
}
|
||||
|
||||
flushAndClearExpired := helper.ParseArgs[[]func(Cache[K, V], context.Context)](nil, a...)
|
||||
if flushAndClearExpired == nil {
|
||||
r.flush = r.Cache.Flush
|
||||
r.clearExpired = r.Cache.ClearExpired
|
||||
} else {
|
||||
r.flush = func(ctx context.Context) {
|
||||
flushAndClearExpired[0](r.Cache, ctx)
|
||||
}
|
||||
if len(flushAndClearExpired) > 1 {
|
||||
r.clearExpired = func(ctx context.Context) {
|
||||
flushAndClearExpired[1](r.Cache, ctx)
|
||||
}
|
||||
} else {
|
||||
r.clearExpired = r.Cache.ClearExpired
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) SetDefaultBatchFunc(fn MapSingleFn[K, V]) {
|
||||
m.batchCacheFn = func(ctx context.Context, ids []K, a ...any) (map[K]V, error) {
|
||||
var err error
|
||||
rr := make(map[K]V)
|
||||
for _, id := range ids {
|
||||
v, er := fn(ctx, id, a...)
|
||||
if er != nil {
|
||||
err = errors.Join(er)
|
||||
continue
|
||||
}
|
||||
rr[id] = v
|
||||
}
|
||||
return rr, err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) SetCacheFunc(fn MapSingleFn[K, V]) {
|
||||
func (m *MapCache[K, V]) SetCacheFunc(fn func(...any) (V, error)) {
|
||||
m.cacheFunc = fn
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) Ttl(ctx context.Context, k K) time.Duration {
|
||||
return m.data.Ttl(ctx, k, m.expireTime)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) GetLastSetTime(ctx context.Context, k K) (t time.Time) {
|
||||
tt := m.Ttl(ctx, k)
|
||||
tt := m.data.Ttl(ctx, k, m.expireTime)
|
||||
if tt <= 0 {
|
||||
return
|
||||
}
|
||||
return time.Now().Add(m.Ttl(ctx, k)).Add(-m.GetExpireTime(ctx))
|
||||
return time.Now().Add(m.data.Ttl(ctx, k, m.expireTime)).Add(-m.expireTime)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) SetCacheBatchFn(fn MapBatchFn[K, V]) {
|
||||
func (m *MapCache[K, V]) SetCacheBatchFn(fn func(...any) (map[K]V, error)) {
|
||||
m.batchCacheFn = fn
|
||||
if m.cacheFunc == nil {
|
||||
m.setDefaultCacheFn(fn)
|
||||
m.setCacheFn(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) setDefaultCacheFn(fn MapBatchFn[K, V]) {
|
||||
m.cacheFunc = func(ctx context.Context, k K, a ...any) (V, error) {
|
||||
func (m *MapCache[K, V]) setCacheFn(fn func(...any) (map[K]V, error)) {
|
||||
m.cacheFunc = func(a ...any) (V, error) {
|
||||
var err error
|
||||
var r map[K]V
|
||||
r, err = fn(ctx, []K{k}, a...)
|
||||
var id K
|
||||
ctx, ok := a[0].(context.Context)
|
||||
if ok {
|
||||
id = a[1].(K)
|
||||
r, err = fn(ctx, []K{id})
|
||||
} else {
|
||||
id = a[0].(K)
|
||||
r, err = fn([]K{id})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
var rr V
|
||||
return rr, err
|
||||
}
|
||||
return r[k], err
|
||||
return r[id], err
|
||||
}
|
||||
}
|
||||
|
||||
func NewMapCacheByFn[K comparable, V any](cacheType Cache[K, V], fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] {
|
||||
return &MapCache[K, V]{
|
||||
mux: sync.Mutex{},
|
||||
cacheFunc: fn,
|
||||
expireTime: expireTime,
|
||||
data: cacheType,
|
||||
}
|
||||
}
|
||||
func NewMapCacheByBatchFn[K comparable, V any](cacheType Cache[K, V], fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] {
|
||||
r := &MapCache[K, V]{
|
||||
mux: sync.Mutex{},
|
||||
batchCacheFn: fn,
|
||||
expireTime: expireTime,
|
||||
data: cacheType,
|
||||
}
|
||||
r.setCacheFn(fn)
|
||||
return r
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) Flush(ctx context.Context) {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
m.flush(ctx)
|
||||
m.data.Flush(ctx)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) increaseUpdates(c context.Context, timeout time.Duration, data V, key K, params ...any) (V, error) {
|
||||
var err error
|
||||
nowTime := time.Now()
|
||||
if nowTime.Sub(m.GetLastSetTime(c, key)) < m.increaseUpdate.CycleTime() {
|
||||
return data, err
|
||||
func (m *MapCache[K, V]) Get(ctx context.Context, k K) (V, bool) {
|
||||
return m.data.Get(ctx, k)
|
||||
}
|
||||
fn := func() {
|
||||
l := m.muFn(c, m.mux, key)
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
if nowTime.Sub(m.GetLastSetTime(c, key)) < m.increaseUpdate.CycleTime() {
|
||||
return
|
||||
}
|
||||
dat, save, refresh, er := m.increaseUpdate.Fn(c, data, key, m.GetLastSetTime(c, key), params...)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
if refresh {
|
||||
m.refresh.Refresh(c, key, params...)
|
||||
}
|
||||
if save {
|
||||
m.Set(c, key, dat)
|
||||
data = dat
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
er := helper.RunFnWithTimeout(c, timeout, fn)
|
||||
if err == nil && er != nil {
|
||||
return data, fmt.Errorf("increateUpdate cache %v err:[%s]", key, er)
|
||||
}
|
||||
} else {
|
||||
fn()
|
||||
}
|
||||
return data, err
|
||||
|
||||
func (m *MapCache[K, V]) Set(ctx context.Context, k K, v V) {
|
||||
m.data.Set(ctx, k, v, m.expireTime)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duration, params ...any) (V, error) {
|
||||
data, ok := m.Get(c, key)
|
||||
data, ok := m.data.Get(c, key)
|
||||
var err error
|
||||
if ok {
|
||||
if m.increaseUpdate == nil || m.refresh == nil {
|
||||
return data, err
|
||||
}
|
||||
return m.increaseUpdates(c, timeout, data, key, params...)
|
||||
}
|
||||
if !ok || m.data.Ttl(c, key, m.expireTime) <= 0 {
|
||||
ver := m.data.Ver(c, key)
|
||||
call := func() {
|
||||
l := m.muFn(c, m.mux, key)
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
if data, ok = m.Get(c, key); ok {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
if m.data.Ver(c, key) > ver {
|
||||
data, _ = m.data.Get(c, key)
|
||||
return
|
||||
}
|
||||
data, err = m.cacheFunc(c, key, params...)
|
||||
data, err = m.cacheFunc(params...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.Set(c, key, data)
|
||||
}
|
||||
if timeout > 0 {
|
||||
er := helper.RunFnWithTimeout(c, timeout, call, fmt.Sprintf("get cache %v ", key))
|
||||
if err == nil && er != nil {
|
||||
err = er
|
||||
ctx, cancel := context.WithTimeout(c, timeout)
|
||||
defer cancel()
|
||||
done := make(chan struct{}, 1)
|
||||
go func() {
|
||||
call()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error()))
|
||||
case <-done:
|
||||
}
|
||||
} else {
|
||||
call()
|
||||
}
|
||||
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
|
||||
return m.getCacheBatch(c, key, timeout, params...)
|
||||
var res []V
|
||||
ver := 0
|
||||
needFlush := slice.FilterAndMap(key, func(k K) (r K, ok bool) {
|
||||
if _, ok := m.data.Get(c, k); !ok {
|
||||
return k, true
|
||||
}
|
||||
ver += m.data.Ver(c, k)
|
||||
return
|
||||
})
|
||||
|
||||
func (m *MapCache[K, V]) GetBatchToMap(c context.Context, key []K, timeout time.Duration, params ...any) (map[K]V, error) {
|
||||
return m.getCacheBatchToMap(c, key, timeout, params...)
|
||||
}
|
||||
func (m *MapCache[K, V]) getBatchToMap(e Expend[K, V]) func(c context.Context, key []K, timeout time.Duration, params ...any) (map[K]V, error) {
|
||||
return func(ctx context.Context, key []K, timeout time.Duration, params ...any) (map[K]V, error) {
|
||||
var res map[K]V
|
||||
var err error
|
||||
mm, err := e.Gets(ctx, key)
|
||||
if err != nil || len(key) == len(mm) {
|
||||
return mm, err
|
||||
}
|
||||
var needIndex = make(map[K]int)
|
||||
res = mm
|
||||
var flushKeys []K
|
||||
for i, k := range key {
|
||||
_, ok := mm[k]
|
||||
if !ok {
|
||||
flushKeys = append(flushKeys, k)
|
||||
needIndex[k] = i
|
||||
}
|
||||
}
|
||||
|
||||
if len(needFlush) > 0 {
|
||||
call := func() {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
mmm, er := e.Gets(ctx, maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
|
||||
return k, true
|
||||
}))
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
for k, v := range mmm {
|
||||
res[k] = v
|
||||
delete(needIndex, k)
|
||||
}
|
||||
|
||||
if len(needIndex) < 1 {
|
||||
vers := slice.Reduce(needFlush, func(t K, r int) int {
|
||||
return r + m.data.Ver(c, t)
|
||||
}, 0)
|
||||
|
||||
if vers > ver {
|
||||
return
|
||||
}
|
||||
|
||||
r, er := m.batchCacheFn(ctx, maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
|
||||
return k, true
|
||||
}), params...)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
e.Sets(ctx, r)
|
||||
|
||||
for k := range needIndex {
|
||||
v, ok := r[k]
|
||||
if ok {
|
||||
res[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
done := make(chan struct{}, 1)
|
||||
go func() {
|
||||
call()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error()))
|
||||
return nil, err
|
||||
case <-done:
|
||||
}
|
||||
} else {
|
||||
call()
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
func (m *MapCache[K, V]) getBatchToMapes(c context.Context, key []K, timeout time.Duration, params ...any) (r map[K]V, err error) {
|
||||
r = make(map[K]V)
|
||||
var needIndex = make(map[K]int)
|
||||
for i, k := range key {
|
||||
v, ok := m.Get(c, k)
|
||||
if !ok {
|
||||
needIndex[k] = i
|
||||
} else {
|
||||
r[k] = v
|
||||
}
|
||||
}
|
||||
if len(needIndex) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
call := func() {
|
||||
l := m.muFn(c, m.mux, key...)
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
needFlushs := maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
|
||||
vv, ok := m.Get(c, k)
|
||||
if ok {
|
||||
r[k] = vv
|
||||
delete(needIndex, k)
|
||||
return k, false
|
||||
}
|
||||
return k, true
|
||||
})
|
||||
|
||||
if len(needFlushs) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
rr, er := m.batchCacheFn(c, needFlushs, params...)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
for k := range needIndex {
|
||||
v, ok := rr[k]
|
||||
if ok {
|
||||
r[k] = v
|
||||
}
|
||||
m.Set(c, k, v)
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(c, timeout)
|
||||
defer cancel()
|
||||
done := make(chan struct{}, 1)
|
||||
go func() {
|
||||
call()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error()))
|
||||
return nil, err
|
||||
case <-done:
|
||||
}
|
||||
} else {
|
||||
call()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) getCacheBatchs(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
|
||||
var res = make([]V, 0, len(key))
|
||||
var needIndex = make(map[K]int)
|
||||
for i, k := range key {
|
||||
v, ok := m.Get(c, k)
|
||||
if !ok {
|
||||
needIndex[k] = i
|
||||
}
|
||||
res = append(res, v)
|
||||
}
|
||||
if len(needIndex) < 1 {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
call := func() {
|
||||
l := m.muFn(c, m.mux, key...)
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
needFlushs := maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
|
||||
vv, ok := m.Get(c, k)
|
||||
if ok {
|
||||
res[needIndex[k]] = vv
|
||||
delete(needIndex, k)
|
||||
return k, false
|
||||
}
|
||||
return k, true
|
||||
})
|
||||
|
||||
if len(needFlushs) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
r, er := m.batchCacheFn(c, needFlushs, params...)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
for k, i := range needIndex {
|
||||
v, ok := r[k]
|
||||
if ok {
|
||||
res[i] = v
|
||||
m.Set(c, k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(c, timeout)
|
||||
defer cancel()
|
||||
done := make(chan struct{}, 1)
|
||||
go func() {
|
||||
call()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error()))
|
||||
return nil, err
|
||||
case <-done:
|
||||
}
|
||||
} else {
|
||||
call()
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) getBatches(e Expend[K, V]) func(ctx context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
|
||||
cc := e
|
||||
return func(ctx context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
|
||||
var res = make([]V, 0, len(key))
|
||||
var needIndex = make(map[K]int)
|
||||
var err error
|
||||
mm, err := cc.Gets(ctx, key)
|
||||
r, er := m.batchCacheFn(params...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var flushKeys []K
|
||||
for i, k := range key {
|
||||
v, ok := mm[k]
|
||||
if !ok {
|
||||
flushKeys = append(flushKeys, k)
|
||||
needIndex[k] = i
|
||||
var vv V
|
||||
v = vv
|
||||
}
|
||||
res = append(res, v)
|
||||
}
|
||||
if len(needIndex) < 1 {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
call := func() {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
mmm, er := cc.Gets(ctx, maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
|
||||
return k, true
|
||||
}))
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
for k, v := range mmm {
|
||||
res[needIndex[k]] = v
|
||||
delete(needIndex, k)
|
||||
}
|
||||
|
||||
if len(needIndex) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
r, er := m.batchCacheFn(ctx, maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
|
||||
return k, true
|
||||
}), params...)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
cc.Sets(ctx, r)
|
||||
|
||||
for k, i := range needIndex {
|
||||
v, ok := r[k]
|
||||
if ok {
|
||||
res[i] = v
|
||||
}
|
||||
for k, v := range r {
|
||||
m.Set(c, k, v)
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
ctx, cancel := context.WithTimeout(c, timeout)
|
||||
defer cancel()
|
||||
done := make(chan struct{}, 1)
|
||||
go func() {
|
||||
@ -569,13 +179,20 @@ func (m *MapCache[K, V]) getBatches(e Expend[K, V]) func(ctx context.Context, ke
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error()))
|
||||
return nil, err
|
||||
case <-done:
|
||||
}
|
||||
} else {
|
||||
call()
|
||||
}
|
||||
|
||||
}
|
||||
res = slice.FilterAndMap(key, func(k K) (V, bool) {
|
||||
return m.data.Get(c, k)
|
||||
})
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) ClearExpired(ctx context.Context) {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
m.data.ClearExpired(ctx, m.expireTime)
|
||||
}
|
||||
|
39
cache/map_test.go
vendored
39
cache/map_test.go
vendored
@ -12,22 +12,27 @@ import (
|
||||
)
|
||||
|
||||
var ca MapCache[string, string]
|
||||
var fn MapSingleFn[string, string]
|
||||
var batchFn MapBatchFn[string, string]
|
||||
var fn func(a ...any) (string, error)
|
||||
var batchFn func(a ...any) (map[string]string, error)
|
||||
var ct context.Context
|
||||
|
||||
func init() {
|
||||
fn = func(ctx context.Context, aa string, a ...any) (string, error) {
|
||||
fn = func(a ...any) (string, error) {
|
||||
aa := a[1].(string)
|
||||
return strings.Repeat(aa, 2), nil
|
||||
}
|
||||
ct = context.Background()
|
||||
batchFn = func(ctx context.Context, arr []string, a ...any) (map[string]string, error) {
|
||||
batchFn = func(a ...any) (map[string]string, error) {
|
||||
fmt.Println(a)
|
||||
return slice.FilterAndToMap(arr, func(t string, _ int) (string, string, bool) {
|
||||
arr := a[1].([]string)
|
||||
return slice.FilterAndToMap(arr, func(t string) (string, string, bool) {
|
||||
return t, strings.Repeat(t, 2), true
|
||||
}), nil
|
||||
}
|
||||
|
||||
ca = *NewMemoryMapCacheByFn[string, string](fn, time.Second*2)
|
||||
ca.SetCacheBatchFn(batchFn)
|
||||
_, _ = ca.GetCache(ct, "aa", time.Second, ct, "aa")
|
||||
_, _ = ca.GetCache(ct, "bb", time.Second, ct, "bb")
|
||||
}
|
||||
func TestMapCache_ClearExpired(t *testing.T) {
|
||||
type args struct {
|
||||
@ -67,9 +72,7 @@ func TestMapCache_Flush(t *testing.T) {
|
||||
m MapCache[K, V]
|
||||
args args
|
||||
}
|
||||
ca := *NewMapCache[string, string](NewMemoryMapCache[string, string](func() time.Duration {
|
||||
return time.Second
|
||||
}), fn, nil, nil, nil)
|
||||
ca := *NewMemoryMapCacheByFn[string, string](fn, time.Second)
|
||||
_, _ = ca.GetCache(ct, "aa", time.Second, ct, "aa")
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
@ -290,7 +293,7 @@ func TestMapCache_Set(t *testing.T) {
|
||||
|
||||
func TestMapCache_SetCacheBatchFn(t *testing.T) {
|
||||
type args[K comparable, V any] struct {
|
||||
fn MapBatchFn[K, V]
|
||||
fn func(...any) (map[K]V, error)
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
@ -312,19 +315,19 @@ func TestMapCache_SetCacheBatchFn(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMapCache_SetCacheFunc(t *testing.T) {
|
||||
type args[K comparable, V any] struct {
|
||||
fn MapSingleFn[K, V]
|
||||
type args[V any] struct {
|
||||
fn func(...any) (V, error)
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[K, V]
|
||||
args args[V]
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string, string]{fn: fn},
|
||||
args: args[string]{fn: fn},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@ -352,12 +355,12 @@ func TestMapCache_Ttl(t *testing.T) {
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string]{ct, "aa"},
|
||||
want: ca.GetExpireTime(ct) - tx.Sub(txx),
|
||||
want: ca.expireTime - tx.Sub(txx),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Printf("过期时间=%v \nttl=%v \n当前时间 =%v\n最后设置时间=%v\n当时时间-最后设置时间=%v ", ca.GetExpireTime(ct), ca.Ttl(ct, "aa"), tx, txx, tx.Sub(txx))
|
||||
fmt.Printf("过期时间=%v \nttl=%v \n当前时间 =%v\n最后设置时间=%v\n当时时间-最后设置时间=%v ", ca.expireTime, ca.Ttl(ct, "aa"), tx, txx, tx.Sub(txx))
|
||||
if got := tt.m.Ttl(tt.args.ct, tt.args.k); got != tt.want {
|
||||
t.Errorf("Ttl() = %v, want %v", got, tt.want)
|
||||
}
|
||||
@ -367,7 +370,7 @@ func TestMapCache_Ttl(t *testing.T) {
|
||||
|
||||
func TestMapCache_setCacheFn(t *testing.T) {
|
||||
type args[K comparable, V any] struct {
|
||||
fn MapBatchFn[K, V]
|
||||
fn func(...any) (map[K]V, error)
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
@ -384,7 +387,7 @@ func TestMapCache_setCacheFn(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ca.cacheFunc = nil
|
||||
tt.m.setDefaultCacheFn(tt.args.fn)
|
||||
tt.m.setCacheFn(tt.args.fn)
|
||||
fmt.Println(ca.GetCache(ct, "xx", time.Second, ct, "xx"))
|
||||
})
|
||||
}
|
||||
|
67
cache/memorymapcache.go
vendored
67
cache/memorymapcache.go
vendored
@ -2,22 +2,37 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MemoryMapCache[K comparable, V any] struct {
|
||||
*safety.Map[K, mapVal[V]]
|
||||
expireTime func() time.Duration
|
||||
}
|
||||
|
||||
func NewMemoryMapCache[K comparable, V any](expireTime func() time.Duration) *MemoryMapCache[K, V] {
|
||||
return &MemoryMapCache[K, V]{
|
||||
Map: safety.NewMap[K, mapVal[V]](),
|
||||
func NewMemoryMapCacheByFn[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] {
|
||||
return &MapCache[K, V]{
|
||||
data: NewMemoryMapCache[K, V](),
|
||||
cacheFunc: fn,
|
||||
expireTime: expireTime,
|
||||
mux: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
func NewMemoryMapCacheByBatchFn[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] {
|
||||
r := &MapCache[K, V]{
|
||||
data: NewMemoryMapCache[K, V](),
|
||||
batchCacheFn: fn,
|
||||
expireTime: expireTime,
|
||||
mux: sync.Mutex{},
|
||||
}
|
||||
r.setCacheFn(fn)
|
||||
return r
|
||||
}
|
||||
|
||||
func NewMemoryMapCache[K comparable, V any]() *MemoryMapCache[K, V] {
|
||||
return &MemoryMapCache[K, V]{Map: safety.NewMap[K, mapVal[V]]()}
|
||||
}
|
||||
|
||||
type mapVal[T any] struct {
|
||||
setTime time.Time
|
||||
@ -25,28 +40,15 @@ type mapVal[T any] struct {
|
||||
data T
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) SetExpiredTime(f func() time.Duration) {
|
||||
m.expireTime = f
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) GetExpireTime(_ context.Context) time.Duration {
|
||||
return m.expireTime()
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Get(_ context.Context, key K) (r V, ok bool) {
|
||||
v, ok := m.Load(key)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
r = v.data
|
||||
t := m.expireTime() - time.Now().Sub(v.setTime)
|
||||
if t <= 0 {
|
||||
ok = false
|
||||
if ok {
|
||||
return v.data, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V) {
|
||||
func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V, _ time.Duration) {
|
||||
v, ok := m.Load(key)
|
||||
t := time.Now()
|
||||
if ok {
|
||||
@ -63,12 +65,12 @@ func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V) {
|
||||
m.Store(key, v)
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Ttl(_ context.Context, key K) time.Duration {
|
||||
func (m *MemoryMapCache[K, V]) Ttl(_ context.Context, key K, expire time.Duration) time.Duration {
|
||||
v, ok := m.Load(key)
|
||||
if !ok {
|
||||
return time.Duration(-1)
|
||||
}
|
||||
return m.expireTime() - time.Now().Sub(v.setTime)
|
||||
return expire - time.Now().Sub(v.setTime)
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Ver(_ context.Context, key K) int {
|
||||
@ -83,28 +85,17 @@ func (m *MemoryMapCache[K, V]) Flush(context.Context) {
|
||||
m.Map.Flush()
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Del(_ context.Context, keys ...K) {
|
||||
for _, key := range keys {
|
||||
func (m *MemoryMapCache[K, V]) Delete(_ context.Context, key K) {
|
||||
m.Map.Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) ClearExpired(_ context.Context) {
|
||||
func (m *MemoryMapCache[K, V]) ClearExpired(_ context.Context, expire time.Duration) {
|
||||
now := time.Duration(time.Now().UnixNano())
|
||||
|
||||
m.Range(func(k K, v mapVal[V]) bool {
|
||||
if now > time.Duration(v.setTime.UnixNano())+m.expireTime() {
|
||||
if now > time.Duration(v.setTime.UnixNano())+expire {
|
||||
m.Map.Delete(k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Refresh(_ context.Context, k K, a ...any) {
|
||||
v, ok := m.Load(k)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
t := helper.ParseArgs(time.Now(), a...)
|
||||
v.setTime = t
|
||||
m.Store(k, v)
|
||||
}
|
||||
|
14
cache/memorymapcache_test.go
vendored
14
cache/memorymapcache_test.go
vendored
@ -15,7 +15,7 @@ var ttt time.Time
|
||||
|
||||
func init() {
|
||||
ctx = context.Background()
|
||||
mm = *NewMemoryMapCache[string, string](3 * time.Second)
|
||||
mm = *NewMemoryMapCache[string, string]()
|
||||
ttt = time.Now()
|
||||
mm.Store("aa", mapVal[string]{
|
||||
setTime: ttt,
|
||||
@ -53,7 +53,7 @@ func TestMemoryMapCache_ClearExpired(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Println(tt.m)
|
||||
tt.m.ClearExpired(tt.args.in0)
|
||||
tt.m.ClearExpired(tt.args.in0, tt.args.expire)
|
||||
time.Sleep(time.Second)
|
||||
fmt.Println(tt.m)
|
||||
})
|
||||
@ -83,7 +83,7 @@ func TestMemoryMapCache_Delete(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Println(mm.Get(ctx, "aa"))
|
||||
tt.m.Del(tt.args.in0, tt.args.key)
|
||||
tt.m.Delete(tt.args.in0, tt.args.key)
|
||||
fmt.Println(mm.Get(ctx, "aa"))
|
||||
|
||||
})
|
||||
@ -111,7 +111,7 @@ func TestMemoryMapCache_Flush(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.m.Flush(tt.args.in0)
|
||||
mm.Set(ctx, "aa", "xx")
|
||||
mm.Set(ctx, "aa", "xx", time.Second)
|
||||
fmt.Println(mm.Get(ctx, "aa"))
|
||||
})
|
||||
}
|
||||
@ -180,7 +180,7 @@ func TestMemoryMapCache_Set(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.m.Set(tt.args.in0, tt.args.key, tt.args.val)
|
||||
tt.m.Set(tt.args.in0, tt.args.key, tt.args.val, tt.args.in3)
|
||||
fmt.Println(tt.m.Get(ctx, tt.args.key))
|
||||
})
|
||||
}
|
||||
@ -209,7 +209,7 @@ func TestMemoryMapCache_Ttl(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.m.Ttl(tt.args.in0, tt.args.key); got != tt.want {
|
||||
if got := tt.m.Ttl(tt.args.in0, tt.args.key, tt.args.expire); got != tt.want {
|
||||
t.Errorf("Ttl() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
@ -227,7 +227,7 @@ func TestMemoryMapCache_Ver(t *testing.T) {
|
||||
args args[K]
|
||||
want int
|
||||
}
|
||||
mm.Set(ctx, "aa", "ff")
|
||||
mm.Set(ctx, "aa", "ff", time.Second)
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
|
186
cache/pagination.go
vendored
186
cache/pagination.go
vendored
@ -1,186 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Pagination[K comparable, V any] struct {
|
||||
*MapCache[string, helper.PaginationData[V]]
|
||||
maxNum func() int
|
||||
isSwitch *safety.Map[K, bool]
|
||||
dbFn func(ctx context.Context, k K, page, limit, totalRaw int, a ...any) ([]V, int, error)
|
||||
localFn func(ctx context.Context, data []V, k K, page, limit int, a ...any) ([]V, int, error)
|
||||
batchFetchNum func() int
|
||||
localKeyFn func(K K, a ...any) string
|
||||
dbKeyFn func(K K, a ...any) string
|
||||
name string
|
||||
}
|
||||
|
||||
var switchDb = errors.New("switch Db")
|
||||
|
||||
type DbFn[K comparable, V any] func(ctx context.Context, k K, page, limit, totalRaw int, a ...any) ([]V, int, error)
|
||||
|
||||
type LocalFn[K comparable, V any] func(ctx context.Context, data []V, k K, page, limit int, a ...any) ([]V, int, error)
|
||||
|
||||
func (p *Pagination[K, V]) IsSwitchDB(k K) bool {
|
||||
v, _ := p.isSwitch.Load(k)
|
||||
return v == true
|
||||
}
|
||||
|
||||
func NewPagination[K comparable, V any](m *MapCache[string, helper.PaginationData[V]], maxNum func() int,
|
||||
dbFn DbFn[K, V], localFn LocalFn[K, V], dbKeyFn, localKeyFn func(K, ...any) string,
|
||||
batchFetchNum func() int, name string) *Pagination[K, V] {
|
||||
if dbKeyFn == nil {
|
||||
dbKeyFn = func(k K, a ...any) string {
|
||||
s := str.NewBuilder()
|
||||
for _, v := range append([]any{k}, a...) {
|
||||
s.Sprintf("%v|", v)
|
||||
}
|
||||
|
||||
return strings.TrimRight(s.String(), "|")
|
||||
}
|
||||
}
|
||||
if localKeyFn == nil {
|
||||
localKeyFn = func(k K, a ...any) string {
|
||||
return fmt.Sprintf("%v", k)
|
||||
}
|
||||
}
|
||||
return &Pagination[K, V]{
|
||||
MapCache: m,
|
||||
maxNum: maxNum,
|
||||
isSwitch: safety.NewMap[K, bool](),
|
||||
dbFn: dbFn,
|
||||
localFn: localFn,
|
||||
batchFetchNum: batchFetchNum,
|
||||
name: name,
|
||||
dbKeyFn: dbKeyFn,
|
||||
localKeyFn: localKeyFn,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pagination[K, V]) Pagination(ctx context.Context, timeout time.Duration, k K, page, limit int, a ...any) ([]V, int, error) {
|
||||
if is, _ := p.isSwitch.Load(k); is {
|
||||
return p.paginationByDB(ctx, timeout, k, page, limit, 0, a...)
|
||||
}
|
||||
data, total, err := p.paginationByLocal(ctx, timeout, k, page, limit, a...)
|
||||
if err != nil {
|
||||
if errors.Is(err, switchDb) {
|
||||
p.isSwitch.Store(k, true)
|
||||
err = nil
|
||||
return p.paginationByDB(ctx, timeout, k, page, limit, total, a...)
|
||||
}
|
||||
return nil, 0, err
|
||||
}
|
||||
return data, total, err
|
||||
}
|
||||
|
||||
func (p *Pagination[K, V]) paginationByLocal(ctx context.Context, timeout time.Duration, k K, page, limit int, a ...any) ([]V, int, error) {
|
||||
key := p.localKeyFn(k)
|
||||
data, ok := p.Get(ctx, key)
|
||||
if ok {
|
||||
if p.increaseUpdate != nil && p.refresh != nil {
|
||||
dat, err := p.increaseUpdates(ctx, timeout, data, key, a...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if dat.TotalRaw >= p.maxNum() {
|
||||
return nil, 0, switchDb
|
||||
}
|
||||
data = dat
|
||||
}
|
||||
return p.localFn(ctx, data.Data, k, page, limit, a...)
|
||||
}
|
||||
p.mux.Lock()
|
||||
defer p.mux.Unlock()
|
||||
data, ok = p.Get(ctx, key)
|
||||
if ok {
|
||||
return data.Data, data.TotalRaw, nil
|
||||
}
|
||||
batchNum := p.batchFetchNum()
|
||||
da, totalRaw, err := p.fetchDb(ctx, timeout, k, 1, 0, 0, a...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if totalRaw < 1 {
|
||||
data.Data = nil
|
||||
data.TotalRaw = 0
|
||||
p.Set(ctx, key, data)
|
||||
return nil, 0, nil
|
||||
}
|
||||
if totalRaw >= p.maxNum() {
|
||||
return nil, totalRaw, switchDb
|
||||
}
|
||||
totalPage := number.DivideCeil(totalRaw, batchNum)
|
||||
for i := 1; i <= totalPage; i++ {
|
||||
daa, _, err := p.fetchDb(ctx, timeout, k, i, batchNum, totalRaw, a...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
da = append(da, daa...)
|
||||
}
|
||||
data.Data = da
|
||||
data.TotalRaw = totalRaw
|
||||
p.Set(ctx, key, data)
|
||||
|
||||
return p.localFn(ctx, data.Data, k, page, limit, a...)
|
||||
}
|
||||
|
||||
func (p *Pagination[K, V]) dbGet(ctx context.Context, key string) (helper.PaginationData[V], bool) {
|
||||
data, ok := p.Get(ctx, key)
|
||||
if ok && p.increaseUpdate != nil && p.increaseUpdate.CycleTime() > p.GetExpireTime(ctx)-p.Ttl(ctx, key) {
|
||||
return data, true
|
||||
}
|
||||
return data, false
|
||||
}
|
||||
|
||||
func (p *Pagination[K, V]) paginationByDB(ctx context.Context, timeout time.Duration, k K, page, limit, totalRaw int, a ...any) ([]V, int, error) {
|
||||
key := p.dbKeyFn(k, append([]any{page, limit}, a...)...)
|
||||
data, ok := p.dbGet(ctx, key)
|
||||
if ok {
|
||||
return data.Data, data.TotalRaw, nil
|
||||
}
|
||||
p.mux.Lock()
|
||||
defer p.mux.Unlock()
|
||||
data, ok = p.dbGet(ctx, key)
|
||||
if ok {
|
||||
return data.Data, data.TotalRaw, nil
|
||||
}
|
||||
dat, total, err := p.fetchDb(ctx, timeout, k, page, limit, totalRaw, a...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
data.Data, data.TotalRaw = dat, total
|
||||
p.Set(ctx, key, data)
|
||||
return data.Data, data.TotalRaw, err
|
||||
}
|
||||
|
||||
func (p *Pagination[K, V]) fetchDb(ctx context.Context, timeout time.Duration, k K, page, limit, totalRaw int, a ...any) ([]V, int, error) {
|
||||
var data helper.PaginationData[V]
|
||||
var err error
|
||||
fn := func() {
|
||||
da, total, er := p.dbFn(ctx, k, page, limit, totalRaw, a...)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
data.Data = da
|
||||
data.TotalRaw = total
|
||||
}
|
||||
if timeout > 0 {
|
||||
er := helper.RunFnWithTimeout(ctx, timeout, fn, fmt.Sprintf("fetch %s-[%v]-page[%d]-limit[%d] from db fail", p.name, k, page, limit))
|
||||
if err == nil && er != nil {
|
||||
err = er
|
||||
}
|
||||
} else {
|
||||
fn()
|
||||
}
|
||||
return data.Data, data.TotalRaw, err
|
||||
}
|
544
cache/reload/reload.go
vendored
544
cache/reload/reload.go
vendored
@ -1,544 +0,0 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Queue struct {
|
||||
Fn func()
|
||||
Order float64
|
||||
Name string
|
||||
AutoExec bool
|
||||
Once bool
|
||||
}
|
||||
|
||||
var mut = &sync.Mutex{}
|
||||
|
||||
func GetGlobeMutex() *sync.Mutex {
|
||||
return mut
|
||||
}
|
||||
|
||||
var reloadQueues = safety.NewSlice[Queue]()
|
||||
|
||||
var reloadQueueHookFns = safety.NewVar[[]func(queue Queue) (Queue, bool)](nil)
|
||||
|
||||
var setFnVal = safety.NewMap[string, any]()
|
||||
|
||||
func DeleteReloadQueue(names ...string) {
|
||||
hooks := reloadQueueHookFns.Load()
|
||||
for _, name := range names {
|
||||
hooks = append(hooks, func(queue Queue) (Queue, bool) {
|
||||
if name != queue.Name {
|
||||
return queue, true
|
||||
}
|
||||
return queue, false
|
||||
})
|
||||
}
|
||||
reloadQueueHookFns.Store(hooks)
|
||||
}
|
||||
|
||||
func HookReloadQueue(fn func(queue Queue) (Queue, bool)) {
|
||||
a := reloadQueueHookFns.Load()
|
||||
a = append(a, fn)
|
||||
reloadQueueHookFns.Store(a)
|
||||
}
|
||||
|
||||
func GetReloadFn(name string) func() {
|
||||
hookQueue()
|
||||
i, queue := slice.SearchFirst(reloadQueues.Load(), func(queue Queue) bool {
|
||||
return queue.Name == name
|
||||
})
|
||||
if i > -1 && queue.Fn != nil {
|
||||
return queue.Fn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hookQueue() {
|
||||
hooks := reloadQueueHookFns.Load()
|
||||
queues := reloadQueues.Load()
|
||||
length := len(queues)
|
||||
for _, hook := range hooks {
|
||||
queues = slice.FilterAndMap(queues, hook)
|
||||
}
|
||||
if len(queues) != length {
|
||||
reloadQueues.Store(queues)
|
||||
}
|
||||
reloadQueueHookFns.Flush()
|
||||
}
|
||||
|
||||
type SafetyVar[T, A any] struct {
|
||||
Val *safety.Var[Val[T]]
|
||||
Mutex sync.Mutex
|
||||
}
|
||||
type Val[T any] struct {
|
||||
V T
|
||||
Ok bool
|
||||
}
|
||||
type SafetyMap[K comparable, V, A any] struct {
|
||||
Val *safety.Map[K, V]
|
||||
Mutex sync.Mutex
|
||||
}
|
||||
|
||||
var safetyMaps = safety.NewMap[string, any]()
|
||||
var safetyMapLock = sync.Mutex{}
|
||||
|
||||
var deleteMapFn = safety.NewMap[string, func(any)]()
|
||||
|
||||
// GetValMap can get stored map value with namespace which called BuildSafetyMap, BuildMapFnWithConfirm, BuildMapFn, BuildMapFnWithAnyParams
|
||||
func GetValMap[K comparable, V any](namespace string) (*safety.Map[K, V], bool) {
|
||||
m, ok := safetyMaps.Load(namespace)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
v, ok := m.(*safety.Map[K, V])
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func DeleteMapVal[T any](namespace string, key ...T) {
|
||||
fn, ok := deleteMapFn.Load(namespace)
|
||||
if !ok || len(key) < 1 {
|
||||
return
|
||||
}
|
||||
fn(key)
|
||||
}
|
||||
|
||||
func Reloads(namespaces ...string) {
|
||||
mut.Lock()
|
||||
defer mut.Unlock()
|
||||
hookQueue()
|
||||
queues := reloadQueues.Load()
|
||||
for _, name := range namespaces {
|
||||
i, queue := slice.SearchFirst(queues, func(queue Queue) bool {
|
||||
return name == queue.Name
|
||||
})
|
||||
if i < 0 {
|
||||
continue
|
||||
}
|
||||
queue.Fn()
|
||||
if queue.Once {
|
||||
slice.Delete(&queues, i)
|
||||
reloadQueues.Store(queues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BuildMapFnWithConfirm same as BuildMapFn
|
||||
func BuildMapFnWithConfirm[K comparable, V, A any](namespace string, fn func(A) (V, bool), a ...any) func(key K, args A) V {
|
||||
m := BuildSafetyMap[K, V, A](namespace, a...)
|
||||
return func(key K, a A) V {
|
||||
v, ok := m.Val.Load(key)
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
m.Mutex.Lock()
|
||||
defer m.Mutex.Unlock()
|
||||
v, ok = m.Val.Load(key)
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
v, ok = fn(a)
|
||||
if ok {
|
||||
m.Val.Store(key, v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// BuildMapFn build given fn with a new fn which returned value can be saved and flushed when called Reload or Reloads
|
||||
// with namespace
|
||||
//
|
||||
// if give a float then can be reloaded early or lately, more bigger more earlier
|
||||
//
|
||||
// if give a bool false will not flushed when called Reload, then can called GetValMap to flush manually
|
||||
func BuildMapFn[K comparable, V, A any](namespace string, fn func(A) V, a ...any) func(key K, args A) V {
|
||||
m := BuildSafetyMap[K, V, A](namespace, a...)
|
||||
return func(key K, a A) V {
|
||||
v, ok := m.Val.Load(key)
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
m.Mutex.Lock()
|
||||
defer m.Mutex.Unlock()
|
||||
v, ok = m.Val.Load(key)
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
v = fn(a)
|
||||
m.Val.Store(key, v)
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// BuildMapFnWithAnyParams same as BuildMapFn use multiple params
|
||||
func BuildMapFnWithAnyParams[K comparable, V any](namespace string, fn func(...any) V, a ...any) func(key K, a ...any) V {
|
||||
m := BuildSafetyMap[K, V, any](namespace, a...)
|
||||
return func(key K, a ...any) V {
|
||||
v, ok := m.Val.Load(key)
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
m.Mutex.Lock()
|
||||
defer m.Mutex.Unlock()
|
||||
v, ok = m.Val.Load(key)
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
v = fn(a...)
|
||||
m.Val.Store(key, v)
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func BuildSafetyMap[K comparable, V, A any](namespace string, args ...any) *SafetyMap[K, V, A] {
|
||||
vv, ok := safetyMaps.Load(namespace)
|
||||
var m *SafetyMap[K, V, A]
|
||||
if ok {
|
||||
m = vv.(*SafetyMap[K, V, A])
|
||||
return m
|
||||
}
|
||||
safetyMapLock.Lock()
|
||||
defer safetyMapLock.Unlock()
|
||||
vv, ok = safetyMaps.Load(namespace)
|
||||
if ok {
|
||||
m = vv.(*SafetyMap[K, V, A])
|
||||
return m
|
||||
}
|
||||
m = &SafetyMap[K, V, A]{safety.NewMap[K, V](), sync.Mutex{}}
|
||||
args = append(args, namespace)
|
||||
deleteMapFn.Store(namespace, func(a any) {
|
||||
k, ok := a.([]K)
|
||||
if !ok && len(k) > 0 {
|
||||
return
|
||||
}
|
||||
for _, key := range k {
|
||||
m.Val.Delete(key)
|
||||
}
|
||||
})
|
||||
Append(m.Val.Flush, args...)
|
||||
safetyMaps.Store(namespace, m)
|
||||
return m
|
||||
}
|
||||
|
||||
func GetAnyValMapBy[K comparable, V, A any](namespace string, key K, a A, fn func(A) (V, bool), args ...any) V {
|
||||
m := BuildSafetyMap[K, V, A](namespace, args...)
|
||||
v, ok := m.Val.Load(key)
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
m.Mutex.Lock()
|
||||
defer m.Mutex.Unlock()
|
||||
v, ok = m.Val.Load(key)
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
v, ok = fn(a)
|
||||
if ok {
|
||||
m.Val.Store(key, v)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func BuildAnyVal[T, A any](namespace string, args ...any) *SafetyVar[T, A] {
|
||||
var vv *SafetyVar[T, A]
|
||||
vvv, ok := safetyMaps.Load(namespace)
|
||||
if ok {
|
||||
vv = vvv.(*SafetyVar[T, A])
|
||||
}
|
||||
safetyMapLock.Lock()
|
||||
defer safetyMapLock.Unlock()
|
||||
vvv, ok = safetyMaps.Load(namespace)
|
||||
if ok {
|
||||
vv = vvv.(*SafetyVar[T, A])
|
||||
return vv
|
||||
}
|
||||
v := Val[T]{}
|
||||
vv = &SafetyVar[T, A]{safety.NewVar(v), sync.Mutex{}}
|
||||
args = append(args, namespace)
|
||||
Append(vv.Val.Flush, args...)
|
||||
safetyMaps.Store(namespace, vv)
|
||||
return vv
|
||||
}
|
||||
|
||||
func GetAnyValBys[T, A any](namespace string, a A, fn func(A) (T, bool), args ...any) T {
|
||||
var vv = BuildAnyVal[T, A](namespace, args...)
|
||||
v := vv.Val.Load()
|
||||
if v.Ok {
|
||||
return v.V
|
||||
}
|
||||
vv.Mutex.Lock()
|
||||
defer vv.Mutex.Unlock()
|
||||
v = vv.Val.Load()
|
||||
if v.Ok {
|
||||
return v.V
|
||||
}
|
||||
v.V, v.Ok = fn(a)
|
||||
vv.Val.Store(v)
|
||||
return v.V
|
||||
}
|
||||
|
||||
// BuildValFnWithConfirm same as BuildValFn
|
||||
//
|
||||
// if give a int and value bigger than 1 will be a times which built fn called return false
|
||||
func BuildValFnWithConfirm[T, A any](namespace string, fn func(A) (T, bool), args ...any) func(A) T {
|
||||
var vv = BuildAnyVal[T, A](namespace, args...)
|
||||
tryTimes := helper.ParseArgs(1, args...)
|
||||
var counter int64
|
||||
if tryTimes > 1 {
|
||||
Append(func() {
|
||||
atomic.StoreInt64(&counter, 0)
|
||||
}, str.Join("reload-valFn-counter-", namespace))
|
||||
}
|
||||
return func(a A) T {
|
||||
v := vv.Val.Load()
|
||||
if v.Ok {
|
||||
return v.V
|
||||
}
|
||||
vv.Mutex.Lock()
|
||||
defer vv.Mutex.Unlock()
|
||||
v = vv.Val.Load()
|
||||
if v.Ok {
|
||||
return v.V
|
||||
}
|
||||
v.V, v.Ok = fn(a)
|
||||
if v.Ok {
|
||||
vv.Val.Store(v)
|
||||
return v.V
|
||||
}
|
||||
if atomic.LoadInt64(&counter) <= 1 {
|
||||
return v.V
|
||||
}
|
||||
atomic.AddInt64(&counter, 1)
|
||||
if atomic.LoadInt64(&counter) >= int64(tryTimes) {
|
||||
v.Ok = true
|
||||
vv.Val.Store(v)
|
||||
}
|
||||
return v.V
|
||||
}
|
||||
}
|
||||
|
||||
// BuildValFn build given fn a new fn which return value can be saved and flushed when called Reload or Reloads
|
||||
// with namespace.
|
||||
//
|
||||
// note: namespace should be not same as BuildMapFn and related fn, they stored same safety.Map[string,any].
|
||||
//
|
||||
// if give a float then can be reloaded early or lately, more bigger more earlier
|
||||
//
|
||||
// if give a bool false will not flushed when called Reload, but can call GetValMap or Reloads to flush manually
|
||||
func BuildValFn[T, A any](namespace string, fn func(A) T, args ...any) func(A) T {
|
||||
var vv = BuildAnyVal[T, A](namespace, args...)
|
||||
return func(a A) T {
|
||||
v := vv.Val.Load()
|
||||
if v.Ok {
|
||||
return v.V
|
||||
}
|
||||
vv.Mutex.Lock()
|
||||
defer vv.Mutex.Unlock()
|
||||
v = vv.Val.Load()
|
||||
if v.Ok {
|
||||
return v.V
|
||||
}
|
||||
v.V = fn(a)
|
||||
v.Ok = true
|
||||
vv.Val.Store(v)
|
||||
return v.V
|
||||
}
|
||||
}
|
||||
|
||||
// BuildValFnWithAnyParams same as BuildValFn use multiple params
|
||||
func BuildValFnWithAnyParams[T any](namespace string, fn func(...any) T, args ...any) func(...any) T {
|
||||
var vv = BuildAnyVal[T, any](namespace, args...)
|
||||
return func(a ...any) T {
|
||||
v := vv.Val.Load()
|
||||
if v.Ok {
|
||||
return v.V
|
||||
}
|
||||
vv.Mutex.Lock()
|
||||
defer vv.Mutex.Unlock()
|
||||
v = vv.Val.Load()
|
||||
if v.Ok {
|
||||
return v.V
|
||||
}
|
||||
v.V = fn(a...)
|
||||
v.Ok = true
|
||||
vv.Val.Store(v)
|
||||
return v.V
|
||||
}
|
||||
}
|
||||
|
||||
// Vars get default value and whenever reloaded assign default value
|
||||
//
|
||||
// args same as Append
|
||||
//
|
||||
// if give a name, then can be flushed by calls Reloads
|
||||
//
|
||||
// if give a float then can be reloaded early or lately, more bigger more earlier
|
||||
//
|
||||
// if give a bool false will not flushed when called Reload, but can call GetValMap or Reloads to flush manually
|
||||
//
|
||||
// if give a int 1 will only execute once when called Reload or Reloads and then delete the flush fn
|
||||
func Vars[T any](defaults T, args ...any) *safety.Var[T] {
|
||||
ss := safety.NewVar(defaults)
|
||||
|
||||
Append(func() {
|
||||
ss.Store(defaults)
|
||||
}, args...)
|
||||
return ss
|
||||
}
|
||||
|
||||
func parseArgs(a ...any) (ord float64, name string) {
|
||||
if len(a) > 0 {
|
||||
for _, arg := range a {
|
||||
v, ok := arg.(float64)
|
||||
if ok {
|
||||
ord = v
|
||||
}
|
||||
vv, ok := arg.(string)
|
||||
if ok {
|
||||
name = vv
|
||||
}
|
||||
}
|
||||
}
|
||||
return ord, name
|
||||
}
|
||||
|
||||
// VarsBy
|
||||
//
|
||||
// args same as Append
|
||||
// if give a name, then can be flushed by calls Reloads
|
||||
func VarsBy[T any](fn func() T, args ...any) *safety.Var[T] {
|
||||
ss := safety.NewVar(fn())
|
||||
Append(func() {
|
||||
ss.Store(fn())
|
||||
}, args...)
|
||||
return ss
|
||||
}
|
||||
func MapBy[K comparable, T any](fn func(*safety.Map[K, T]), args ...any) *safety.Map[K, T] {
|
||||
m := safety.NewMap[K, T]()
|
||||
if fn != nil {
|
||||
fn(m)
|
||||
}
|
||||
Append(func() {
|
||||
m.Flush()
|
||||
if fn != nil {
|
||||
fn(m)
|
||||
}
|
||||
}, args...)
|
||||
return m
|
||||
}
|
||||
|
||||
func SafeMap[K comparable, T any](args ...any) *safety.Map[K, T] {
|
||||
m := safety.NewMap[K, T]()
|
||||
Append(m.Flush, args...)
|
||||
return m
|
||||
}
|
||||
|
||||
// Append the func that will be called whenever Reload called
|
||||
//
|
||||
// if give a name, then can be called by called Reloads
|
||||
//
|
||||
// if give a float then can be called early or lately when called Reload, more bigger more earlier
|
||||
//
|
||||
// if give a bool false will not execute when called Reload, then can called Reloads to execute manually
|
||||
//
|
||||
// if give a int 1 will only execute once when called Reload or Reloads and then delete the Queue
|
||||
func Append(fn func(), a ...any) {
|
||||
ord, name := parseArgs(a...)
|
||||
autoExec := helper.ParseArgs(true, a...)
|
||||
once := helper.ParseArgs(0, a...)
|
||||
queues := reloadQueues.Load()
|
||||
queue := Queue{fn, ord, name, autoExec, once == 1}
|
||||
if name != "" {
|
||||
i, _ := slice.SearchFirst(queues, func(queue Queue) bool {
|
||||
return queue.Name == name
|
||||
})
|
||||
if i > -1 {
|
||||
queues[i] = queue
|
||||
reloadQueues.Store(queues)
|
||||
return
|
||||
}
|
||||
}
|
||||
reloadQueues.Append(queue)
|
||||
}
|
||||
|
||||
// AppendOnceFn function and args same as Append, but func will execute only once when called Reload or Reloads and then will be deleted. Especially suitable for using to develop plugins, when uninstall plugin can clean or recover some progress's self relative data or behavior which was changed by plugin.
|
||||
func AppendOnceFn(fn func(), a ...any) {
|
||||
a = append([]any{1}, a...)
|
||||
Append(fn, a...)
|
||||
}
|
||||
|
||||
func Reload() {
|
||||
mut.Lock()
|
||||
defer mut.Unlock()
|
||||
deleteMapFn.Flush()
|
||||
hookQueue()
|
||||
queues := reloadQueues.Load()
|
||||
length := len(queues)
|
||||
slice.SimpleSort(queues, slice.DESC, func(t Queue) float64 {
|
||||
return t.Order
|
||||
})
|
||||
for i, queue := range queues {
|
||||
if !queue.AutoExec {
|
||||
continue
|
||||
}
|
||||
queue.Fn()
|
||||
if queue.Once {
|
||||
slice.Delete(&queues, i)
|
||||
}
|
||||
}
|
||||
if length != len(queues) {
|
||||
reloadQueues.Store(queues)
|
||||
}
|
||||
}
|
||||
|
||||
type Any[T any] struct {
|
||||
fn func() T
|
||||
v *safety.Var[T]
|
||||
isManual *safety.Var[bool]
|
||||
}
|
||||
|
||||
// BuildFnVal build a new fn which can be set value by SetFnVal with name or set default value by given fn when called Reload
|
||||
func BuildFnVal[T any](name string, t T, fn func() T) func() T {
|
||||
if fn == nil {
|
||||
fn = func() T {
|
||||
return t
|
||||
}
|
||||
} else {
|
||||
t = fn()
|
||||
}
|
||||
p := safety.NewVar(t)
|
||||
e := Any[T]{
|
||||
fn: fn,
|
||||
v: p,
|
||||
isManual: safety.NewVar(false),
|
||||
}
|
||||
Append(func() {
|
||||
if !e.isManual.Load() {
|
||||
e.v.Store(fn())
|
||||
}
|
||||
}, str.Join("fnval-", name))
|
||||
setFnVal.Store(name, e)
|
||||
return func() T {
|
||||
return e.v.Load()
|
||||
}
|
||||
}
|
||||
|
||||
func SetFnVal[T any](name string, val T, onlyManual bool) {
|
||||
v, ok := setFnVal.Load(name)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
vv, ok := v.(Any[T])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if onlyManual && !vv.isManual.Load() {
|
||||
vv.isManual.Store(true)
|
||||
}
|
||||
vv.v.Store(val)
|
||||
}
|
47
cache/reload/reload_test.go
vendored
47
cache/reload/reload_test.go
vendored
@ -1,47 +0,0 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlushMapVal(t *testing.T) {
|
||||
t.Run("t1", func(t *testing.T) {
|
||||
c := 0
|
||||
v := GetAnyValMapBy("key", 2, struct{}{}, func(a struct{}) (int, bool) {
|
||||
c++
|
||||
return 33, true
|
||||
})
|
||||
fmt.Println(v)
|
||||
DeleteMapVal("key", 2)
|
||||
|
||||
v = GetAnyValMapBy("key", 2, struct{}{}, func(a struct{}) (int, bool) {
|
||||
fmt.Println("xxxxx")
|
||||
return 33, true
|
||||
})
|
||||
fmt.Println(v)
|
||||
Reloads("key")
|
||||
v = GetAnyValMapBy[int, int, struct{}]("key", 2, struct{}{}, func(a struct{}) (int, bool) {
|
||||
fmt.Println("yyyy")
|
||||
return 33, true
|
||||
})
|
||||
fmt.Println(v)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAnyMapFnBys(t *testing.T) {
|
||||
var i int
|
||||
t.Run("t1", func(t *testing.T) {
|
||||
v := BuildMapFnWithConfirm[int]("name", func(a int) (int, bool) {
|
||||
i++
|
||||
return a + 1, true
|
||||
})
|
||||
vv := v(1, 2)
|
||||
vvv := v(2, 3)
|
||||
fmt.Println(vv, vvv)
|
||||
v(1, 2)
|
||||
DeleteMapVal("name", 2)
|
||||
v(2, 3)
|
||||
fmt.Println(i)
|
||||
})
|
||||
}
|
251
cache/vars.go
vendored
251
cache/vars.go
vendored
@ -2,197 +2,98 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type VarCache[T any] struct {
|
||||
AnyCache[T]
|
||||
setCacheFunc func(context.Context, ...any) (T, error)
|
||||
mutex sync.Mutex
|
||||
increaseUpdate *IncreaseUpdateVar[T]
|
||||
refresh RefreshVar[T]
|
||||
get func(ctx context.Context) (T, bool)
|
||||
set func(ctx context.Context, v T)
|
||||
flush func(ctx context.Context)
|
||||
getLastSetTime func(ctx context.Context) time.Time
|
||||
}
|
||||
|
||||
type IncreaseUpdateVar[T any] struct {
|
||||
CycleTime func() time.Duration
|
||||
Fn IncreaseVarFn[T]
|
||||
}
|
||||
|
||||
type IncreaseVarFn[T any] func(c context.Context, currentData T, t time.Time, a ...any) (data T, save bool, refresh bool, err error)
|
||||
|
||||
func (t *VarCache[T]) Get(ctx context.Context) (T, bool) {
|
||||
return t.get(ctx)
|
||||
}
|
||||
func (t *VarCache[T]) Set(ctx context.Context, v T) {
|
||||
t.set(ctx, v)
|
||||
}
|
||||
func (t *VarCache[T]) Flush(ctx context.Context) {
|
||||
t.flush(ctx)
|
||||
}
|
||||
func (t *VarCache[T]) GetLastSetTime(ctx context.Context) time.Time {
|
||||
return t.getLastSetTime(ctx)
|
||||
}
|
||||
|
||||
func initVarCache[T any](t *VarCache[T], a ...any) {
|
||||
gets := helper.ParseArgs[func(AnyCache[T], context.Context) (T, bool)](nil, a...)
|
||||
if gets == nil {
|
||||
t.get = t.AnyCache.Get
|
||||
} else {
|
||||
t.get = func(ctx context.Context) (T, bool) {
|
||||
return gets(t.AnyCache, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
set := helper.ParseArgs[func(AnyCache[T], context.Context, T)](nil, a...)
|
||||
if set == nil {
|
||||
t.set = t.AnyCache.Set
|
||||
} else {
|
||||
t.set = func(ctx context.Context, v T) {
|
||||
set(t.AnyCache, ctx, v)
|
||||
}
|
||||
}
|
||||
|
||||
flush := helper.ParseArgs[func(AnyCache[T], context.Context)](nil, a...)
|
||||
if flush == nil {
|
||||
t.flush = t.AnyCache.Flush
|
||||
} else {
|
||||
t.flush = func(ctx context.Context) {
|
||||
flush(t.AnyCache, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
getLastSetTime := helper.ParseArgs[func(AnyCache[T], context.Context) time.Time](nil, a...)
|
||||
if getLastSetTime == nil {
|
||||
t.getLastSetTime = t.AnyCache.GetLastSetTime
|
||||
} else {
|
||||
t.getLastSetTime = func(ctx context.Context) time.Time {
|
||||
return getLastSetTime(t.AnyCache, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewVarCache[T any](cache AnyCache[T], fn func(context.Context, ...any) (T, error), inc *IncreaseUpdateVar[T], ref RefreshVar[T], a ...any) *VarCache[T] {
|
||||
r := &VarCache[T]{
|
||||
AnyCache: cache, setCacheFunc: fn, mutex: sync.Mutex{},
|
||||
increaseUpdate: inc,
|
||||
refresh: ref,
|
||||
}
|
||||
initVarCache(r, a...)
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *VarCache[T]) GetCache(ctx context.Context, timeout time.Duration, params ...any) (T, error) {
|
||||
data, ok := t.Get(ctx)
|
||||
var err error
|
||||
if ok {
|
||||
if t.increaseUpdate != nil && t.refresh != nil {
|
||||
nowTime := time.Now()
|
||||
if t.increaseUpdate.CycleTime() > nowTime.Sub(t.GetLastSetTime(ctx)) {
|
||||
return data, nil
|
||||
}
|
||||
fn := func() {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
da, save, refresh, er := t.increaseUpdate.Fn(ctx, data, t.GetLastSetTime(ctx), params...)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
if save {
|
||||
t.Set(ctx, da)
|
||||
}
|
||||
if refresh {
|
||||
t.refresh.Refresh(ctx, params...)
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
er := helper.RunFnWithTimeout(ctx, timeout, fn, "increaseUpdate cache fail")
|
||||
if err == nil && er != nil {
|
||||
err = er
|
||||
}
|
||||
} else {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
call := func() {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
dat, ok := t.Get(ctx)
|
||||
if ok {
|
||||
data = dat
|
||||
return
|
||||
}
|
||||
r, er := t.setCacheFunc(ctx, params...)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
t.Set(ctx, r)
|
||||
data = r
|
||||
}
|
||||
if timeout > 0 {
|
||||
er := helper.RunFnWithTimeout(ctx, timeout, call, "get cache fail")
|
||||
if err == nil && er != nil {
|
||||
err = er
|
||||
}
|
||||
} else {
|
||||
call()
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
type VarMemoryCache[T any] struct {
|
||||
v *safety.Var[vars[T]]
|
||||
expireTime func() time.Duration
|
||||
}
|
||||
|
||||
func (c *VarMemoryCache[T]) ClearExpired(ctx context.Context) {
|
||||
_, ok := c.Get(ctx)
|
||||
if !ok {
|
||||
c.Flush(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func NewVarMemoryCache[T any](expireTime func() time.Duration) *VarMemoryCache[T] {
|
||||
return &VarMemoryCache[T]{v: safety.NewVar(vars[T]{}), expireTime: expireTime}
|
||||
}
|
||||
|
||||
func (c *VarMemoryCache[T]) Get(_ context.Context) (T, bool) {
|
||||
v := c.v.Load()
|
||||
return v.data, c.expireTime() >= time.Now().Sub(v.setTime)
|
||||
}
|
||||
|
||||
func (c *VarMemoryCache[T]) Set(_ context.Context, v T) {
|
||||
vv := c.v.Load()
|
||||
vv.data = v
|
||||
vv.setTime = time.Now()
|
||||
vv.incr++
|
||||
c.v.Store(vv)
|
||||
}
|
||||
|
||||
func (c *VarMemoryCache[T]) SetExpiredTime(f func() time.Duration) {
|
||||
c.expireTime = f
|
||||
}
|
||||
|
||||
type vars[T any] struct {
|
||||
data T
|
||||
mutex *sync.Mutex
|
||||
setCacheFunc func(...any) (T, error)
|
||||
expireTime time.Duration
|
||||
setTime time.Time
|
||||
incr int
|
||||
}
|
||||
|
||||
func (c *VarMemoryCache[T]) GetLastSetTime(_ context.Context) time.Time {
|
||||
func (c *VarCache[T]) GetLastSetTime() time.Time {
|
||||
return c.v.Load().setTime
|
||||
}
|
||||
|
||||
func (c *VarMemoryCache[T]) Flush(_ context.Context) {
|
||||
c.v.Flush()
|
||||
func NewVarCache[T any](fun func(...any) (T, error), duration time.Duration) *VarCache[T] {
|
||||
return &VarCache[T]{
|
||||
v: safety.NewVar(vars[T]{
|
||||
mutex: &sync.Mutex{},
|
||||
setCacheFunc: fun,
|
||||
expireTime: duration,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *VarCache[T]) IsExpired() bool {
|
||||
v := c.v.Load()
|
||||
return time.Duration(v.setTime.UnixNano())+v.expireTime < time.Duration(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func (c *VarCache[T]) Flush() {
|
||||
v := c.v.Load()
|
||||
mu := v.mutex
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
var vv T
|
||||
v.data = vv
|
||||
c.v.Store(v)
|
||||
}
|
||||
|
||||
func (c *VarCache[T]) GetCache(ctx context.Context, timeout time.Duration, params ...any) (T, error) {
|
||||
v := c.v.Load()
|
||||
data := v.data
|
||||
var err error
|
||||
if v.expireTime <= 0 || ((time.Duration(v.setTime.UnixNano()) + v.expireTime) < time.Duration(time.Now().UnixNano())) {
|
||||
t := v.incr
|
||||
call := func() {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
vv := c.v.Load()
|
||||
if vv.incr > t {
|
||||
return
|
||||
}
|
||||
r, er := vv.setCacheFunc(params...)
|
||||
if err != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
vv.setTime = time.Now()
|
||||
vv.data = r
|
||||
data = r
|
||||
vv.incr++
|
||||
c.v.Store(vv)
|
||||
}
|
||||
if timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
done := make(chan struct{}, 1)
|
||||
go func() {
|
||||
call()
|
||||
done <- struct{}{}
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = errors.New(fmt.Sprintf("get cache %s", ctx.Err().Error()))
|
||||
case <-done:
|
||||
|
||||
}
|
||||
} else {
|
||||
call()
|
||||
}
|
||||
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
41
cache/vars_test.go
vendored
41
cache/vars_test.go
vendored
@ -7,11 +7,9 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var cc = *NewVarCache[int](NewVarMemoryCache[int](func() time.Duration {
|
||||
return time.Minute
|
||||
}), func(ctx context.Context, a ...any) (int, error) {
|
||||
var cc = *NewVarCache(func(a ...any) (int, error) {
|
||||
return 1, nil
|
||||
})
|
||||
}, time.Minute)
|
||||
|
||||
func TestVarCache_Flush(t *testing.T) {
|
||||
type testCase[T any] struct {
|
||||
@ -28,8 +26,41 @@ func TestVarCache_Flush(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Println(tt.c.GetCache(c, time.Second))
|
||||
tt.c.Flush(ctx)
|
||||
tt.c.Flush()
|
||||
fmt.Println(tt.c.GetCache(c, time.Second))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVarCache_IsExpired(t *testing.T) {
|
||||
type testCase[T any] struct {
|
||||
name string
|
||||
c VarCache[T]
|
||||
want bool
|
||||
}
|
||||
tests := []testCase[int]{
|
||||
{
|
||||
name: "expired",
|
||||
c: cc,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "not expired",
|
||||
c: func() VarCache[int] {
|
||||
v := *NewVarCache(func(a ...any) (int, error) {
|
||||
return 1, nil
|
||||
}, time.Minute)
|
||||
_, _ = v.GetCache(context.Background(), time.Second)
|
||||
return v
|
||||
}(),
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.c.IsExpired(); got != tt.want {
|
||||
t.Errorf("IsExpired() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,81 +0,0 @@
|
||||
{
|
||||
"mysql": {
|
||||
"dsn": {
|
||||
"host": "localhost",
|
||||
"port": 3306,
|
||||
"db": "wordpress",
|
||||
"user": "root",
|
||||
"password": "root",
|
||||
"charset": "utf8mb4"
|
||||
},
|
||||
"pool": {
|
||||
"connMaxIdleTime": 60,
|
||||
"maxOpenConn": 100,
|
||||
"maxIdleConn": 10,
|
||||
"connMaxLifetime": 236
|
||||
}
|
||||
},
|
||||
"Mail": {
|
||||
"user": "xx@163.com",
|
||||
"alias": "xx",
|
||||
"pass": null,
|
||||
"host": "smtp.163.com",
|
||||
"port": 465,
|
||||
"insecureSkipVerify": false
|
||||
},
|
||||
"ssl": {
|
||||
"cert": "",
|
||||
"key": ""
|
||||
},
|
||||
"cacheTime": {
|
||||
"cacheControl": "72h",
|
||||
"recentPostCacheTime": "5m",
|
||||
"categoryCacheTime": "5m",
|
||||
"contextPostCacheTime": "10h",
|
||||
"recentCommentsCacheTime": "5m",
|
||||
"digestCacheTime": "5m",
|
||||
"postListCacheTime": "1h",
|
||||
"searchPostCacheTime": "5m",
|
||||
"monthPostCacheTime": "1h",
|
||||
"postDataCacheTime": "1h",
|
||||
"postCommentsCacheTime": "5m",
|
||||
"crontabClearCacheTime": "5m",
|
||||
"maxPostIdCacheTime": "1h",
|
||||
"userInfoCacheTime": "24h",
|
||||
"commentsCacheTime": "24h",
|
||||
"sleepTime": [
|
||||
"1s",
|
||||
"3s"
|
||||
]
|
||||
},
|
||||
"digestWordCount": 300,
|
||||
"digestTag": "<a><b><blockquote><br><cite><code><dd><del><div><dl><dt><em><h1><h2><h3><h4><h5><h6><i><img><li><ol><p><pre><span><strong><ul>",
|
||||
"maxRequestSleepNum": 100,
|
||||
"maxRequestNum": 500,
|
||||
"singleIpSearchNum": 10,
|
||||
"logOutput": "err.log",
|
||||
"gzip": false,
|
||||
"postCommentUrl": "http://127.0.0.1/wp-comments-post.php",
|
||||
"trustIps": [],
|
||||
"paginationStep": 1,
|
||||
"showQuerySql": false,
|
||||
"trustServerNames": [
|
||||
"xy.test",
|
||||
"blog.xy.test"
|
||||
],
|
||||
"theme": "twentyfifteen",
|
||||
"postOrder": "desc",
|
||||
"wpDir": "/var/www/html/wordpress",
|
||||
"pprof": "/debug/pprof",
|
||||
"plugins": [
|
||||
"enlightjs"
|
||||
],
|
||||
"pluginPath": "./plugins",
|
||||
"listPagePlugins": [
|
||||
"digest"
|
||||
],
|
||||
"externScript": [
|
||||
"",
|
||||
""
|
||||
]
|
||||
}
|
@ -19,9 +19,9 @@ Mail:
|
||||
user: xx@163.com
|
||||
alias: xx
|
||||
pass:
|
||||
host: smtp.163.com
|
||||
port: 465
|
||||
insecureSkipVerify: false
|
||||
Host: smtp.163.com
|
||||
Port: 465
|
||||
Ssl: true
|
||||
|
||||
ssl:
|
||||
cert: ""
|
||||
@ -58,55 +58,12 @@ cacheTime:
|
||||
userInfoCacheTime: 24h
|
||||
# 单独评论缓存时间
|
||||
commentsCacheTime: 24h
|
||||
# 评论增量更新时间
|
||||
commentsIncreaseUpdateTime: 30s
|
||||
# 主题的页眉图片缓存时间
|
||||
themeHeaderImagCacheTime: 5m
|
||||
# 随机sleep时间
|
||||
sleepTime: [ 1s,3s ]
|
||||
# 摘要字数 >0截取指定字数 =0输出出空字符 <0为不截取,原样输出
|
||||
# 摘要字数
|
||||
digestWordCount: 300
|
||||
# 摘要允许的标签 默认为<a><b><blockquote><br><cite><code><dd><del><div><dl><dt><em><h1><h2><h3><h4><h5><h6><i><img><li><ol><p><pre><span><strong><ul>
|
||||
digestTag: "<a><b><blockquote><br><cite><code><dd><del><div><dl><dt><em><h1><h2><h3><h4><h5><h6><i><img><li><ol><p><pre><span><strong><ul>"
|
||||
|
||||
# 设置html转义实体正则 the html coded character set regex file: plugin/digest/digest.go:12
|
||||
#digestRegex: ""*|&*|<*|>*| *|[*|]*| *"
|
||||
|
||||
# 可以设置每个标签或者转义字符占用的字数,默认都为0 set tag or escape character occupied num, default every tag occupied 0
|
||||
#digestTagOccupyNum: [
|
||||
# {
|
||||
# tag: "<top>", # 最外层固定tag outermost immovable tag
|
||||
# num: 0,
|
||||
# chuckOvered: false,
|
||||
# escapeCharacter: [
|
||||
# {
|
||||
# character: [ "\n","\r","\t" ],
|
||||
# num: 0
|
||||
# },
|
||||
# ]
|
||||
# },{
|
||||
# tag: "<img>",
|
||||
# num: 1,
|
||||
# chuckOvered: false
|
||||
# },
|
||||
# {
|
||||
# tag: "<pre><code>",
|
||||
# num: 0,
|
||||
# escapeCharacter: [
|
||||
# {
|
||||
# character: ["\t"],
|
||||
# num: 4,
|
||||
# chuckOvered: false,
|
||||
# },
|
||||
# {
|
||||
# character: ["\n","\r"],
|
||||
# num: 1
|
||||
# },
|
||||
# {
|
||||
# tags: "<br>",
|
||||
# num: 1
|
||||
# },
|
||||
# ]
|
||||
# },
|
||||
#]
|
||||
|
||||
# 到达指定并发请求数时随机sleep
|
||||
maxRequestSleepNum: 100
|
||||
@ -114,12 +71,12 @@ maxRequestSleepNum: 100
|
||||
maxRequestNum: 500
|
||||
# 单ip同时最大搜索请求数
|
||||
singleIpSearchNum: 10
|
||||
# 错误日志输出路径 stdout|stderr|file path 默认为stderr
|
||||
logOutput: err.log
|
||||
|
||||
|
||||
# Gzip
|
||||
gzip: false
|
||||
# 提交评论url host需为ip形式
|
||||
postCommentUrl: http://127.0.0.1/wp-comments-post.php
|
||||
# 提交评论url
|
||||
postCommentUrl: http://wp.test/wp-comments-post.php
|
||||
# TrustIps
|
||||
trustIps: []
|
||||
# 分页器间隔数
|
||||
@ -132,15 +89,9 @@ trustServerNames: [ "xy.test","blog.xy.test" ]
|
||||
theme: "twentyfifteen"
|
||||
# 文档排序默认升序还是降序
|
||||
postOrder: "desc"
|
||||
# WordPress path
|
||||
wpDir: "/var/www/html/wordpress"
|
||||
# 上传的目录
|
||||
uploadDir: ""
|
||||
# pprof route path 为空表示不开启pprof,否则为pprof的路由
|
||||
pprof: "/debug/pprof"
|
||||
# 要使用的程序插件名
|
||||
plugins: [ "enlightjs" ]
|
||||
# 插件存放路径
|
||||
pluginPath: "./plugins"
|
||||
# 列表页面post使用的插件
|
||||
listPagePlugins: [ "digest" ]
|
||||
# 额外引入的脚本 第一个为head 第二个为footer
|
||||
externScript: [ "","" ]
|
||||
listPagePlugins: ["passwordProject","digest","twentyseventeen_postThumbnail"]
|
67
go.mod
67
go.mod
@ -1,53 +1,42 @@
|
||||
module github.com/fthvgb1/wp-go
|
||||
|
||||
go 1.21.0
|
||||
|
||||
toolchain go1.23.0
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/dlclark/regexp2 v1.11.4
|
||||
github.com/elliotchance/phpserialize v1.4.0
|
||||
github.com/gin-contrib/gzip v1.0.1
|
||||
github.com/gin-contrib/pprof v1.5.0
|
||||
github.com/gin-contrib/sessions v1.0.1
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/go-playground/locales v0.14.1
|
||||
github.com/go-playground/universal-translator v0.18.1
|
||||
github.com/go-playground/validator/v10 v10.22.0
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/goccy/go-json v0.10.3
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
github.com/dlclark/regexp2 v1.7.0
|
||||
github.com/elliotchance/phpserialize v1.3.3
|
||||
github.com/gin-contrib/gzip v0.0.6
|
||||
github.com/gin-contrib/pprof v1.4.0
|
||||
github.com/gin-contrib/sessions v0.0.5
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/soxfmr/gomail v0.0.0-20200806033254-80bf84e583f0
|
||||
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7
|
||||
golang.org/x/exp v0.0.0-20230203172020-98cc5a0785f9
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/bytedance/sonic v1.12.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.3.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
||||
github.com/goccy/go-json v0.9.11 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/gorilla/sessions v1.2.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.9.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
golang.org/x/net v0.4.0 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||
)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user