Compare commits

..

No commits in common. "master" and "newcache" have entirely different histories.

392 changed files with 39478 additions and 18653 deletions

8
.gitignore vendored
View File

@ -1,7 +1,3 @@
.idea .idea
/wp-go.iml wp-go.iml
/config.yaml config.yaml
err.log
/plugins/
/config.json
go.sum

View File

@ -1,7 +1,8 @@
FROM golang:1.22.2-alpine as gobulidIso FROM golang:latest as gobulidIso
COPY ./ /go/src/wp-go COPY ./ /go/src/wp-go
WORKDIR /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 FROM alpine:latest
WORKDIR /opt/wp-go WORKDIR /opt/wp-go

View File

@ -1,87 +1,19 @@
## wp-go ## wp-go
a simple front of WordPress build with golang.
[en readme](https://github.com/fthvgb1/wp-go/blob/master/readme_en.md) 一个go写的WordPress的前端功能比较简单只有列表页和详情页,rss2主题只有twentyfifteen和twentyseventeen两套主题插件的话只有一个简单的列表页的摘要生成和enlighter代码高亮。本身只用于展示文章添加评论走的转发请求到php的WordPress。因为大量用了泛型功能所以要求go的版本在1.18及以上。
一个go写的WordPress的前端功能比较简单只有列表页和详情页,rss2主题只有twentyfifteen和twentyseventeen两套主题插件的话只有一个简单的列表页的摘要生成和enlighter代码高亮。本身只用于展示文章及评论。要求go的版本在1.20以上,越新越好。。。
#### 特色功能 #### 特色功能
- 基本实现全站缓存,并且可防止缓存击穿 - 多种缓存配置
- 列表页也可以高亮语法格式化显示代码
- 简易插件扩展开发机制、配置后支持热加载更新
- 使用.so扩展主题、插件、路由等
- 丰富繁杂的配置,呃,配置是有点儿多,虽然大部分都是可选项。。。
- 添加评论或panic时发邮件通知包涵栈调用和请求信息 - 添加评论或panic时发邮件通知包涵栈调用和请求信息
- 简单的流量限制中间件,可以限制全瞬时最大请求数量 - 简单的流量限制中间件
- 除配置文件外将所有静态资源都打包到执行文件中 - 除配置文件外将所有静态资源都打包到执行文件中
- 支持密码查看且cookie信息可被php版所验证 - 支持密码查看且cookie信息可被php版所验证
- 支持rss2订阅 - 支持rss2订阅
- 热更新配置、切换主题、清空缓存 - 热更新配置、清空缓存
- kill -SIGUSR1 PID 更新配置和清空缓存 - kill -SIGUSR1 PID 更新配置和清空缓存
- kill -SIGUSR2 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,在外面封装了层查询的方法。后台可以设置的比较少,大部分设置还没打通。
用的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>

View File

@ -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()'>&laquo; 返回</a></p></body>
</html>
`

View File

@ -1,108 +0,0 @@
package actions
import (
"github.com/fthvgb1/wp-go/app/pkg/cache"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
var tmp = "Mon, 02 Jan 2006 15:04:05 GMT"
func Feed(c *gin.Context) {
v, ok := c.GetQuery("feed")
if !ok || v == "" {
c.Next()
return
}
switch v {
case "rss2":
p, ok := c.GetQuery("p")
if ok && p != "" {
c.AddParam("id", p)
PostFeed(c)
} else {
SiteFeed(c)
}
c.Abort()
return
case "comments-rss2":
CommentsFeed(c)
c.Abort()
return
}
}
func isCacheExpired(c *gin.Context, lastTime time.Time) bool {
eTag := str.Md5(lastTime.Format(tmp))
since := c.Request.Header.Get("If-Modified-Since")
cTag := c.Request.Header.Get("If-None-Match")
if since != "" && cTag != "" {
cGMT, err := time.Parse(tmp, since)
if err == nil && lastTime.Unix() <= cGMT.Unix() && eTag == cTag {
c.Status(http.StatusNotModified)
return false
}
}
return true
}
func SiteFeed(c *gin.Context) {
feed := cache.FeedCache()
if !isCacheExpired(c, feed.GetLastSetTime(c)) {
c.Status(http.StatusNotModified)
return
}
r, err := feed.GetCache(c, time.Second, c)
if err != nil {
c.Status(http.StatusInternalServerError)
c.Abort()
c.Error(err)
return
}
setFeed(r[0], c, feed.GetLastSetTime(c))
}
func setFeed(s string, c *gin.Context, t time.Time) {
lastTimeGMT := t.Format(tmp)
eTag := str.Md5(lastTimeGMT)
c.Header("Content-Type", "application/rss+xml; charset=UTF-8")
c.Header("Last-Modified", lastTimeGMT)
c.Header("ETag", eTag)
c.String(http.StatusOK, s)
}
func PostFeed(c *gin.Context) {
id := c.Param("id")
postFeed := cache.PostFeedCache()
if !isCacheExpired(c, postFeed.GetLastSetTime(c, id)) {
c.Status(http.StatusNotModified)
return
}
s, err := postFeed.GetCache(c, id, time.Second)
if err != nil {
c.Status(http.StatusInternalServerError)
c.Abort()
c.Error(err)
return
}
setFeed(s, c, postFeed.GetLastSetTime(c, id))
}
func CommentsFeed(c *gin.Context) {
feed := cache.CommentsFeedCache()
if !isCacheExpired(c, feed.GetLastSetTime(c)) {
c.Status(http.StatusNotModified)
return
}
r, err := feed.GetCache(c, time.Second, c)
if err != nil {
c.Status(http.StatusInternalServerError)
c.Abort()
c.Error(err)
return
}
setFeed(r[0], c, feed.GetLastSetTime(c))
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -1,22 +0,0 @@
package middleware
import (
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/gin-gonic/gin"
)
func SearchLimit(num int64) func(ctx *gin.Context) {
fn, reFn := IpLimit(num)
reload.Append(func() {
reFn(config.GetConfig().SingleIpSearchNum)
}, "search-ip-limit-number")
return func(c *gin.Context) {
if c.Query("s") != "" {
fn(c)
} else {
c.Next()
}
}
}

View File

@ -1,33 +0,0 @@
package middleware
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/gin-gonic/gin"
"net/http"
"path/filepath"
"strings"
)
var path = map[string]struct{}{
"wp-includes": {},
"wp-content": {},
"favicon.ico": {},
}
func SetStaticFileCache(c *gin.Context) {
f := strings.Split(strings.TrimLeft(c.FullPath(), "/"), "/")
if _, ok := path[f[0]]; ok {
if ".php" == filepath.Ext(c.Request.URL.Path) {
c.Abort()
c.Status(http.StatusForbidden)
return
}
t := config.GetConfig().CacheTime.CacheControl
if t > 0 {
c.Header("Cache-Control", fmt.Sprintf("private, max-age=%d", int(t.Seconds())))
}
}
c.Next()
}

View File

@ -1,33 +0,0 @@
package middleware
import (
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps"
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
func ValidateServerNames() func(ctx *gin.Context) {
sites := reload.VarsBy(func() map[string]struct{} {
r := config.GetConfig().TrustServerNames
m := map[string]struct{}{}
if len(r) > 0 {
for _, name := range r {
m[name] = struct{}{}
}
}
return m
}, "site-names")
return func(c *gin.Context) {
m := sites.Load()
if len(m) > 0 && !maps.IsExists(m, strings.Split(c.Request.Host, ":")[0]) {
c.Status(http.StatusForbidden)
c.Abort()
return
}
c.Next()
}
}

View File

@ -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()
}

View File

@ -1,37 +0,0 @@
package phphelper
import (
"github.com/elliotchance/phpserialize"
"github.com/fthvgb1/wp-go/helper/maps"
)
// UnPHPSerializeToStruct 使用 json tag
func UnPHPSerializeToStruct[T any](s string) (r T, err error) {
var rr map[any]any
err = phpserialize.Unmarshal([]byte(s), &rr)
if err == nil {
rx := maps.AnyAnyToStrAny(rr)
r, err = maps.StrAnyMapToStruct[T](rx)
}
return
}
func UnPHPSerializeToStrAnyMap(s string) (map[string]any, error) {
m := map[string]any{}
var r map[any]any
err := phpserialize.Unmarshal([]byte(s), &r)
if err != nil {
return nil, err
}
m = maps.AnyAnyToStrAny(r)
return m, err
}
func UnPHPSerializeToAnyAnyMap(s string) (map[any]any, error) {
var r map[any]any
err := phpserialize.Unmarshal([]byte(s), &r)
if err != nil {
return nil, err
}
return r, err
}

126
app/pkg/cache/cache.go vendored
View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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"
)

View File

@ -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
}

View File

@ -1,76 +0,0 @@
package dao
import (
"context"
"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"
"github.com/fthvgb1/wp-go/model"
)
var TotalRaw int64
type PostIds struct {
Ids []uint64
Length int
}
type PostContext struct {
Prev models.Posts
Next models.Posts
}
func CategoriesAndTags(ctx context.Context, t string, _ ...any) (terms []models.TermsMy, err error) {
var in = []any{"category", "post_tag"}
switch t {
case constraints.Category:
in = []any{"category"}
case constraints.Tag:
in = []any{"post_tag"}
}
w := model.SqlBuilder{
{"tt.taxonomy", "in", ""},
}
if helper.GetContextVal(ctx, "showOnlyTopLevel", false) {
w = append(w, []string{"tt.parent", "=", "0", "int"})
}
if !helper.GetContextVal(ctx, "showEmpty", false) {
w = append(w, []string{"tt.count", ">", "0", "int"})
}
order := []string{"name", "asc"}
ord := helper.GetContextVal[[]string](ctx, "order", nil)
if ord != nil {
order = ord
}
terms, err = model.Finds[models.TermsMy](ctx, model.Conditions(
model.Where(w),
model.Fields("t.term_id"),
model.Order(model.SqlBuilder{order}),
model.Join(model.SqlBuilder{
{"t", "inner join", "wp_term_taxonomy tt", "t.term_id = tt.term_id"},
}),
model.In(in),
))
for i := 0; i < len(terms); i++ {
if v, ok := wpconfig.GetTerm(terms[i].Terms.TermId); ok {
terms[i].Terms = v
}
if v, ok := wpconfig.GetTermTaxonomy(terms[i].Terms.TermId); ok {
terms[i].TermTaxonomy = v
}
}
return
}
func Archives(ctx context.Context) ([]models.PostArchive, error) {
return model.Finds[models.PostArchive](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"post_type", "post"},
{"post_status", "publish"},
}),
model.Fields("YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, count(ID) as posts"),
model.Group("year,month"),
model.Order(model.SqlBuilder{{"year", "desc"}, {"month", "desc"}}),
))
}

View File

@ -1,204 +0,0 @@
package dao
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/pkg/models/relation"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/model"
"strings"
"sync/atomic"
"time"
)
func GetPostsByIds(ctx context.Context, ids []uint64, _ ...any) (m map[uint64]models.Posts, err error) {
m = make(map[uint64]models.Posts)
q := model.Conditions(
model.Where(model.SqlBuilder{{"Id", "in", ""}}),
model.Join(model.SqlBuilder{
{"a", "left join", "wp_term_relationships b", "a.Id=b.object_id"},
{"left join", "wp_term_taxonomy c", "b.term_taxonomy_id=c.term_taxonomy_id"},
{"left join", "wp_terms d", "c.term_id=d.term_id"},
}),
model.Fields("a.*,ifnull(d.name,'') category_name,ifnull(c.term_id,0) terms_id,ifnull(taxonomy,'') `taxonomy`"),
model.In(slice.ToAnySlice(ids)),
)
if helper.GetContextVal(ctx, "getPostAuthor", false) {
q.RelationFn = append(q.RelationFn, model.AddRelationFn(true, false, helper.GetContextVal[*model.QueryCondition](ctx, "postAuthorQueryCondition", nil), relation.PostsWithAuthor))
}
rawPosts, err := model.Finds[models.Posts](ctx, q)
if err != nil {
return m, err
}
postsMap := make(map[uint64]models.Posts)
for i, post := range rawPosts {
v, ok := postsMap[post.Id]
if !ok {
v = rawPosts[i]
}
if post.Taxonomy == "category" {
v.Categories = append(v.Categories, post.CategoryName)
} else if post.Taxonomy == "post_tag" {
v.Tags = append(v.Tags, post.CategoryName)
}
if post.TermsId > 0 {
v.TermIds = append(v.TermIds, post.TermsId)
}
postsMap[post.Id] = v
}
//host, _ := wpconfig.Options.Load("siteurl")
host := ""
meta, _ := GetPostMetaByPostIds(ctx, ids)
for k, pp := range postsMap {
if len(pp.Categories) > 0 {
t := make([]string, 0, len(pp.Categories))
for _, cat := range pp.Categories {
t = append(t, fmt.Sprintf(`<a href="/p/category/%s" rel="category tag">%s</a>`, cat, cat))
}
pp.CategoriesHtml = strings.Join(t, "、")
}
mm, ok := meta[pp.Id]
if ok {
pp.Metas = mm
attMeta, ok := mm["_wp_attachment_metadata"]
if ok {
att, ok := attMeta.(models.WpAttachmentMetadata)
if ok {
pp.AttachmentMetadata = att
}
}
if pp.PostType != "attachment" {
thumb := ToPostThumb(ctx, mm, host)
if thumb.Path != "" {
pp.Thumbnail = thumb
}
} else if pp.PostType == "attachment" && pp.AttachmentMetadata.File != "" {
thumb := wpconfig.Thumbnail(pp.AttachmentMetadata, "thumbnail", host, "thumbnail", "post-thumbnail")
if thumb.Path != "" {
pp.Thumbnail = thumb
}
}
}
if len(pp.Tags) > 0 {
t := make([]string, 0, len(pp.Tags))
for _, cat := range pp.Tags {
t = append(t, fmt.Sprintf(`<a href="/p/tag/%s" rel="tag">%s</a>`, cat, cat))
}
pp.TagsHtml = strings.Join(t, "、")
}
m[k] = pp
}
return
}
func SearchPostIds(ctx context.Context, _ string, args ...any) (ids PostIds, err error) {
q := args[0].(*model.QueryCondition)
page := args[1].(int)
pageSize := args[2].(int)
q.Fields = "ID"
res, total, err := model.Pagination[models.Posts](ctx, q, page, pageSize)
for _, posts := range res {
ids.Ids = append(ids.Ids, posts.Id)
}
ids.Length = total
totalR := int(atomic.LoadInt64(&TotalRaw))
if total > totalR {
tt := int64(total)
atomic.StoreInt64(&TotalRaw, tt)
}
return
}
func GetMaxPostId(ctx context.Context, _ ...any) (uint64, error) {
r, err := model.Finds[models.Posts](ctx,
model.Conditions(
model.Where(model.SqlBuilder{
{"post_type", "post"},
{"post_status", "publish"}},
),
model.Fields("ID"),
model.Order(model.SqlBuilder{
{"ID", "desc"},
}),
model.Limit(1),
),
)
var id uint64
if len(r) > 0 {
id = r[0].Id
}
return id, err
}
func RecentPosts(ctx context.Context, a ...any) (r []models.Posts, err error) {
num := helper.ParseArgs(10, a...)
r, err = model.Finds[models.Posts](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"post_type", "post"},
{"post_status", "publish"},
}),
model.Fields("ID,post_title,post_password,post_date_gmt"),
model.Order([][]string{{"post_date", "desc"}}),
model.Limit(num),
))
return
}
func GetPostContext(ctx context.Context, _ uint64, arg ...any) (r PostContext, err error) {
t := arg[0].(time.Time)
next, err := model.FirstOne[models.Posts](ctx, model.SqlBuilder{
{"post_date", ">", t.Format("2006-01-02 15:04:05")},
{"post_status", "in", ""},
{"post_type", "post"},
}, "ID,post_title,post_password", nil, []any{"publish"})
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
return
}
prev, err := model.FirstOne[models.Posts](ctx, model.SqlBuilder{
{"post_date", "<", t.Format("2006-01-02 15:04:05")},
{"post_status", "in", ""},
{"post_type", "post"},
}, "ID,post_title", model.SqlBuilder{{"post_date", "desc"}}, []any{"publish"})
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
if err != nil {
return
}
r = PostContext{
Prev: prev,
Next: next,
}
return
}
func MonthPost(ctx context.Context, _ string, args ...any) (r []uint64, err error) {
year, month := args[0].(string), args[1].(string)
where := model.SqlBuilder{
{"post_type", "post"},
{"post_status", "publish"},
{"year(post_date)", year},
{"month(post_date)", month},
}
r, err = model.Column[models.Posts, uint64](ctx, func(v models.Posts) (uint64, bool) {
return v.Id, true
}, model.Conditions(
model.Fields("ID"),
model.Where(where),
))
l := int64(len(r))
if l > atomic.LoadInt64(&TotalRaw) {
atomic.StoreInt64(&TotalRaw, l)
}
return
}

View File

@ -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
}

View File

@ -1,77 +0,0 @@
package db
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/model"
"github.com/fthvgb1/wp-go/safety"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
"log"
"runtime"
)
var safeDb = safety.NewVar[*sqlx.DB](nil)
var showQuerySql func() bool
func GetSqlxDB() *sqlx.DB {
return safeDb.Load()
}
func InitDb() (*safety.Var[*sqlx.DB], error) {
c := config.GetConfig()
dsn := c.Mysql.Dsn.GetDsn()
db, err := sqlx.Open("mysql", dsn)
if err != nil {
return nil, err
}
preDb := safeDb.Load()
if c.Mysql.Pool.ConnMaxIdleTime != 0 {
db.SetConnMaxIdleTime(c.Mysql.Pool.ConnMaxLifetime)
}
if c.Mysql.Pool.MaxIdleConn != 0 {
db.SetMaxIdleConns(c.Mysql.Pool.MaxIdleConn)
}
if c.Mysql.Pool.MaxOpenConn != 0 {
db.SetMaxOpenConns(c.Mysql.Pool.MaxOpenConn)
}
if c.Mysql.Pool.ConnMaxLifetime != 0 {
db.SetConnMaxLifetime(c.Mysql.Pool.ConnMaxLifetime)
}
safeDb.Store(db)
if preDb != nil {
_ = preDb.Close()
}
if showQuerySql == nil {
showQuerySql = reload.BuildFnVal("showQuerySql", false, func() bool {
return config.GetConfig().ShowQuerySql
})
}
return safeDb, err
}
func QueryDb(db *safety.Var[*sqlx.DB]) *model.SqlxQuery {
query := model.NewSqlxQuery(db, model.NewUniversalDb(
nil,
nil))
model.SetSelect(query, func(ctx context.Context, a any, s string, args ...any) error {
if showQuerySql() {
_, f, l, _ := runtime.Caller(5)
go func() {
log.Printf("%v:%v %v\n", f, l, model.FormatSql(s, args...))
}()
}
return query.Selects(ctx, a, s, args...)
})
model.SetGet(query, func(ctx context.Context, a any, s string, args ...any) error {
if showQuerySql() {
_, f, l, _ := runtime.Caller(5)
go func() {
log.Printf("%v:%v %v\n", f, l, model.FormatSql(s, args...))
}()
}
return query.Gets(ctx, a, s, args...)
})
return query
}

View File

@ -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...)
}

View File

@ -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",
})

View File

@ -1,9 +0,0 @@
<div>
{{.aa}}
</div>
<div>
{{ range $k,$v := .posts}}
<h2>{{$v.PostTitle}} </h2>
{{end}}
</div>

View File

@ -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()
})
}

View File

@ -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

View File

@ -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")
}

View File

@ -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
)

View File

@ -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())
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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>`

View File

@ -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)
})
}

View File

@ -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>`: "",
})
})
}

View File

@ -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")
}))
}

View File

@ -1,19 +0,0 @@
package wpposts
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/models"
)
func PasswordProjectTitle(post *models.Posts) {
post.PostTitle = fmt.Sprintf("密码保护:%s", post.PostTitle)
}
func PasswdProjectContent(post *models.Posts) {
format := `
<form action="/login" class="post-password-form" method="post">
<p>此内容受密码保护如需查阅请在下列字段中输入您的密码</p>
<p><label for="pwbox-%d">密码 <input name="post_password" id="pwbox-%d" type="password" size="20"></label> <input type="submit" name="Submit" value="提交"></p>
</form>`
post.PostContent = fmt.Sprintf(format, post.Id, post.Id)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"))
}

View File

@ -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))
},
}
}

View File

@ -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
}

View File

@ -1,614 +0,0 @@
package twentyfifteen
import (
"fmt"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/helper/slice"
"strconv"
"strings"
)
func colorSchemeCss(h *wp.Handle) string {
s := slice.Filter([]string{calColorSchemeCss(h), calSidebarTextColorCss(h), calHeaderBackgroundColorCss(h)}, func(s string, i int) bool {
return s != ""
})
if len(s) < 1 {
return ""
}
return fmt.Sprintf(`<style id='%s-inline-css'%s>\n%s\n</style>`, "twentyfifteen-style", "", strings.Join(s, "\n"))
}
func calColorSchemeCss(h *wp.Handle) (r string) {
color := getColorScheme(h)
if "default" == h.CommonThemeMods().ColorScheme || len(color) < 1 {
return
}
textColorRgb := slice.ToAnySlice(Hex2RgbUint8(color[3]))
sidebarTextColorRgb := Hex2RgbUint8(color[4])
sidebarTextColorRgbs := slice.ToAnySlice(sidebarTextColorRgb)
colors := map[string]string{
"background_color": color[0],
"header_background_color": color[1],
"box_background_color": color[2],
"textcolor": color[3],
"secondary_textcolor": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", textColorRgb...),
"border_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", textColorRgb...),
"border_focus_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", textColorRgb...),
"sidebar_textcolor": color[4],
"sidebar_border_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", sidebarTextColorRgbs...),
"sidebar_border_focus_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", sidebarTextColorRgbs...),
"secondary_sidebar_textcolor": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", sidebarTextColorRgbs...),
"meta_box_background_color": color[5],
}
r = cssTemplate
for k, v := range colors {
r = strings.ReplaceAll(r, fmt.Sprintf(`{$colors['%s']}`, k), v)
}
return
}
func calSidebarTextColorCss(h *wp.Handle) (r string) {
colors := getColorScheme(h)
themeMods := h.CommonThemeMods()
if themeMods.SidebarTextcolor == "" || themeMods.SidebarTextcolor == colors[4] {
return
}
linkColorRgb := Hex2RgbUint8(themeMods.SidebarTextcolor)
color := slice.ToAnySlice(linkColorRgb)
textColor := fmt.Sprintf(`rgba( %[1]v, %[2]v, %[3]v, 0.7)`, color...)
borderColor := fmt.Sprintf(`rgba( %[1]v, %[2]v, %[3]v, 0.1)`, color...)
borderFocusColor := fmt.Sprintf(`rgba( %[1]v, %[2]v, %[3]v, 0.3)`, color...)
r = fmt.Sprintf(sidebarTextColorTemplate, themeMods.SidebarTextcolor, textColor, borderColor, borderFocusColor)
return
}
func calHeaderBackgroundColorCss(h *wp.Handle) (r string) {
colors := getColorScheme(h)
themeMods := h.CommonThemeMods()
if themeMods.HeaderBackgroundColor == "" || themeMods.HeaderBackgroundColor == colors[1] {
return
}
r = fmt.Sprintf(headerBackgroundColorCssTemplate, themeMods.HeaderBackgroundColor, themeMods.HeaderBackgroundColor)
return
}
func getColorScheme(h *wp.Handle) (r []string) {
x, ok := colorscheme[h.CommonThemeMods().ColorScheme]
if ok {
r = x.Colors
}
return
}
type ColorScheme struct {
Label string `json:"label,omitempty"`
Colors []string `json:"colors,omitempty"`
}
func Hex2RgbUint8(color string) []uint8 {
var r []uint8
color = strings.TrimLeft(color, "#")
fn := func(s string) uint8 {
n, _ := strconv.ParseInt(s, 16, 0)
return uint8(n)
}
switch len(color) {
case 3:
r = []uint8{color[0], color[1], color[2]}
case 6:
r = []uint8{fn(color[:2]), fn(color[2:4]), fn(color[4:])}
}
return r
}
var cssTemplate = `
/* Color Scheme */
/* Background Color */
body {
background-color: {$colors['background_color']};
}
/* Sidebar Background Color */
body:before,
.site-header {
background-color: {$colors['header_background_color']};
}
/* Box Background Color */
.post-navigation,
.pagination,
.secondary,
.site-footer,
.hentry,
.page-header,
.page-content,
.comments-area,
.widecolumn {
background-color: {$colors['box_background_color']};
}
/* Box Background Color */
button,
input[type="button"],
input[type="reset"],
input[type="submit"],
.pagination .prev,
.pagination .next,
.widget_calendar tbody a,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus,
.page-links a,
.page-links a:hover,
.page-links a:focus,
.sticky-post {
color: {$colors['box_background_color']};
}
/* Main Text Color */
button,
input[type="button"],
input[type="reset"],
input[type="submit"],
.pagination .prev,
.pagination .next,
.widget_calendar tbody a,
.page-links a,
.sticky-post {
background-color: {$colors['textcolor']};
}
/* Main Text Color */
body,
blockquote cite,
blockquote small,
a,
.dropdown-toggle:after,
.image-navigation a:hover,
.image-navigation a:focus,
.comment-navigation a:hover,
.comment-navigation a:focus,
.widget-title,
.entry-footer a:hover,
.entry-footer a:focus,
.comment-metadata a:hover,
.comment-metadata a:focus,
.pingback .edit-link a:hover,
.pingback .edit-link a:focus,
.comment-list .reply a:hover,
.comment-list .reply a:focus,
.site-info a:hover,
.site-info a:focus {
color: {$colors['textcolor']};
}
/* Main Text Color */
.entry-content a,
.entry-summary a,
.page-content a,
.comment-content a,
.pingback .comment-body > a,
.author-description a,
.taxonomy-description a,
.textwidget a,
.entry-footer a:hover,
.comment-metadata a:hover,
.pingback .edit-link a:hover,
.comment-list .reply a:hover,
.site-info a:hover {
border-color: {$colors['textcolor']};
}
/* Secondary Text Color */
button:hover,
button:focus,
input[type="button"]:hover,
input[type="button"]:focus,
input[type="reset"]:hover,
input[type="reset"]:focus,
input[type="submit"]:hover,
input[type="submit"]:focus,
.pagination .prev:hover,
.pagination .prev:focus,
.pagination .next:hover,
.pagination .next:focus,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus,
.page-links a:hover,
.page-links a:focus {
background-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
background-color: {$colors['secondary_textcolor']};
}
/* Secondary Text Color */
blockquote,
a:hover,
a:focus,
.main-navigation .menu-item-description,
.post-navigation .meta-nav,
.post-navigation a:hover .post-title,
.post-navigation a:focus .post-title,
.image-navigation,
.image-navigation a,
.comment-navigation,
.comment-navigation a,
.widget,
.author-heading,
.entry-footer,
.entry-footer a,
.taxonomy-description,
.page-links > .page-links-title,
.entry-caption,
.comment-author,
.comment-metadata,
.comment-metadata a,
.pingback .edit-link,
.pingback .edit-link a,
.post-password-form label,
.comment-form label,
.comment-notes,
.comment-awaiting-moderation,
.logged-in-as,
.form-allowed-tags,
.no-comments,
.site-info,
.site-info a,
.wp-caption-text,
.gallery-caption,
.comment-list .reply a,
.widecolumn label,
.widecolumn .mu_register label {
color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
color: {$colors['secondary_textcolor']};
}
/* Secondary Text Color */
blockquote,
.logged-in-as a:hover,
.comment-author a:hover {
border-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
border-color: {$colors['secondary_textcolor']};
}
/* Border Color */
hr,
.dropdown-toggle:hover,
.dropdown-toggle:focus {
background-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
background-color: {$colors['border_color']};
}
/* Border Color */
pre,
abbr[title],
table,
th,
td,
input,
textarea,
.main-navigation ul,
.main-navigation li,
.post-navigation,
.post-navigation div + div,
.pagination,
.comment-navigation,
.widget li,
.widget_categories .children,
.widget_nav_menu .sub-menu,
.widget_pages .children,
.site-header,
.site-footer,
.hentry + .hentry,
.author-info,
.entry-content .page-links a,
.page-links > span,
.page-header,
.comments-area,
.comment-list + .comment-respond,
.comment-list article,
.comment-list .pingback,
.comment-list .trackback,
.comment-list .reply a,
.no-comments {
border-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
border-color: {$colors['border_color']};
}
/* Border Focus Color */
a:focus,
button:focus,
input:focus {
outline-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
outline-color: {$colors['border_focus_color']};
}
input:focus,
textarea:focus {
border-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
border-color: {$colors['border_focus_color']};
}
/* Sidebar Link Color */
.secondary-toggle:before {
color: {$colors['sidebar_textcolor']};
}
.site-title a,
.site-description {
color: {$colors['sidebar_textcolor']};
}
/* Sidebar Text Color */
.site-title a:hover,
.site-title a:focus {
color: {$colors['secondary_sidebar_textcolor']};
}
/* Sidebar Border Color */
.secondary-toggle {
border-color: {$colors['sidebar_textcolor']}; /* Fallback for IE7 and IE8 */
border-color: {$colors['sidebar_border_color']};
}
/* Sidebar Border Focus Color */
.secondary-toggle:hover,
.secondary-toggle:focus {
border-color: {$colors['sidebar_textcolor']}; /* Fallback for IE7 and IE8 */
border-color: {$colors['sidebar_border_focus_color']};
}
.site-title a {
outline-color: {$colors['sidebar_textcolor']}; /* Fallback for IE7 and IE8 */
outline-color: {$colors['sidebar_border_focus_color']};
}
/* Meta Background Color */
.entry-footer {
background-color: {$colors['meta_box_background_color']};
}
@media screen and (min-width: 38.75em) {
/* Main Text Color */
.page-header {
border-color: {$colors['textcolor']};
}
}
@media screen and (min-width: 59.6875em) {
/* Make sure its transparent on desktop */
.site-header,
.secondary {
background-color: transparent;
}
/* Sidebar Background Color */
.widget button,
.widget input[type="button"],
.widget input[type="reset"],
.widget input[type="submit"],
.widget_calendar tbody a,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus {
color: {$colors['header_background_color']};
}
/* Sidebar Link Color */
.secondary a,
.dropdown-toggle:after,
.widget-title,
.widget blockquote cite,
.widget blockquote small {
color: {$colors['sidebar_textcolor']};
}
.widget button,
.widget input[type="button"],
.widget input[type="reset"],
.widget input[type="submit"],
.widget_calendar tbody a {
background-color: {$colors['sidebar_textcolor']};
}
.textwidget a {
border-color: {$colors['sidebar_textcolor']};
}
/* Sidebar Text Color */
.secondary a:hover,
.secondary a:focus,
.main-navigation .menu-item-description,
.widget,
.widget blockquote,
.widget .wp-caption-text,
.widget .gallery-caption {
color: {$colors['secondary_sidebar_textcolor']};
}
.widget button:hover,
.widget button:focus,
.widget input[type="button"]:hover,
.widget input[type="button"]:focus,
.widget input[type="reset"]:hover,
.widget input[type="reset"]:focus,
.widget input[type="submit"]:hover,
.widget input[type="submit"]:focus,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus {
background-color: {$colors['secondary_sidebar_textcolor']};
}
.widget blockquote {
border-color: {$colors['secondary_sidebar_textcolor']};
}
/* Sidebar Border Color */
.main-navigation ul,
.main-navigation li,
.widget input,
.widget textarea,
.widget table,
.widget th,
.widget td,
.widget pre,
.widget li,
.widget_categories .children,
.widget_nav_menu .sub-menu,
.widget_pages .children,
.widget abbr[title] {
border-color: {$colors['sidebar_border_color']};
}
.dropdown-toggle:hover,
.dropdown-toggle:focus,
.widget hr {
background-color: {$colors['sidebar_border_color']};
}
.widget input:focus,
.widget textarea:focus {
border-color: {$colors['sidebar_border_focus_color']};
}
.sidebar a:focus,
.dropdown-toggle:focus {
outline-color: {$colors['sidebar_border_focus_color']};
}
}
`
var headerBackgroundColorCssTemplate = `
/* Custom Header Background Color */
body:before,
.site-header {
background-color: %s;
}
@media screen and (min-width: 59.6875em) {
.site-header,
.secondary {
background-color: transparent;
}
.widget button,
.widget input[type="button"],
.widget input[type="reset"],
.widget input[type="submit"],
.widget_calendar tbody a,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus {
color: %s;
}
}
`
var sidebarTextColorTemplate = `
/* Custom Sidebar Text Color */
.site-title a,
.site-description,
.secondary-toggle:before {
color: %[1]v;
}
.site-title a:hover,
.site-title a:focus {
color: %[1]v; /* Fallback for IE7 and IE8 */
color: %[2]v;
}
.secondary-toggle {
border-color: %[1]v; /* Fallback for IE7 and IE8 */
border-color: %[3]v;
}
.secondary-toggle:hover,
.secondary-toggle:focus {
border-color: %[1]v; /* Fallback for IE7 and IE8 */
border-color: %[4]v;
}
.site-title a {
outline-color: %[1]v; /* Fallback for IE7 and IE8 */
outline-color: %[4]v;
}
@media screen and (min-width: 59.6875em) {
.secondary a,
.dropdown-toggle:after,
.widget-title,
.widget blockquote cite,
.widget blockquote small {
color: %[1]v;
}
.widget button,
.widget input[type="button"],
.widget input[type="reset"],
.widget input[type="submit"],
.widget_calendar tbody a {
background-color: %[1]v;
}
.textwidget a {
border-color: %[1]v;
}
.secondary a:hover,
.secondary a:focus,
.main-navigation .menu-item-description,
.widget,
.widget blockquote,
.widget .wp-caption-text,
.widget .gallery-caption {
color: %[2]v;
}
.widget button:hover,
.widget button:focus,
.widget input[type="button"]:hover,
.widget input[type="button"]:focus,
.widget input[type="reset"]:hover,
.widget input[type="reset"]:focus,
.widget input[type="submit"]:hover,
.widget input[type="submit"]:focus,
.widget_calendar tbody a:hover,
.widget_calendar tbody a:focus {
background-color: %[2]v;
}
.widget blockquote {
border-color: %[2]v;
}
.main-navigation ul,
.main-navigation li,
.secondary-toggle,
.widget input,
.widget textarea,
.widget table,
.widget th,
.widget td,
.widget pre,
.widget li,
.widget_categories .children,
.widget_nav_menu .sub-menu,
.widget_pages .children,
.widget abbr[title] {
border-color: %[3]v;
}
.dropdown-toggle:hover,
.dropdown-toggle:focus,
.widget hr {
background-color: %[3]v;
}
.widget input:focus,
.widget textarea:focus {
border-color: %[4]v;
}
.sidebar a:focus,
.dropdown-toggle:focus {
outline-color: %[4]v;
}
}
`

View File

@ -1,65 +0,0 @@
package twentyfifteen
import (
"fmt"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/maps"
str "github.com/fthvgb1/wp-go/helper/strings"
)
var postx = map[string]string{
"left": "left",
"right": "right",
"center": "center",
}
var posty = map[string]string{
"top": "top",
"bottom": "bottom",
"center": "center",
}
var size = map[string]string{
"auto": "auto",
"contain": "contain",
"cover": "cover",
}
var repeat = map[string]string{
"repeat-x": "repeat-x",
"repeat-y": "repeat-y",
"repeat": "repeat",
"no-repeat": "no-repeat",
}
func CalCustomBackGround(h *wp.Handle) (r string) {
themeMods := h.CommonThemeMods()
if themeMods.BackgroundImage == "" && (themeMods.BackgroundColor == "" || themeMods.BackgroundColor == themesupport.CustomBackground.DefaultColor) {
return
}
s := str.NewBuilder()
if themeMods.BackgroundImage != "" {
s.Sprintf(` background-image: url("%s");`, helper.CutUrlHost(themeMods.BackgroundImage))
}
backgroundPositionX := helper.Defaults(themeMods.BackgroundPositionX, themesupport.CustomBackground.DefaultPositionX)
backgroundPositionX = maps.WithDefaultVal(postx, backgroundPositionX, "left")
backgroundPositionY := helper.Defaults(themeMods.BackgroundPositionY, themesupport.CustomBackground.DefaultPositionY)
backgroundPositionY = maps.WithDefaultVal(posty, backgroundPositionY, "top")
positon := fmt.Sprintf(" background-position: %s %s;", backgroundPositionX, backgroundPositionY)
siz := helper.DefaultVal(themeMods.BackgroundSize, themesupport.CustomBackground.DefaultSize)
siz = maps.WithDefaultVal(size, siz, "auto")
siz = fmt.Sprintf(" background-size: %s;", siz)
repeats := helper.Defaults(themeMods.BackgroundRepeat, themesupport.CustomBackground.DefaultRepeat)
repeats = maps.WithDefaultVal(repeat, repeats, "repeat")
repeats = fmt.Sprintf(" background-repeat: %s;", repeats)
attachment := helper.Defaults(themeMods.BackgroundAttachment, themesupport.CustomBackground.DefaultAttachment)
attachment = helper.Or(attachment == "fixed", "fixed", "scroll")
attachment = fmt.Sprintf(" background-attachment: %s;", attachment)
s.WriteString(positon, siz, repeats, attachment)
r = fmt.Sprintf(`<style id="custom-background-css">
body.custom-background {%s}
</style>`, s.String())
return
}

View File

@ -1,123 +0,0 @@
package twentyfifteen
import (
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/cache/reload"
str "github.com/fthvgb1/wp-go/helper/strings"
)
var style = `<style type="text/css" id="twentyfifteen-header-css">`
var defaultTextStyle = `.site-header {
padding-top: 14px;
padding-bottom: 14px;
}
.site-branding {
min-height: 42px;
}
@media screen and (min-width: 46.25em) {
.site-header {
padding-top: 21px;
padding-bottom: 21px;
}
.site-branding {
min-height: 56px;
}
}
@media screen and (min-width: 55em) {
.site-header {
padding-top: 25px;
padding-bottom: 25px;
}
.site-branding {
min-height: 62px;
}
}
@media screen and (min-width: 59.6875em) {
.site-header {
padding-top: 0;
padding-bottom: 0;
}
.site-branding {
min-height: 0;
}
}`
var imgStyle = `.site-header {
/*
* No shorthand so the Customizer can override individual properties.
* @see https://core.trac.wordpress.org/ticket/31460
*/
background-image: url("%s");
background-repeat: no-repeat;
background-position: 50% 50%;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
@media screen and (min-width: 59.6875em) {
body:before {
/*
* No shorthand so the Customizer can override individual properties.
* @see https://core.trac.wordpress.org/ticket/31460
*/
background-image: url("%s");
background-repeat: no-repeat;
background-position: 100% 50%;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
border-right: 0;
}
.site-header {
background: transparent;
}
}`
var header = reload.Vars(constraints.Defaults, "twentyfifteen-customheader")
func calCustomHeaderImg(h *wp.Handle) (r string, rand bool) {
img, rand := h.GetCustomHeaderImg()
if img.Path == "" && h.DisplayHeaderText() {
return
}
ss := str.NewBuilder()
ss.WriteString(style)
if img.Path == "" && !h.DisplayHeaderText() {
ss.WriteString(defaultTextStyle)
}
if img.Path != "" {
ss.Sprintf(imgStyle, img.Path, img.Path)
}
if !h.DisplayHeaderText() {
ss.WriteString(`.site-title,
.site-description {
clip: rect(1px, 1px, 1px, 1px);
position: absolute;
}`)
}
ss.WriteString("</style>")
r = ss.String()
return
}
func customHeader(h *wp.Handle) func() string {
return func() string {
headers := header.Load()
if headers == constraints.Defaults {
headerss, rand := calCustomHeaderImg(h)
headers = headerss
if !rand {
header.Store(headers)
}
}
return headers
}
}

View File

@ -1,5 +0,0 @@
{{define "layout/sidebar" }}
<div id="widget-area" class="widget-area" role="complementary">
{{template "common/sidebarWidget" .}}
</div>
{{end}}

View File

@ -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{}{}
}()

View File

@ -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))
}

View File

@ -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; */
}
}
`

View File

@ -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;
}
`

View File

@ -1,48 +0,0 @@
{{define "layout/empty"}}
<div class="wrap">
<div id="primary" class="content-area">
<main id="main" class="site-main">
<section class="error-404 not-found">
<header class="page-header">
<h1 class="page-title">
{{if .search}}
未找到
{{else}}
有点尴尬诶!该页无法显示。
{{end}}
</h1>
</header><!-- .page-header -->
<div class="page-content">
<p>{{if .search}}
抱歉,没有符合您搜索条件的结果。请换其它关键词再试。
{{else}}
这儿似乎什么都没有,试试搜索?
{{end}}
</p>
<form role="search" method="get" class="search-form" action="/">
<label for="search-form-1">
<span class="screen-reader-text">搜索:</span>
</label>
<input type="search" id="search-form-1" class="search-field" placeholder="搜索…" value="{{.search}}" 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">搜索</span>
</button>
</form>
</div><!-- .page-content -->
</section><!-- .no-results -->
</main><!-- .site-main -->
</div>
{{if .search }}
<aside id="secondary" class="widget-area" aria-label="博客边栏">
{{template "layout/sidebar" .}}
</aside>
{{end}}
</div>
{{end}}

View File

@ -1,5 +0,0 @@
{{define "layout/footer"}}
{{template "common/footer" .}}
{{ block "footer" .}}
{{end}}
{{end}}

View File

@ -1,3 +0,0 @@
{{define "layout/sidebar" }}
{{template "common/sidebarWidget" .}}
{{end}}

View File

@ -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}}

View File

@ -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>`

View File

@ -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{}{}
}()

View File

@ -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 ""
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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>
`

View File

@ -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()
}

View File

@ -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>",
}
}

View File

@ -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("&nbsp;", 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 ""
}

View File

@ -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...))
}

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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))
}

View File

@ -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}": "搜索&hellip;",
"{$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))
}

View File

@ -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
})
}

View File

@ -1,19 +0,0 @@
package wp
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/helper/html"
)
func CalCustomCss(h *Handle) (r string) {
if h.themeMods.CustomCssPostId < 1 {
return
}
post, err := cache.GetPostById(h.C, uint64(h.themeMods.CustomCssPostId))
if err != nil || post.Id < 1 {
return
}
r = fmt.Sprintf(`<style id="wp-custom-css">%s</style>`, html.StripTags(post.PostContent, ""))
return
}

View File

@ -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
}

View File

@ -1,51 +0,0 @@
package wp
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/cache"
"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 CalCustomLogo(h *Handle) (r string) {
id := uint64(h.themeMods.CustomLogo)
if id < 1 {
id = str.ToInteger[uint64](wpconfig.GetOption("site_logo"), 0)
if id < 1 {
return
}
}
logo, err := cache.GetPostById(h.C, id)
if err != nil || logo.AttachmentMetadata.File == "" {
return
}
siz := "full"
meta, _ := cache.GetPostMetaByPostId(h.C, id)
alt := maps.WithDefaultVal(meta, "_wp_attachment_image_alt", any(wpconfig.GetOption("blogname")))
desc := alt.(string)
imgx := map[string]string{
"class": "custom-logo",
"alt": desc,
"decoding": "async",
//"loading":"lazy",
}
img := wpconfig.Thumbnail(logo.AttachmentMetadata, siz, "", "")
imgx["srcset"] = img.Srcset
imgx["sizes"] = img.Sizes
imgx["src"] = img.Path
r = fmt.Sprintf("%s />", maps.Reduce(imgx, func(k string, v string, t string) string {
return fmt.Sprintf(`%s %s="%s"`, t, k, v)
}, fmt.Sprintf(`<img wight="%v" height="%v"`, img.Width, img.Height)))
r = fmt.Sprintf(`<a href="%s" class="custom-logo-link" rel="home"%s>%s</a>`, "/", ` aria-current="page"`, r)
return
}
var GetCustomLog = reload.BuildValFn("customLogo", CalCustomLogo)
func customLogo(h *Handle) func() string {
return func() string {
return GetCustomLog(h)
}
}

View File

@ -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
}))
}

View File

@ -1,8 +0,0 @@
package wp
const (
None = iota
Low
High
Fatal
)

View File

@ -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 ""
}

View File

@ -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")
}

View File

@ -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
})...)
}

View File

@ -1,235 +0,0 @@
package wp
import (
"errors"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/maps"
"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"
"github.com/gin-gonic/gin"
"strconv"
"strings"
"sync/atomic"
"time"
)
type IndexParams struct {
ParseSearch func()
ParseArchive func() error
ParseCategory func() error
ParseTag func() error
ParseAuthor func() error
CategoryCondition func()
ParseParams func()
Ctx *gin.Context
Page int
PageSize int
Title string
TitleL string
TitleR string
Search string
Author string
TotalPage int
Category string
CategoryType string
Where model.SqlBuilder
OrderBy string
Order string
Month string
Year string
Join model.SqlBuilder
PostType []any
PostStatus []any
Header string
PaginationStep int
CacheKey string
BlogName string
}
var months = slice.SimpleToMap(number.Range(1, 12, 1), func(v int) int {
return v
})
var orders = map[string]struct{}{"asc": {}, "desc": {}}
func (i *IndexParams) setTitleLR(l, r string) {
i.TitleL = l
i.TitleR = r
}
func (i *IndexParams) getTitle() string {
i.Title = fmt.Sprintf("%s - %s", i.TitleL, i.TitleR)
return i.Title
}
func (i *IndexParams) getSearchKey() string {
return fmt.Sprintf("action:%s|%s|%s|%s|%s|%s|%d|%d", i.Author, i.Search, i.OrderBy, i.Order, i.Category, i.CategoryType, i.Page, i.PageSize)
}
func NewIndexParams(ctx *gin.Context) *IndexParams {
blogName := wpconfig.GetOption("blogname")
size := str.ToInteger(wpconfig.GetOption("posts_per_page"), 10)
i := &IndexParams{
Ctx: ctx,
Page: 1,
PageSize: size,
PaginationStep: number.Max(1, config.GetConfig().PaginationStep),
TitleL: blogName,
TitleR: wpconfig.GetOption("blogdescription"),
Where: model.SqlBuilder{
{"post_type", "in", ""},
{"post_status", "in", ""},
},
OrderBy: "post_date",
Join: model.SqlBuilder{},
PostType: []any{"post"},
PostStatus: []any{"publish"},
BlogName: wpconfig.GetOption("blogname"),
}
i.ParseSearch = i.ParseSearchs
i.ParseArchive = i.ParseArchives
i.ParseCategory = i.ParseCategorys
i.ParseTag = i.ParseTags
i.CategoryCondition = i.CategoryConditions
i.ParseAuthor = i.ParseAuthors
i.ParseParams = i.ParseParamss
return i
}
func (i *IndexParams) ParseSearchs() {
s := i.Ctx.Query("s")
q := str.Join("%", s, "%")
i.Where = append(i.Where, []string{
"and", "post_title", "like", q, "",
"or", "post_content", "like", q, "",
"or", "post_excerpt", "like", q, "",
}, []string{"post_password", ""})
i.PostType = append(i.PostType, "Page", "attachment")
i.Header = fmt.Sprintf("<span>%s</span>的搜索结果", s)
i.setTitleLR(str.Join(`"`, s, `"`, "的搜索结果"), i.BlogName)
i.Search = s
}
func (i *IndexParams) ParseArchives() error {
year := i.Ctx.Param("year")
if year != "" {
y := str.ToInteger(year, -1)
if y > time.Now().Year() || y <= 1970 {
return errors.New(str.Join("year err : ", year))
}
i.Where = append(i.Where, []string{
"year(post_date)", year,
})
i.Year = year
}
month := i.Ctx.Param("month")
if month != "" {
m := str.ToInteger(month, -1)
if !maps.IsExists(months, m) {
return errors.New(str.Join("months err ", month))
}
i.Where = append(i.Where, []string{
"month(post_date)", month,
})
ss := fmt.Sprintf("%s年%s月", year, strings.TrimLeft(month, "0"))
i.Header = fmt.Sprintf("月度归档: <span>%s</span>", ss)
i.setTitleLR(ss, i.BlogName)
i.Month = month
}
return nil
}
func (i *IndexParams) ParseCategorys() error {
category := i.Ctx.Param("category")
if category != "" {
if !maps.IsExists(cache.AllCategoryTagsNames(i.Ctx, constraints.Category), category) {
return errors.New(str.Join("not exists category ", category))
}
i.CategoryType = "category"
i.Header = fmt.Sprintf("分类: <span>%s</span>", category)
i.Category = category
i.CategoryCondition()
}
return nil
}
func (i *IndexParams) ParseTags() error {
tag := i.Ctx.Param("tag")
if tag != "" {
if !maps.IsExists(cache.AllCategoryTagsNames(i.Ctx, constraints.Tag), tag) {
return errors.New(str.Join("not exists tag ", tag))
}
i.CategoryType = "post_tag"
i.Header = fmt.Sprintf("标签: <span>%s</span>", tag)
i.Category = tag
i.CategoryCondition()
}
return nil
}
func (i *IndexParams) CategoryConditions() {
if i.Category != "" {
i.Where = append(i.Where, []string{
"d.name", i.Category,
}, []string{"taxonomy", i.CategoryType})
i.Join = append(i.Join, []string{
"a", "left Join", "wp_term_relationships b", "a.Id=b.object_id",
}, []string{
"left Join", "wp_term_taxonomy c", "b.term_taxonomy_id=c.term_taxonomy_id",
}, []string{
"left Join", "wp_terms d", "c.term_id=d.term_id",
})
i.setTitleLR(i.Category, i.BlogName)
}
}
func (i *IndexParams) ParseAuthors() (err error) {
username := i.Ctx.Param("author")
if username != "" {
allUsername, er := cache.GetAllUsername(i.Ctx)
if err != nil {
err = er
return
}
if !maps.IsExists(allUsername, username) {
err = errors.New(str.Join("user ", username, " is not exists"))
return
}
user, er := cache.GetUserByName(i.Ctx, username)
if er != nil {
return
}
i.Author = username
i.Where = append(i.Where, []string{
"post_author", "=", strconv.FormatUint(user.Id, 10), "int",
})
i.Header = str.Join("作者:", username)
}
return
}
func (i *IndexParams) ParseParamss() {
i.Order = i.Ctx.Query("Order")
if !maps.IsExists(orders, i.Order) {
order := config.GetConfig().PostOrder
i.Order = "asc"
if order != "" && maps.IsExists(orders, order) {
i.Order = order
}
}
i.Page = str.ToInteger(i.Ctx.Param("page"), 1)
total := int(atomic.LoadInt64(&dao.TotalRaw))
if total > 0 && total < (i.Page-1)*i.PageSize {
i.Page = 1
}
if i.Page > 1 && (i.Category != "" || i.Search != "" || i.Month != "") {
i.setTitleLR(fmt.Sprintf("%s-第%d页", i.TitleL, i.Page), i.BlogName)
}
return
}

View File

@ -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
})
}

View File

@ -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"),
)
}

View File

@ -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)))
}

View File

@ -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()
}

View File

@ -1,41 +0,0 @@
package wp
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"strings"
)
var iconSizes = []string{"site_icon-270", "site_icon-32", "site_icon-192", "site_icon-180"}
func CalSiteIcon(h *Handle) (r string) {
id := str.ToInteger[uint64](wpconfig.GetOption("site_icon"), 0)
if id < 1 {
return
}
icon, err := cache.GetPostById(h.C, id)
if err != nil || icon.AttachmentMetadata.File == "" {
return
}
m := strings.Join(strings.Split(icon.AttachmentMetadata.File, "/")[:2], "/")
size := slice.FilterAndMap(iconSizes, func(t string) (string, bool) {
s, ok := icon.AttachmentMetadata.Sizes[t]
if !ok {
return "", false
}
switch t {
case "site_icon-270":
return fmt.Sprintf(`<meta name="msapplication-TileImage" content="/wp-content/uploads/%s/%s" />`, m, s.File), true
case "site_icon-180":
return fmt.Sprintf(`<link rel="apple-touch-icon" href="/wp-content/uploads/%s/%s" />`, m, s.File), true
default:
ss := strings.Replace(t, "site_icon-", "", 1)
return fmt.Sprintf(`<link rel="icon" href="/wp-content/uploads/%s/%s" sizes="%sx%s" />`, m, s.File, ss, ss), true
}
})
r = strings.Join(size, "\n")
return
}

View File

@ -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
}

View File

@ -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}}

View File

@ -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"))
}

View File

@ -1,79 +0,0 @@
package wpconfig
import (
"context"
"github.com/fthvgb1/wp-go/app/phphelper"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/model"
"github.com/fthvgb1/wp-go/safety"
"strings"
)
var options safety.Map[string, string]
var phpArr safety.Map[string, map[any]any]
var ctx context.Context
func InitOptions() error {
options.Flush()
phpArr.Flush()
if ctx == nil {
ctx = context.Background()
}
ops, err := model.FindToStringMap[models.Options](ctx, model.Conditions(
model.Where(model.SqlBuilder{{"autoload", "yes"}}),
model.Fields("option_name k, option_value v"),
))
if err != nil {
return err
}
for _, option := range ops {
options.Store(option["k"], option["v"])
}
return nil
}
func GetOption(k string) string {
v, ok := options.Load(k)
if ok {
return v
}
vv, err := model.GetField[models.Options](ctx, "option_value",
model.Conditions(
model.Where(
model.SqlBuilder{{"option_name", k}},
),
),
)
options.Store(k, vv)
if err != nil {
return ""
}
return vv
}
func GetLang() string {
s, ok := options.Load("WPLANG")
if !ok {
s = "zh-CN"
}
return strings.Replace(s, "_", "-", 1)
}
func GetPHPArrayVal[T any](optionName string, defaults T, key ...any) T {
op, ok := phpArr.Load(optionName)
if ok {
return maps.GetAnyAnyValWithDefaults(op, defaults, key...)
}
v := GetOption(optionName)
if v == "" {
return defaults
}
arr, err := phphelper.UnPHPSerializeToAnyAnyMap(v)
if err != nil {
return defaults
}
phpArr.Store(optionName, arr)
return maps.GetAnyAnyValWithDefaults(arr, defaults, key...)
}

View File

@ -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
}

View File

@ -1,186 +0,0 @@
package wpconfig
import (
"embed"
"fmt"
"github.com/fthvgb1/wp-go/app/phphelper"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/safety"
"path/filepath"
"strings"
)
var templateFs embed.FS
func SetTemplateFs(fs embed.FS) {
templateFs = fs
}
// ThemeMods 只有部分公共的参数,其它的参数调用 GetThemeModsVal 函数获取
type ThemeMods struct {
CustomCssPostId int `json:"custom_css_post_id,omitempty"`
NavMenuLocations map[string]int `json:"nav_menu_locations,omitempty"`
CustomLogo int `json:"custom_logo,omitempty"`
HeaderImage string `json:"header_image,omitempty"`
BackgroundImage string `json:"background_image,omitempty"`
BackgroundSize string `json:"background_size,omitempty"`
BackgroundRepeat string `json:"background_repeat,omitempty"`
BackgroundColor string `json:"background_color,omitempty"`
BackgroundPreset string `json:"background_preset"`
BackgroundPositionX string `json:"background_position_x,omitempty"`
BackgroundPositionY string `json:"background_position_y"`
BackgroundAttachment string `json:"background_attachment"`
ColorScheme string `json:"color_scheme"`
SidebarTextcolor string `json:"sidebar_textcolor,omitempty"`
HeaderBackgroundColor string `json:"header_background_color,omitempty"`
HeaderTextcolor string `json:"header_textcolor,omitempty"`
HeaderVideo int `json:"header_video,omitempty"`
ExternalHeaderVideo string `json:"external_header_video,omitempty"`
HeaderImagData ImageData `json:"header_image_data,omitempty"`
SidebarsWidgets Sidebars `json:"sidebars_widgets,omitempty"`
ThemeSupport ThemeSupport
}
type Sidebars struct {
Time int `json:"time,omitempty"`
Data SidebarsData `json:"data,omitempty"`
}
type ColorScheme struct {
Label string `json:"label,omitempty"`
Colors []string `json:"colors,omitempty"`
}
type SidebarsData struct {
WpInactiveWidgets []string `json:"wp_inactive_widgets,omitempty"`
Sidebar1 []string `json:"sidebar-1,omitempty"`
Sidebar2 []string `json:"sidebar-2,omitempty"`
Sidebar3 []string `json:"sidebar-3,omitempty"`
}
type ImageData struct {
AttachmentId int64 `json:"attachment_id,omitempty"`
Url string `json:"url,omitempty"`
ThumbnailUrl string `json:"thumbnail_url,omitempty"`
Height int64 `json:"height,omitempty"`
Width int64 `json:"width,omitempty"`
}
func Thumbnail(metadata models.WpAttachmentMetadata, Type, host string, except ...string) (r models.PostThumbnail) {
up := strings.Split(metadata.File, "/")
if metadata.File != "" && Type == "full" {
mimeType := metadata.Sizes["thumbnail"].MimeType
metadata.Sizes = maps.Copy(metadata.Sizes)
metadata.Sizes["full"] = models.MetaDataFileSize{
File: filepath.Base(metadata.File),
Width: metadata.Width,
Height: metadata.Height,
MimeType: mimeType,
FileSize: metadata.FileSize,
}
}
if siz, ok := metadata.Sizes[Type]; ok {
r.Path = fmt.Sprintf("%s/wp-content/uploads/%s", host, strings.ReplaceAll(metadata.File, filepath.Base(metadata.File), siz.File))
r.Width = metadata.Sizes[Type].Width
r.Height = metadata.Sizes[Type].Height
r.Srcset = strings.Join(maps.FilterToSlice[string](metadata.Sizes, func(s string, size models.MetaDataFileSize) (r string, ok bool) {
up[len(up)-1] = size.File
for _, s2 := range except {
if s == s2 {
return
}
}
r = fmt.Sprintf("%s/wp-content/uploads/%s %dw", host, strings.Join(up, "/"), size.Width)
ok = true
return
}), ", ")
r.Sizes = fmt.Sprintf("(max-width: %dpx) 100vw, %dpx", r.Width, r.Width)
if r.Width >= 740 && r.Width < 767 {
r.Sizes = "(max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px"
} else if r.Width >= 767 {
r.Sizes = "(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px"
}
r.OriginAttachmentData = metadata
}
return
}
var themeModes = func() *safety.Map[string, ThemeMods] {
m := safety.NewMap[string, ThemeMods]()
themeModsRaw = safety.NewMap[string, map[string]any]()
reload.Append(func() {
m.Flush()
themeModsRaw.Flush()
}, "theme-modes")
return m
}()
var themeModsRaw *safety.Map[string, map[string]any]
var themeSupport = map[string]ThemeSupport{}
func SetThemeSupport(theme string, support ThemeSupport) {
themeSupport[theme] = support
}
func GetThemeModsVal[T any](theme, k string, defaults T) (r T) {
m, ok := themeModsRaw.Load(theme)
if !ok {
r = defaults
return
}
r = maps.GetStrAnyValWithDefaults(m, k, defaults)
return
}
func GetThemeMods(theme string) (r ThemeMods, err error) {
r, ok := themeModes.Load(theme)
if ok {
return
}
mods := GetOption(fmt.Sprintf("theme_mods_%s", theme))
if mods == "" {
return
}
m, err := phphelper.UnPHPSerializeToStrAnyMap(mods)
if err != nil {
return
}
themeModsRaw.Store(theme, m)
//这里在的err可以不用处理因为php的默认值和有设置过的类型可能不一样直接按有设置的类型处理就行
r, err = maps.StrAnyMapToStruct[ThemeMods](m)
if err != nil {
logs.Error(err, "解析thememods错误(可忽略)")
err = nil
}
r.setThemeSupport(theme)
themeModes.Store(theme, r)
return
}
func IsCustomBackground(theme string) bool {
mods, err := GetThemeMods(theme)
if err != nil {
return false
}
if mods.BackgroundColor != "" && mods.BackgroundColor != "default-color" || mods.BackgroundImage != "" && mods.BackgroundImage != "default-image" {
return true
}
return false
}
func (m *ThemeMods) setThemeSupport(themeName string) {
var v ThemeSupport
vv, ok := themeSupport[themeName]
if ok {
m.ThemeSupport = vv
} else {
m.ThemeSupport = v
}
}

Some files were not shown because too many files have changed in this diff Show More