Compare commits
383 Commits
Author | SHA1 | Date | |
---|---|---|---|
8aeec60181 | |||
58d3e4f645 | |||
e592a753bf | |||
d598fb5ce5 | |||
63f769796f | |||
7cbfdf8601 | |||
9c60d10568 | |||
dcbe760f09 | |||
d220bb2a6c | |||
eb7ccd4f2d | |||
39c9dc1b09 | |||
68beb0fcbb | |||
1f5f7f465d | |||
b7c09cb5a2 | |||
5942f76972 | |||
1f40810b78 | |||
da858d55e0 | |||
166bb08ed0 | |||
5eaf798a6c | |||
e2e6bcc8ce | |||
daa7101493 | |||
6f51f1c295 | |||
a8d1dcdd5b | |||
9af2257650 | |||
63591899bb | |||
ee9ba3fcf0 | |||
a78815f3d3 | |||
6209f2b5e5 | |||
08d3bca39c | |||
9b8e597066 | |||
3215b0c8ea | |||
c7c97c469f | |||
b45ad0e54e | |||
a0c6e48e83 | |||
3c3ed73971 | |||
be58a35245 | |||
67bdf1e070 | |||
981e0ad85b | |||
74159940b5 | |||
206e5902d1 | |||
2d38b36525 | |||
af712f06a5 | |||
73575965d3 | |||
f49ad731e3 | |||
802c7c7dcc | |||
20ea36c727 | |||
fd3199a83c | |||
e25f679ea6 | |||
4842d53316 | |||
65bfccdd48 | |||
c60146d614 | |||
9285698ee4 | |||
5e18c9babd | |||
8bdc53d175 | |||
ad9bdc7574 | |||
0352dd0fd0 | |||
6044b8eaec | |||
8d3197ee98 | |||
8fcf3ecca2 | |||
4d9d011213 | |||
12b75b9d82 | |||
568ab15a34 | |||
c38b62c82a | |||
0774b122ee | |||
f7d2377101 | |||
088fc306de | |||
57d345e445 | |||
f257a966e6 | |||
ceffeccf8d | |||
eed45f51ba | |||
d72bed0c8c | |||
0cdb3ba040 | |||
227de8bdc8 | |||
74304b5b12 | |||
ac29cf2448 | |||
137b2a9738 | |||
9f49a274cd | |||
7c3f8baaa2 | |||
547d8e59e6 | |||
7bbc961f72 | |||
66c02f7fc9 | |||
a5294e2e20 | |||
7978e9ee74 | |||
f6a5cf4255 | |||
928a608878 | |||
42d2795a05 | |||
041d06104b | |||
d1fb560578 | |||
42484e3f96 | |||
64a2c2e33b | |||
86d1616732 | |||
2eb58f732b | |||
936d033547 | |||
cd4787991d | |||
71ddf299e4 | |||
0f467bbc98 | |||
171b3bc59c | |||
f369cf4f22 | |||
ddf6e62e5a | |||
e502f581e1 | |||
acb064b762 | |||
f0c1744998 | |||
876a81c062 | |||
78f5157efe | |||
acbcbf455f | |||
179545f2bb | |||
48a3fe6588 | |||
3fd4f412b1 | |||
5273f4ecf9 | |||
b9c2527f4c | |||
1286338af0 | |||
21a4ff3b3e | |||
2302aa7ff8 | |||
bc71be7238 | |||
a4274b74dd | |||
03b448e70c | |||
879238cda1 | |||
a00bfff91c | |||
96daa93656 | |||
173eef2adc | |||
c5c3d5f78b | |||
228e19ee85 | |||
b1ebedd24c | |||
282e0c26e0 | |||
e3d57830b0 | |||
937e766294 | |||
19c0952005 | |||
3493eee919 | |||
e4fa93e893 | |||
a08b9538ae | |||
7431823ac7 | |||
23c9cc1596 | |||
ae6e496dd8 | |||
e408efacc8 | |||
448d6a6baf | |||
b6091c6b42 | |||
f2d69196bc | |||
b41b6e0a09 | |||
fe9ac0d126 | |||
1c33665e34 | |||
bafd839637 | |||
b4d3a41459 | |||
4705d17e0e | |||
60fd016b5f | |||
95e3298b7e | |||
d5a546c01a | |||
9580678b55 | |||
2d90b05cf8 | |||
0827cdc551 | |||
7c50f2b1a1 | |||
c95fd7e5da | |||
4242d850ff | |||
6caf07b575 | |||
044e55a399 | |||
a6ee333232 | |||
1353541c94 | |||
8f995d23dc | |||
871649a7e9 | |||
debcad6292 | |||
65a0ca94f6 | |||
40451970a0 | |||
dfe7bb3181 | |||
b0618cf800 | |||
21d0b8c041 | |||
471a58658f | |||
68c345f928 | |||
4058ff9b7b | |||
036f1a1212 | |||
87f639da58 | |||
ec211e8970 | |||
b69b01f27b | |||
7c571654a6 | |||
90d2432575 | |||
972ff7d815 | |||
2d2c6445e8 | |||
f9b565294f | |||
b87a1b4a1c | |||
625da92f10 | |||
0d3481f722 | |||
4323c508ba | |||
3ba2c02db5 | |||
2d61243ef1 | |||
120c874a8d | |||
da7dd410cd | |||
43ec093e4c | |||
43754377e8 | |||
3f299e5a84 | |||
4760f65b9b | |||
345cdcd4e0 | |||
72bad0fa36 | |||
023212ba7b | |||
8a9209196e | |||
8da369b166 | |||
9e71de8e97 | |||
f735211a92 | |||
5266e85f21 | |||
98cee2f18b | |||
b53819f733 | |||
125764711d | |||
db49f99c04 | |||
0d8ee89580 | |||
e505ee2e03 | |||
df7bd8c1c6 | |||
9e293e7f39 | |||
1f3ca99441 | |||
c09f54b9c0 | |||
78f0c0a87a | |||
1ecb338af5 | |||
7e55253126 | |||
e7e643f5d3 | |||
522a358819 | |||
96368234de | |||
49e4210365 | |||
54448f68b0 | |||
e10dc4e45e | |||
13520a0d43 | |||
bebe6bac81 | |||
a1f36da904 | |||
6f800230b0 | |||
e1c1da6083 | |||
ab56f45790 | |||
341b7197b8 | |||
26bdcb44ac | |||
e7ea2bf334 | |||
d48a156983 | |||
00c16c03a4 | |||
ec4dfad86a | |||
322d2ebe0a | |||
57a21010a8 | |||
14aad9e15b | |||
3f96c09d36 | |||
ca94295eb7 | |||
aaa1f3c937 | |||
decea28316 | |||
f6e2f86ee7 | |||
cb1ce2e878 | |||
e03194b768 | |||
8d398016ac | |||
a313210f71 | |||
2d476ea4f6 | |||
b6b92ede34 | |||
12a1fea5ed | |||
d211e49036 | |||
cc09668fd7 | |||
179e1e14e2 | |||
ad875857d8 | |||
1da5062356 | |||
1f7e51858b | |||
096514a677 | |||
240a41f1eb | |||
4f1a2f717e | |||
8b7edea000 | |||
01e10f69dd | |||
2f456f62c0 | |||
a905f59eae | |||
997839e98a | |||
f3595874ae | |||
5665e0021b | |||
|
a3b1e054d0 | ||
|
2ea52f1752 | ||
|
d3be997b8f | ||
|
0d62ebd5a5 | ||
|
5ce707e427 | ||
|
2ca1f10bfc | ||
|
ae20829209 | ||
|
9af2a04a97 | ||
|
4d50f60c62 | ||
|
7d27668159 | ||
|
cbf3cabb24 | ||
|
dd384b0169 | ||
|
e1d8c0098f | ||
|
ccc02399ef | ||
|
9b46a86d5d | ||
|
26950a37bb | ||
|
eab6db83eb | ||
|
1b1e3bf8f3 | ||
|
5ae0ad646e | ||
|
477d8fe6f9 | ||
|
b4cc570e8a | ||
|
9c89f44841 | ||
|
11e0c32c6f | ||
|
0f0c3997c0 | ||
|
e0786f7f8b | ||
|
00e42c2d56 | ||
|
e978e86304 | ||
|
8a5fc02247 | ||
|
ccbba30ae2 | ||
|
865b37cf89 | ||
|
bd7d20160a | ||
|
2a87ebdfd6 | ||
|
6d1fde655d | ||
|
efeeb8b675 | ||
|
4aa54b1ca7 | ||
|
2aacb682e1 | ||
|
2a68b73773 | ||
|
f885b5c8f0 | ||
|
0bc93850ed | ||
|
409ffd8081 | ||
|
df11d6522d | ||
|
b588b7e6d4 | ||
|
56f869c10f | ||
|
226c98cbd9 | ||
|
73fb725960 | ||
|
a319e7595f | ||
|
cb3d043c90 | ||
|
ccfe077947 | ||
|
75c9a47799 | ||
|
d043e75dfd | ||
|
a18616f50f | ||
|
5ca1a2c592 | ||
|
0faac992cf | ||
|
6ad2b3cf64 | ||
|
7d95109e78 | ||
|
cd5a9eadd7 | ||
|
49b51dadcb | ||
|
a635b9a8ea | ||
|
3c21187ee3 | ||
|
1d80534333 | ||
|
f722d685ea | ||
|
c60c11769a | ||
|
a6f80c2639 | ||
|
bc6b3675ab | ||
|
729da0495d | ||
|
8dded98f39 | ||
|
72a37d3bc8 | ||
|
f96f364a69 | ||
|
1451b33af4 | ||
|
b1e824b27d | ||
|
bfd4e4ac4d | ||
|
23f32c8fed | ||
|
a2cebfbb3f | ||
|
cd636e2dc9 | ||
|
ef71192add | ||
|
daa9a26e00 | ||
|
3069bc3892 | ||
|
efd174631b | ||
|
b495dda30f | ||
|
55a9e90ec9 | ||
|
abfcc9b1ad | ||
|
1100b89312 | ||
|
d88dd6b853 | ||
|
febaab279d | ||
|
796ae6f0fa | ||
|
439a49cf12 | ||
|
f0028e72f4 | ||
|
fd951b7ab8 | ||
|
e474fefdae | ||
d69a04e65d | |||
|
fda83e5d1a | ||
|
562520c080 | ||
|
b453910f82 | ||
|
0b0e701bef | ||
|
b1859409da | ||
|
4ec03cba9b | ||
|
f28c41c84a | ||
|
dc203f87b8 | ||
|
404f27f693 | ||
|
f31f7a4cc0 | ||
|
907d5be596 | ||
|
eee0b75d19 | ||
a75c48cdb4 | |||
|
09e99ac379 | ||
7997a9f512 | |||
|
285887d0e2 | ||
996beb12a8 | |||
|
da236257cc | ||
|
1380c0e1e4 | ||
|
8664d1181d | ||
|
4596f4541b | ||
0155d29f77 | |||
|
d185f78d6c | ||
|
29bbeef9dc | ||
504b0efd66 | |||
|
f831499341 | ||
c130c46476 | |||
74808e22fb | |||
0c0fafedec | |||
7e935a8fa6 | |||
8d8ab4ab9b | |||
1267d45ffb | |||
68d045157c | |||
f470a0c31f | |||
06e91853c1 |
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,3 +1,7 @@
|
||||
.idea
|
||||
wp-go.iml
|
||||
config.yaml
|
||||
/wp-go.iml
|
||||
/config.yaml
|
||||
err.log
|
||||
/plugins/
|
||||
/config.json
|
||||
go.sum
|
@ -1,8 +1,7 @@
|
||||
FROM golang:latest as gobulidIso
|
||||
FROM golang:1.22.2-alpine as gobulidIso
|
||||
COPY ./ /go/src/wp-go
|
||||
WORKDIR /go/src/wp-go
|
||||
ENV GOPROXY="https://goproxy.cn"
|
||||
RUN go build -ldflags "-w" -tags netgo -o wp-go internal/cmd/main.go
|
||||
RUN go build -ldflags "-w" -tags netgo -o wp-go app/cmd/main.go
|
||||
|
||||
FROM alpine:latest
|
||||
WORKDIR /opt/wp-go
|
||||
|
84
README.md
84
README.md
@ -1,19 +1,87 @@
|
||||
## wp-go
|
||||
a simple front of WordPress build with golang.
|
||||
|
||||
一个go写的WordPress的前端,功能比较简单,只有列表页和详情页,rss2,主题只有twentyfifteen和twentyseventeen两套主题,插件的话只有一个简单的列表页的摘要生成和enlighter代码高亮。本身只用于展示文章,添加评论走的转发请求到php的WordPress。因为大量用了泛型功能,所以要求go的版本在1.18及以上。
|
||||
[en readme](https://github.com/fthvgb1/wp-go/blob/master/readme_en.md)
|
||||
|
||||
一个go写的WordPress的前端,功能比较简单,只有列表页和详情页,rss2,主题只有twentyfifteen和twentyseventeen两套主题,插件的话只有一个简单的列表页的摘要生成和enlighter代码高亮。本身只用于展示文章及评论。要求go的版本在1.20以上,越新越好。。。
|
||||
|
||||
#### 特色功能
|
||||
|
||||
- 多种缓存配置
|
||||
- 基本实现全站缓存,并且可防止缓存击穿
|
||||
- 列表页也可以高亮语法格式化显示代码
|
||||
- 简易插件扩展开发机制、配置后支持热加载更新
|
||||
- 使用.so扩展主题、插件、路由等
|
||||
- 丰富繁杂的配置,呃,配置是有点儿多,虽然大部分都是可选项。。。
|
||||
- 添加评论或panic时发邮件通知,包涵栈调用和请求信息
|
||||
- 简单的流量限制中间件
|
||||
- 简单的流量限制中间件,可以限制全瞬时最大请求数量
|
||||
- 除配置文件外将所有静态资源都打包到执行文件中
|
||||
- 支持密码查看,且cookie信息可被php版所验证
|
||||
- 支持rss2订阅
|
||||
- 热更新配置、清空缓存
|
||||
- kill -SIGUSR1 PID 更新配置和清空缓存
|
||||
- kill -SIGUSR2 PID 清空缓存
|
||||
- 热更新配置、切换主题、清空缓存
|
||||
- kill -SIGUSR1 PID 更新配置和清空缓存
|
||||
- kill -SIGUSR2 PID 清空缓存
|
||||
|
||||
#### 运行
|
||||
```
|
||||
go run app/cmd/main.go [-c configpath] [-p port]
|
||||
```
|
||||
|
||||
#### 数据显示支持程度
|
||||
|
||||
| 页表 | 支持程度 |
|
||||
|-----|---------------------------------------------|
|
||||
| 列表页 | 首页/搜索/归档/分类/标签/作者 分页列表 |
|
||||
| 详情页 | 显示内容、评论并可以添加评论(转发的php处理,需要配置php版的添加评论的url) |
|
||||
| 侧边栏 | 支持旧版 近期文章、近期评论、规档、分类、其它操作 显示及设置, 支持新版 分类 |
|
||||
|
||||
#### 后台设置支持程度
|
||||
|
||||
- 仪表盘
|
||||
- 外观
|
||||
- 小工具
|
||||
- 搜索
|
||||
- 规档
|
||||
- 近期文章
|
||||
- 近期评论
|
||||
- 分类
|
||||
- 其它操作
|
||||
|
||||
- 设置-
|
||||
- 常规
|
||||
- 站点标题
|
||||
- 副标题
|
||||
- 阅读
|
||||
- 博客页面至多显示数量
|
||||
- Feed中显示最近数量
|
||||
- 讨论
|
||||
- 其他评论设置
|
||||
- `启用|禁止`评论嵌套,最多嵌套层数
|
||||
- 分页显示评论,每页显示评论条数,默认显示`最前/后`页
|
||||
- 在每个页面顶部显示 `新旧`评论
|
||||
|
||||
#### 主题支持程度
|
||||
|
||||
| twentyfifteen | twentyseventeen |
|
||||
|---------------|-----------------|
|
||||
| 站点身份 | 站点身份 |
|
||||
| 颜色 | 颜色 |
|
||||
| 页眉图片 | 页眉媒体 |
|
||||
| 背景图片 | 额外css |
|
||||
| 额外css | |
|
||||
|
||||
#### 插件机制
|
||||
|
||||
分为对列表页文章数据的修改的插件和对影响整个程序表现的插件
|
||||
|
||||
| 列表页文章数据插件 | 整个程序表现的插件 |
|
||||
|---------------------|--------------------------------------|
|
||||
| digest 自动生成指定长度的摘要 | enlighter 代码高亮(需要在后台安装enlighterjs插件) |
|
||||
| | hiddenLogin 隐藏登录入口 |
|
||||
|
||||
#### 其它
|
||||
用的gin框架和sqlx,在外面封装了层查询的方法。后台可以设置的比较少,大部分设置还没打通。
|
||||
|
||||
用的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>
|
||||
|
||||
|
293
app/actions/comment.go
Normal file
293
app/actions/comment.go
Normal file
@ -0,0 +1,293 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/mail"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CommentForm struct {
|
||||
CommentPostId uint64 `form:"comment_post_ID" binding:"required" json:"comment_post_ID"`
|
||||
Author string `form:"author" binding:"required" label:"显示名称" json:"author"`
|
||||
Email string `form:"email" binding:"required,email"`
|
||||
Comment string `form:"comment" binding:"required" label:"评论" json:"comment"`
|
||||
}
|
||||
|
||||
func PostComment(c *gin.Context) {
|
||||
cli := &http.Client{
|
||||
Timeout: time.Second * 3,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
data, err := c.GetRawData()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Writer.WriteHeader(http.StatusConflict)
|
||||
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
var v validator.ValidationErrors
|
||||
if errors.As(err, &v) {
|
||||
e := v.Translate(config.GetZh())
|
||||
for _, v := range e {
|
||||
fmt.Fprintf(c.Writer, fail, v)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.Writer.WriteString("评论出错,请联系管理员或稍后再度")
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
conf := config.GetConfig()
|
||||
if err != nil {
|
||||
logs.Error(err, "获取评论数据错误")
|
||||
return
|
||||
}
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
|
||||
var comment CommentForm
|
||||
if err = c.ShouldBind(&comment); err != nil {
|
||||
return
|
||||
}
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
|
||||
req, err := http.NewRequest("POST", conf.PostCommentUrl, strings.NewReader(c.Request.PostForm.Encode()))
|
||||
if err != nil {
|
||||
logs.Error(err, "创建评论请求错误")
|
||||
return
|
||||
}
|
||||
defer req.Body.Close()
|
||||
req.Header = c.Request.Header.Clone()
|
||||
home, err := url.Parse(wpconfig.GetOption("siteurl"))
|
||||
if err != nil {
|
||||
logs.Error(err, "解析评论接口错误")
|
||||
return
|
||||
}
|
||||
req.Host = home.Host
|
||||
res, err := cli.Do(req)
|
||||
if err != nil && !errors.Is(err, http.ErrUseLastResponse) {
|
||||
logs.Error(err, "请求评论接口错误")
|
||||
return
|
||||
}
|
||||
if res.StatusCode == http.StatusFound {
|
||||
for _, cookie := range res.Cookies() {
|
||||
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
|
||||
}
|
||||
u := res.Header.Get("Location")
|
||||
up, er := url.Parse(u)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
cu, er := url.Parse(conf.PostCommentUrl)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
up.Host = cu.Host
|
||||
up.Scheme = "http"
|
||||
newReq, _ := http.NewRequest("GET", up.String(), nil)
|
||||
newReq.Host = home.Host
|
||||
newReq.Header.Set("Cookie", strings.Join(slice.Map(c.Request.Cookies(), func(t *http.Cookie) string {
|
||||
return fmt.Sprintf("%s=%s", t.Name, t.Value)
|
||||
}), "; "))
|
||||
ress, er := http.DefaultClient.Do(newReq)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
cc := c.Copy()
|
||||
go func() {
|
||||
if gin.Mode() != gin.ReleaseMode {
|
||||
return
|
||||
}
|
||||
id := comment.CommentPostId
|
||||
if id <= 0 {
|
||||
logs.Error(errors.New("获取文档id错误"), "", comment.CommentPostId)
|
||||
return
|
||||
}
|
||||
post, err := cache.GetPostById(cc, id)
|
||||
if err != nil {
|
||||
logs.Error(err, "获取文档错误", id)
|
||||
return
|
||||
}
|
||||
su := fmt.Sprintf("%s: %s[%s]发表了评论对文档[%v]的评论", wpconfig.GetOption("siteurl"), comment.Author, comment.Email, post.PostTitle)
|
||||
err = mail.SendMail([]string{conf.Mail.User}, su, comment.Comment)
|
||||
logs.IfError(err, "发送邮件", conf.Mail.User, su, comment)
|
||||
}()
|
||||
|
||||
s, er := io.ReadAll(ress.Body)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
uuu := ""
|
||||
uu, _ := url.Parse(res.Header.Get("Location"))
|
||||
if up.RawQuery != "" {
|
||||
cache.NewCommentCache().Set(c, up.RawQuery, string(s))
|
||||
uuu = str.Join(uu.Path, "?", uu.RawQuery)
|
||||
} else {
|
||||
uuu = str.Join(uu.Path)
|
||||
}
|
||||
c.Redirect(http.StatusFound, uuu)
|
||||
return
|
||||
}
|
||||
var r io.Reader
|
||||
if res.Header.Get("Content-Encoding") == "gzip" {
|
||||
r, err = gzip.NewReader(res.Body)
|
||||
if err != nil {
|
||||
logs.Error(err, "gzip解压错误")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
r = res.Body
|
||||
}
|
||||
s, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
logs.Error(err, "读取结果错误")
|
||||
return
|
||||
}
|
||||
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
c.Writer.WriteHeader(res.StatusCode)
|
||||
_, _ = c.Writer.Write(s)
|
||||
|
||||
}
|
||||
|
||||
var fail = `
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name='robots' content='max-image-preview:large, noindex, follow' />
|
||||
<title>评论提交失败</title>
|
||||
<style type="text/css">
|
||||
html {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
body {
|
||||
background: #fff;
|
||||
border: 1px solid #ccd0d4;
|
||||
color: #444;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
margin: 2em auto;
|
||||
padding: 1em 2em;
|
||||
max-width: 700px;
|
||||
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
|
||||
}
|
||||
h1 {
|
||||
border-bottom: 1px solid #dadada;
|
||||
clear: both;
|
||||
color: #666;
|
||||
font-size: 24px;
|
||||
margin: 30px 0 0 0;
|
||||
padding: 0;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
#error-page {
|
||||
margin-top: 50px;
|
||||
}
|
||||
#error-page p,
|
||||
#error-page .wp-die-message {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
margin: 25px 0 20px;
|
||||
}
|
||||
#error-page code {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
}
|
||||
ul li {
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px ;
|
||||
}
|
||||
a {
|
||||
color: #0073aa;
|
||||
}
|
||||
a:hover,
|
||||
a:active {
|
||||
color: #006799;
|
||||
}
|
||||
a:focus {
|
||||
color: #124964;
|
||||
-webkit-box-shadow:
|
||||
0 0 0 1px #5b9dd9,
|
||||
0 0 2px 1px rgba(30, 140, 190, 0.8);
|
||||
box-shadow:
|
||||
0 0 0 1px #5b9dd9,
|
||||
0 0 2px 1px rgba(30, 140, 190, 0.8);
|
||||
outline: none;
|
||||
}
|
||||
.button {
|
||||
background: #f3f5f6;
|
||||
border: 1px solid #016087;
|
||||
color: #016087;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
line-height: 2;
|
||||
height: 28px;
|
||||
margin: 0;
|
||||
padding: 0 10px 1px;
|
||||
cursor: pointer;
|
||||
-webkit-border-radius: 3px;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.button.button-large {
|
||||
line-height: 2.30769231;
|
||||
min-height: 32px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
.button:focus {
|
||||
background: #f3f5f6;
|
||||
border-color: #007cba;
|
||||
-webkit-box-shadow: 0 0 0 1px #007cba;
|
||||
box-shadow: 0 0 0 1px #007cba;
|
||||
color: #016087;
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
.button:active {
|
||||
background: #f3f5f6;
|
||||
border-color: #7e8993;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body id="error-page">
|
||||
<div class="wp-die-message"><p><strong>错误:</strong>%s</p></div>
|
||||
<p><a href='javascript:history.back()'>« 返回</a></p></body>
|
||||
</html>
|
||||
|
||||
`
|
108
app/actions/feed.go
Normal file
108
app/actions/feed.go
Normal file
@ -0,0 +1,108 @@
|
||||
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))
|
||||
}
|
@ -2,9 +2,9 @@ package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/phphelper"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/internal/phpass"
|
||||
"github.com/fthvgb1/wp-go/internal/wpconfig"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
@ -28,12 +28,12 @@ func Login(c *gin.Context) {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
pass, err := phpass.NewPasswordHash(8, true).HashPassword(password)
|
||||
pass, err := phphelper.NewPasswordHash(8, true).HashPassword(password)
|
||||
if err != nil {
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
cohash := fmt.Sprintf("wp-postpass_%s", str.Md5(wpconfig.Options.Value("siteurl")))
|
||||
cohash := fmt.Sprintf("wp-postpass_%s", str.Md5(wpconfig.GetOption("siteurl")))
|
||||
c.SetCookie(cohash, pass, 24*3600, "/", "", false, false)
|
||||
|
||||
c.Redirect(http.StatusFound, ref)
|
15
app/actions/themehook.go
Normal file
15
app/actions/themehook.go
Normal file
@ -0,0 +1,15 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/theme"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func ThemeHook(scene string) func(*gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
t := theme.GetCurrentTheme()
|
||||
h := wp.NewHandle(c, scene, t)
|
||||
theme.Hook(t, h)
|
||||
}
|
||||
}
|
111
app/cmd/main.go
Normal file
111
app/cmd/main.go
Normal file
@ -0,0 +1,111 @@
|
||||
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)
|
||||
}
|
||||
}
|
@ -3,8 +3,8 @@ package mail
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/internal/pkg/config"
|
||||
"github.com/soxfmr/gomail"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"gopkg.in/gomail.v2"
|
||||
"mime"
|
||||
"path"
|
||||
)
|
||||
@ -18,7 +18,7 @@ func SendMail(mailTo []string, subject string, body string, files ...string) err
|
||||
m := gomail.NewMessage(
|
||||
gomail.SetEncoding(gomail.Base64),
|
||||
)
|
||||
c := config.Conf.Load()
|
||||
c := config.GetConfig()
|
||||
m.SetHeader("From",
|
||||
m.FormatAddress(c.Mail.User,
|
||||
c.Mail.Alias,
|
||||
@ -45,7 +45,7 @@ func SendMail(mailTo []string, subject string, body string, files ...string) err
|
||||
c.Mail.User,
|
||||
c.Mail.Pass,
|
||||
)
|
||||
if !c.Mail.Ssl {
|
||||
if c.Mail.InsecureSkipVerify {
|
||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
err := d.DialAndSend(m)
|
@ -1,7 +1,7 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/internal/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"testing"
|
||||
)
|
||||
|
@ -19,9 +19,9 @@ func FlowLimit(maxRequestSleepNum, maxRequestNum int64, sleepTime []time.Duratio
|
||||
}
|
||||
s := safety.Var[[]time.Duration]{}
|
||||
s.Store(sleepTime)
|
||||
fn := func(msn, mn int64, st []time.Duration) {
|
||||
atomic.StoreInt64(&maxRequestSleepNum, msn)
|
||||
atomic.StoreInt64(&maxRequestNum, mn)
|
||||
fn := func(sleepNum, maxNum int64, st []time.Duration) {
|
||||
atomic.StoreInt64(&maxRequestSleepNum, sleepNum)
|
||||
atomic.StoreInt64(&maxRequestNum, maxNum)
|
||||
s.Store(st)
|
||||
}
|
||||
return func(c *gin.Context) {
|
@ -25,14 +25,25 @@ func IpLimit(num int64) (func(ctx *gin.Context), func(int64)) {
|
||||
fn(num)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
if atomic.LoadInt64(m.limitNum) <= 0 {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
ip := c.ClientIP()
|
||||
s := false
|
||||
m.mux.RLock()
|
||||
i, ok := m.m[ip]
|
||||
m.mux.RUnlock()
|
||||
|
||||
if !ok {
|
||||
m.mux.Lock()
|
||||
i = new(int64)
|
||||
m.m[ip] = i
|
||||
m.mux.Unlock()
|
||||
}
|
||||
|
||||
defer func() {
|
||||
ii := atomic.LoadInt64(i)
|
||||
if s && ii > 0 {
|
||||
if ii > 0 {
|
||||
atomic.AddInt64(i, -1)
|
||||
if atomic.LoadInt64(i) == 0 {
|
||||
m.mux.Lock()
|
||||
@ -42,20 +53,12 @@ func IpLimit(num int64) (func(ctx *gin.Context), func(int64)) {
|
||||
}
|
||||
}()
|
||||
|
||||
if !ok {
|
||||
m.mux.Lock()
|
||||
i = new(int64)
|
||||
m.m[ip] = i
|
||||
m.mux.Unlock()
|
||||
}
|
||||
|
||||
if atomic.LoadInt64(m.limitNum) > 0 && atomic.LoadInt64(i) >= atomic.LoadInt64(m.limitNum) {
|
||||
if atomic.LoadInt64(i) >= atomic.LoadInt64(m.limitNum) {
|
||||
c.Status(http.StatusForbidden)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
atomic.AddInt64(i, 1)
|
||||
s = true
|
||||
c.Next()
|
||||
}, fn
|
||||
}
|
22
app/middleware/searchlimit.go
Normal file
22
app/middleware/searchlimit.go
Normal file
@ -0,0 +1,22 @@
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -3,15 +3,16 @@ package middleware
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/internal/mail"
|
||||
"github.com/fthvgb1/wp-go/internal/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/internal/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/internal/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/app/mail"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@ -43,11 +44,11 @@ func RecoverAndSendMail(w io.Writer) func(ctx *gin.Context) {
|
||||
)
|
||||
|
||||
er := mail.SendMail(
|
||||
[]string{config.Conf.Load().Mail.User},
|
||||
fmt.Sprintf("%s%s %s 发生错误", fmt.Sprintf(wpconfig.Options.Value("siteurl")), c.FullPath(), time.Now().Format(time.RFC1123Z)), content)
|
||||
[]string{config.GetConfig().Mail.User},
|
||||
fmt.Sprintf("%s%s %s 发生错误", fmt.Sprintf(wpconfig.GetOption("siteurl")), c.FullPath(), time.Now().Format(time.RFC1123Z)), content)
|
||||
|
||||
if er != nil {
|
||||
logs.ErrPrintln(er, "recover send mail fail", fmt.Sprintf("%v", err))
|
||||
logs.IfError(er, "recover send mail fail", fmt.Sprintf("%v", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -64,17 +65,13 @@ var (
|
||||
)
|
||||
|
||||
func formatStack(s string) (r string) {
|
||||
ss := strings.Builder{}
|
||||
ss := str.NewBuilder()
|
||||
t := strings.Split(s, "\n")
|
||||
for i, line := range t {
|
||||
if i%2 == 0 {
|
||||
ss.WriteString("<dt>")
|
||||
ss.WriteString(line)
|
||||
ss.WriteString("</dt>")
|
||||
ss.WriteString("<dt>", line, "</dt>")
|
||||
} else {
|
||||
ss.WriteString("<dd>")
|
||||
ss.WriteString(strings.Trim(line, "\t"))
|
||||
ss.WriteString("</dd>")
|
||||
ss.WriteString("<dt>", strings.Trim(line, "\t"), "</dt>")
|
||||
}
|
||||
}
|
||||
r = ss.String()
|
||||
@ -95,7 +92,7 @@ func stack(skip int) []byte {
|
||||
// Print this much at least. If we can't find the source, it won't show.
|
||||
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
|
||||
if file != lastFile {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
33
app/middleware/staticFileCache.go
Normal file
33
app/middleware/staticFileCache.go
Normal file
@ -0,0 +1,33 @@
|
||||
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()
|
||||
}
|
33
app/middleware/validateservername.go
Normal file
33
app/middleware/validateservername.go
Normal file
@ -0,0 +1,33 @@
|
||||
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()
|
||||
}
|
||||
}
|
69
app/ossigns/signs.go
Normal file
69
app/ossigns/signs.go
Normal file
@ -0,0 +1,69 @@
|
||||
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()
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package phpass
|
||||
package phphelper
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
37
app/phphelper/unserialize.go
Normal file
37
app/phphelper/unserialize.go
Normal file
@ -0,0 +1,37 @@
|
||||
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
Normal file
126
app/pkg/cache/cache.go
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/dao"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"time"
|
||||
)
|
||||
|
||||
func InitActionsCommonCache() {
|
||||
c := config.GetConfig()
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.SearchPostCacheTime, "searchPostIds", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.SearchPostCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.PostListCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.MonthPost, c.CacheTime.MonthPostCacheTime, "monthPostIds", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.MonthPostCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.GetPostContext, c.CacheTime.ContextPostCacheTime, "postContext", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.ContextPostCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(dao.GetPostsByIds, nil, c.CacheTime.PostDataCacheTime, "postData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.PostDataCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(dao.GetPostMetaByPostIds, nil, c.CacheTime.PostDataCacheTime, "postMetaData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.PostDataCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.CategoriesAndTags, c.CacheTime.CategoryCacheTime, "categoryAndTagsData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CategoryCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewVarMemoryCache(dao.RecentPosts, c.CacheTime.RecentPostCacheTime, "recentPosts", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.RecentPostCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewVarMemoryCache(RecentComment, c.CacheTime.RecentCommentsCacheTime, "recentComments", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.RecentCommentsCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.CommentNum, 30*time.Second, "commentNumber", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, PostTopComments, 30*time.Second, "PostCommentsIds", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
||||
})
|
||||
cachemanager.NewMemoryMapCache(nil, dao.PostTopCommentNum, 30*time.Second, "postTopCommentsNum", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(dao.GetCommentByIds, nil, time.Hour, "postCommentData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CommentsCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(dao.CommentChildren, nil, time.Minute, "commentChildren", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
||||
})
|
||||
|
||||
cachemanager.NewVarMemoryCache(dao.GetMaxPostId, c.CacheTime.MaxPostIdCacheTime, "maxPostId", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.MaxPostIdCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.GetUserById, c.CacheTime.UserInfoCacheTime, "userData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.UserInfoCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, dao.GetUserByName, c.CacheTime.UserInfoCacheTime, "usernameToUserData", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.UserInfoCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewVarMemoryCache(dao.AllUsername, c.CacheTime.UserInfoCacheTime, "allUsername", func() time.Duration {
|
||||
return config.GetConfig().CacheTime.UserInfoCacheTime
|
||||
})
|
||||
|
||||
cachemanager.NewVarMemoryCache(SiteFeed, time.Hour, "siteFeed")
|
||||
|
||||
cachemanager.NewMemoryMapCache(nil, PostFeed, time.Hour, "postFeed")
|
||||
|
||||
cachemanager.NewVarMemoryCache(CommentsFeed, time.Hour, "commentsFeed")
|
||||
|
||||
cachemanager.NewMemoryMapCache[string, string](nil, nil, 15*time.Minute, "NewComment")
|
||||
|
||||
InitFeed()
|
||||
}
|
||||
|
||||
type Arch struct {
|
||||
data []models.PostArchive
|
||||
fn func(context.Context) ([]models.PostArchive, error)
|
||||
month time.Month
|
||||
}
|
||||
|
||||
var arch = reload.Vars(Arch{
|
||||
fn: dao.Archives,
|
||||
}, "archives-year-month-data")
|
||||
|
||||
func Archives(ctx context.Context) []models.PostArchive {
|
||||
a := arch.Load()
|
||||
data := a.data
|
||||
l := len(data)
|
||||
m := time.Now().Month()
|
||||
if l < 1 || a.month != m {
|
||||
r, err := a.fn(ctx)
|
||||
if err != nil {
|
||||
logs.Error(err, "set cache Archives fail")
|
||||
return nil
|
||||
}
|
||||
a.month = m
|
||||
a.data = r
|
||||
arch.Store(a)
|
||||
data = r
|
||||
}
|
||||
return data
|
||||
}
|
39
app/pkg/cache/categoryandtag.go
vendored
Normal file
39
app/pkg/cache/categoryandtag.go
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CategoriesTags get all categories or tags
|
||||
//
|
||||
// query func see dao.CategoriesAndTags
|
||||
//
|
||||
// t is constraints.Tag or constraints.Category
|
||||
func CategoriesTags(ctx context.Context, t ...string) []models.TermsMy {
|
||||
tt := ""
|
||||
if len(t) > 0 {
|
||||
tt = t[0]
|
||||
}
|
||||
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
|
||||
logs.IfError(err, "get category fail")
|
||||
return r
|
||||
}
|
||||
func AllCategoryTagsNames(ctx context.Context, t ...string) map[string]struct{} {
|
||||
tt := ""
|
||||
if len(t) > 0 {
|
||||
tt = t[0]
|
||||
}
|
||||
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
|
||||
if err != nil {
|
||||
logs.Error(err, "get category fail")
|
||||
return nil
|
||||
}
|
||||
return slice.ToMap(r, func(t models.TermsMy) (string, struct{}) {
|
||||
return t.Name, struct{}{}
|
||||
}, true)
|
||||
}
|
124
app/pkg/cache/comments.go
vendored
Normal file
124
app/pkg/cache/comments.go
vendored
Normal file
@ -0,0 +1,124 @@
|
||||
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)
|
||||
}
|
127
internal/pkg/cache/feed.go → app/pkg/cache/feed.go
vendored
127
internal/pkg/cache/feed.go → app/pkg/cache/feed.go
vendored
@ -1,17 +1,20 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"github.com/fthvgb1/wp-go/internal/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/internal/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/internal/plugins"
|
||||
"github.com/fthvgb1/wp-go/internal/wpconfig"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/plugin/digest"
|
||||
"github.com/fthvgb1/wp-go/rss2"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -21,31 +24,36 @@ var templateRss rss2.Rss2
|
||||
|
||||
func InitFeed() {
|
||||
templateRss = rss2.Rss2{
|
||||
Title: wpconfig.Options.Value("blogname"),
|
||||
AtomLink: fmt.Sprintf("%s/feed", wpconfig.Options.Value("home")),
|
||||
Link: wpconfig.Options.Value("siteurl"),
|
||||
Description: wpconfig.Options.Value("blogdescription"),
|
||||
Language: "zh-CN",
|
||||
Title: wpconfig.GetOption("blogname"),
|
||||
AtomLink: fmt.Sprintf("%s/feed", wpconfig.GetOption("home")),
|
||||
Link: wpconfig.GetOption("siteurl"),
|
||||
Description: wpconfig.GetOption("blogdescription"),
|
||||
Language: wpconfig.GetLang(),
|
||||
UpdatePeriod: "hourly",
|
||||
UpdateFrequency: 1,
|
||||
Generator: wpconfig.Options.Value("home"),
|
||||
Generator: wpconfig.GetOption("home"),
|
||||
}
|
||||
}
|
||||
|
||||
// CommentsFeedCache query func see CommentsFeed
|
||||
func CommentsFeedCache() *cache.VarCache[[]string] {
|
||||
return commentsFeedCache
|
||||
r, _ := cachemanager.GetVarCache[[]string]("commentsFeed")
|
||||
return r
|
||||
}
|
||||
|
||||
// FeedCache query func see SiteFeed
|
||||
func FeedCache() *cache.VarCache[[]string] {
|
||||
return feedCache
|
||||
r, _ := cachemanager.GetVarCache[[]string]("siteFeed")
|
||||
return r
|
||||
}
|
||||
|
||||
// PostFeedCache query func see PostFeed
|
||||
func PostFeedCache() *cache.MapCache[string, string] {
|
||||
return postFeedCache
|
||||
r, _ := cachemanager.GetMapCache[string, string]("postFeed")
|
||||
return r
|
||||
}
|
||||
|
||||
func feed(arg ...any) (xml []string, err error) {
|
||||
c := arg[0].(*gin.Context)
|
||||
func SiteFeed(c context.Context, _ ...any) (xml []string, err error) {
|
||||
r := RecentPosts(c, 10)
|
||||
ids := slice.Map(r, func(t models.Posts) uint64 {
|
||||
return t.Id
|
||||
@ -54,21 +62,22 @@ func feed(arg ...any) (xml []string, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
site := wpconfig.GetOption("siteurl")
|
||||
rs := templateRss
|
||||
rs.LastBuildDate = time.Now().Format(timeFormat)
|
||||
rs.Items = slice.Map(posts, func(t models.Posts) rss2.Item {
|
||||
desc := "无法提供摘要。这是一篇受保护的文章。"
|
||||
plugins.PasswordProjectTitle(&t)
|
||||
if t.PostPassword != "" {
|
||||
plugins.PasswdProjectContent(&t)
|
||||
wpposts.PasswordProjectTitle(&t)
|
||||
wpposts.PasswdProjectContent(&t)
|
||||
} else {
|
||||
desc = digest.Raw(t.PostContent, 55, fmt.Sprintf("/p/%d", t.Id))
|
||||
desc = plugins.Digests(t.PostContent, t.Id, 55, nil)
|
||||
}
|
||||
l := ""
|
||||
if t.CommentStatus == "open" && t.CommentCount > 0 {
|
||||
l = fmt.Sprintf("%s/p/%d#comments", wpconfig.Options.Value("siteurl"), t.Id)
|
||||
l = fmt.Sprintf("%s/p/%d#comments", site, t.Id)
|
||||
} else if t.CommentStatus == "open" && t.CommentCount == 0 {
|
||||
l = fmt.Sprintf("%s/p/%d#respond", wpconfig.Options.Value("siteurl"), t.Id)
|
||||
l = fmt.Sprintf("%s/p/%d#respond", site, t.Id)
|
||||
}
|
||||
user := GetUserById(c, t.PostAuthor)
|
||||
|
||||
@ -80,8 +89,8 @@ func feed(arg ...any) (xml []string, err error) {
|
||||
Content: t.PostContent,
|
||||
Category: strings.Join(t.Categories, "、"),
|
||||
CommentLink: l,
|
||||
CommentRss: fmt.Sprintf("%s/p/%d/feed", wpconfig.Options.Value("siteurl"), t.Id),
|
||||
Link: fmt.Sprintf("%s/p/%d", wpconfig.Options.Value("siteurl"), t.Id),
|
||||
CommentRss: fmt.Sprintf("%s/p/%d/feed", site, t.Id),
|
||||
Link: fmt.Sprintf("%s/p/%d", site, t.Id),
|
||||
Description: desc,
|
||||
PubDate: t.PostDateGmt.Format(timeFormat),
|
||||
}
|
||||
@ -90,45 +99,46 @@ func feed(arg ...any) (xml []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func postFeed(arg ...any) (x string, err error) {
|
||||
c := arg[0].(*gin.Context)
|
||||
id := arg[1].(string)
|
||||
Id := 0
|
||||
if id != "" {
|
||||
Id, err = strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
ID := uint64(Id)
|
||||
func PostFeed(c context.Context, id string, _ ...any) (x string, err error) {
|
||||
ID := str.ToInteger[uint64](id, 0)
|
||||
maxId, err := GetMaxPostId(c)
|
||||
logs.ErrPrintln(err, "get max post id")
|
||||
if ID > maxId || err != nil {
|
||||
logs.IfError(err, "get max post id")
|
||||
if ID < 1 || ID > maxId || err != nil {
|
||||
return
|
||||
}
|
||||
post, err := GetPostById(c, ID)
|
||||
if post.Id == 0 || err != nil {
|
||||
return
|
||||
}
|
||||
plugins.PasswordProjectTitle(&post)
|
||||
comments, err := PostComments(c, post.Id)
|
||||
limit := str.ToInteger(wpconfig.GetOption("comments_per_page"), 10)
|
||||
ids, err := PostTopLevelCommentIds(c, ID, 1, limit, 0, "desc", "latest-comment")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
comments, err := GetCommentDataByIds(c, ids)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rs := templateRss
|
||||
site := wpconfig.GetOption("siteurl")
|
||||
|
||||
rs.Title = fmt.Sprintf("《%s》的评论", post.PostTitle)
|
||||
rs.AtomLink = fmt.Sprintf("%s/p/%d/feed", wpconfig.Options.Value("siteurl"), post.Id)
|
||||
rs.Link = fmt.Sprintf("%s/p/%d", wpconfig.Options.Value("siteurl"), post.Id)
|
||||
rs.AtomLink = fmt.Sprintf("%s/p/%d/feed", site, post.Id)
|
||||
rs.Link = fmt.Sprintf("%s/p/%d", site, post.Id)
|
||||
rs.LastBuildDate = time.Now().Format(timeFormat)
|
||||
if post.PostPassword != "" {
|
||||
wpposts.PasswordProjectTitle(&post)
|
||||
wpposts.PasswdProjectContent(&post)
|
||||
if len(comments) > 0 {
|
||||
plugins.PasswdProjectContent(&post)
|
||||
t := comments[len(comments)-1]
|
||||
u, err := GetCommentUrl(c, t.CommentId, t.CommentPostId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rs.Items = []rss2.Item{
|
||||
{
|
||||
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
|
||||
Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId),
|
||||
Link: fmt.Sprintf("%s%s", site, u),
|
||||
Creator: t.CommentAuthor,
|
||||
PubDate: t.CommentDateGmt.Format(timeFormat),
|
||||
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
|
||||
@ -139,9 +149,14 @@ func postFeed(arg ...any) (x string, err error) {
|
||||
}
|
||||
} else {
|
||||
rs.Items = slice.Map(comments, func(t models.Comments) rss2.Item {
|
||||
u, er := GetCommentUrl(c, t.CommentId, t.CommentPostId)
|
||||
if er != nil {
|
||||
err = errors.Join(err, er)
|
||||
return rss2.Item{}
|
||||
}
|
||||
return rss2.Item{
|
||||
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
|
||||
Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId),
|
||||
Link: fmt.Sprintf("%s%s", site, u),
|
||||
Creator: t.CommentAuthor,
|
||||
PubDate: t.CommentDateGmt.Format(timeFormat),
|
||||
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
|
||||
@ -154,14 +169,14 @@ func postFeed(arg ...any) (x string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func commentsFeed(args ...any) (r []string, err error) {
|
||||
c := args[0].(*gin.Context)
|
||||
func CommentsFeed(c context.Context, _ ...any) (r []string, err error) {
|
||||
commens := RecentComments(c, 10)
|
||||
rs := templateRss
|
||||
rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.Options.Value("blogname"))
|
||||
rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.GetOption("blogname"))
|
||||
rs.LastBuildDate = time.Now().Format(timeFormat)
|
||||
rs.AtomLink = fmt.Sprintf("%s/comments/feed", wpconfig.Options.Value("siteurl"))
|
||||
com, err := GetCommentByIds(c, slice.Map(commens, func(t models.Comments) uint64 {
|
||||
site := wpconfig.GetOption("siteurl")
|
||||
rs.AtomLink = fmt.Sprintf("%s/comments/feed", site)
|
||||
com, err := GetCommentDataByIds(c, slice.Map(commens, func(t models.Comments) uint64 {
|
||||
return t.CommentId
|
||||
}))
|
||||
if nil != err {
|
||||
@ -169,19 +184,23 @@ func commentsFeed(args ...any) (r []string, err error) {
|
||||
}
|
||||
rs.Items = slice.Map(com, func(t models.Comments) rss2.Item {
|
||||
post, _ := GetPostById(c, t.CommentPostId)
|
||||
plugins.PasswordProjectTitle(&post)
|
||||
desc := "评论受保护:要查看请输入密码。"
|
||||
content := t.CommentContent
|
||||
if post.PostPassword != "" {
|
||||
plugins.PasswdProjectContent(&post)
|
||||
wpposts.PasswordProjectTitle(&post)
|
||||
wpposts.PasswdProjectContent(&post)
|
||||
content = post.PostContent
|
||||
} else {
|
||||
desc = digest.ClearHtml(t.CommentContent)
|
||||
content = desc
|
||||
content = digest.StripTags(t.CommentContent, "")
|
||||
}
|
||||
u, er := GetCommentUrl(c, t.CommentId, t.CommentPostId)
|
||||
if er != nil {
|
||||
errors.Join(err, er)
|
||||
}
|
||||
u = str.Join(site, u)
|
||||
return rss2.Item{
|
||||
Title: fmt.Sprintf("%s对《%s》的评论", t.CommentAuthor, post.PostTitle),
|
||||
Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId),
|
||||
Link: u,
|
||||
Creator: t.CommentAuthor,
|
||||
Description: desc,
|
||||
PubDate: t.CommentDateGmt.Format(timeFormat),
|
17
app/pkg/cache/postmeta.go
vendored
Normal file
17
app/pkg/cache/postmeta.go
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetPostMetaByPostIds query func see dao.GetPostMetaByPostIds
|
||||
func GetPostMetaByPostIds(ctx context.Context, ids []uint64) ([]map[string]any, error) {
|
||||
return cachemanager.GetBatchBy[map[string]any]("postMetaData", ctx, ids, time.Second)
|
||||
}
|
||||
|
||||
// GetPostMetaByPostId query func see dao.GetPostMetaByPostIds
|
||||
func GetPostMetaByPostId(ctx context.Context, id uint64) (map[string]any, error) {
|
||||
return cachemanager.GetBy[map[string]any]("postMetaData", ctx, id, time.Second)
|
||||
}
|
91
app/pkg/cache/posts.go
vendored
Normal file
91
app/pkg/cache/posts.go
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/dao"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetPostById query func see dao.GetPostsByIds
|
||||
func GetPostById(ctx context.Context, id uint64) (models.Posts, error) {
|
||||
return cachemanager.GetBy[models.Posts]("postData", ctx, id, time.Second)
|
||||
}
|
||||
|
||||
// GetPostsByIds query func see dao.GetPostsByIds
|
||||
func GetPostsByIds(ctx context.Context, ids []uint64) ([]models.Posts, error) {
|
||||
return cachemanager.GetBatchBy[models.Posts]("postData", ctx, ids, time.Second)
|
||||
}
|
||||
|
||||
// SearchPost query func see dao.SearchPostIds
|
||||
func SearchPost(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
|
||||
ids, err := cachemanager.GetBy[dao.PostIds]("searchPostIds", ctx, key, time.Second, args...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
total = ids.Length
|
||||
r, err = GetPostsByIds(ctx, ids.Ids)
|
||||
return
|
||||
}
|
||||
|
||||
// PostLists query func see dao.SearchPostIds
|
||||
func PostLists(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
|
||||
ids, err := cachemanager.GetBy[dao.PostIds]("listPostIds", ctx, key, time.Second, args...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
total = ids.Length
|
||||
r, err = GetPostsByIds(ctx, ids.Ids)
|
||||
return
|
||||
}
|
||||
|
||||
// GetMaxPostId query func see dao.GetMaxPostId
|
||||
func GetMaxPostId(ctx context.Context) (uint64, error) {
|
||||
return cachemanager.GetVarVal[uint64]("maxPostId", ctx, time.Second)
|
||||
}
|
||||
|
||||
// RecentPosts query func see dao.RecentPosts
|
||||
func RecentPosts(ctx context.Context, n int) (r []models.Posts) {
|
||||
nn := n
|
||||
feedNum := str.ToInteger(wpconfig.GetOption("posts_per_rss"), 10)
|
||||
nn = number.Max(n, feedNum)
|
||||
r, err := cachemanager.GetVarVal[[]models.Posts]("recentPosts", ctx, time.Second, nn)
|
||||
if n < len(r) {
|
||||
r = r[:n]
|
||||
}
|
||||
logs.IfError(err, "get recent post")
|
||||
return
|
||||
}
|
||||
|
||||
// GetContextPost query func see dao.GetPostContext
|
||||
func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next models.Posts, err error) {
|
||||
postCtx, err := cachemanager.GetBy[dao.PostContext]("postContext", ctx, id, time.Second, date)
|
||||
if err != nil {
|
||||
return models.Posts{}, models.Posts{}, err
|
||||
}
|
||||
prev = postCtx.Prev
|
||||
next = postCtx.Next
|
||||
return
|
||||
}
|
||||
|
||||
// GetMonthPostIds query func see dao.MonthPost
|
||||
func GetMonthPostIds(ctx context.Context, year, month string, page, limit int, order string) (r []models.Posts, total int, err error) {
|
||||
res, err := cachemanager.GetBy[[]uint64]("monthPostIds", ctx, fmt.Sprintf("%s%s", year, month), time.Second, year, month)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if order == "desc" {
|
||||
res = slice.Reverse(res)
|
||||
}
|
||||
total = len(res)
|
||||
rr := slice.Pagination(res, page, limit)
|
||||
r, err = GetPostsByIds(ctx, rr)
|
||||
return
|
||||
}
|
26
app/pkg/cache/users.go
vendored
Normal file
26
app/pkg/cache/users.go
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
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
|
||||
}
|
147
app/pkg/config/config.go
Normal file
147
app/pkg/config/config.go
Normal file
@ -0,0 +1,147 @@
|
||||
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"`
|
||||
}
|
43
app/pkg/config/validationtraslaor.go
Normal file
43
app/pkg/config/validationtraslaor.go
Normal file
@ -0,0 +1,43 @@
|
||||
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
|
||||
}
|
10
app/pkg/constraints/blocks/blocks.go
Normal file
10
app/pkg/constraints/blocks/blocks.go
Normal file
@ -0,0 +1,10 @@
|
||||
package blocks
|
||||
|
||||
const (
|
||||
Search = "block-search"
|
||||
RecentPosts = "block-recent-posts"
|
||||
RecentComments = "block-recent-comments"
|
||||
Archive = "block-archives"
|
||||
Categories = "block-categories"
|
||||
Meta = "block-meta"
|
||||
)
|
30
app/pkg/constraints/const.go
Normal file
30
app/pkg/constraints/const.go
Normal file
@ -0,0 +1,30 @@
|
||||
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"
|
||||
)
|
12
app/pkg/constraints/widgets/constraints.go
Normal file
12
app/pkg/constraints/widgets/constraints.go
Normal file
@ -0,0 +1,12 @@
|
||||
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"
|
||||
)
|
183
app/pkg/dao/comments.go
Normal file
183
app/pkg/dao/comments.go
Normal file
@ -0,0 +1,183 @@
|
||||
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
|
||||
}
|
76
app/pkg/dao/common.go
Normal file
76
app/pkg/dao/common.go
Normal file
@ -0,0 +1,76 @@
|
||||
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"}}),
|
||||
))
|
||||
}
|
@ -2,21 +2,21 @@ package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"github.com/fthvgb1/wp-go/internal/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/internal/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/internal/plugins"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func GetPostMetaByPostIds(args ...any) (r map[uint64]map[string]any, err error) {
|
||||
func GetPostMetaByPostIds(ctx context.Context, ids []uint64, _ ...any) (r map[uint64]map[string]any, err error) {
|
||||
r = make(map[uint64]map[string]any)
|
||||
ctx := args[0].(context.Context)
|
||||
ids := args[1].([]uint64)
|
||||
rr, err := model.Find[models.PostMeta](ctx, model.SqlBuilder{
|
||||
{"post_id", "in", ""},
|
||||
}, "*", "", nil, nil, nil, 0, slice.ToAnySlice(ids))
|
||||
rr, err := model.Finds[models.PostMeta](ctx, model.Conditions(
|
||||
model.Where(model.SqlBuilder{{"post_id", "in", ""}}),
|
||||
model.In(slice.ToAnySlice(ids)),
|
||||
))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -24,18 +24,15 @@ func GetPostMetaByPostIds(args ...any) (r map[uint64]map[string]any, err error)
|
||||
if _, ok := r[postmeta.PostId]; !ok {
|
||||
r[postmeta.PostId] = make(map[string]any)
|
||||
}
|
||||
r[postmeta.PostId][postmeta.MetaKey] = postmeta.MetaValue
|
||||
if postmeta.MetaKey == "_wp_attachment_metadata" {
|
||||
metadata, err := plugins.UnPHPSerialize[models.WpAttachmentMetadata](postmeta.MetaValue)
|
||||
metadata, err := phphelper.UnPHPSerializeToStruct[models.WpAttachmentMetadata](postmeta.MetaValue)
|
||||
if err != nil {
|
||||
logs.ErrPrintln(err, "解析postmeta失败", postmeta.MetaId, postmeta.MetaValue)
|
||||
logs.Error(err, "解析postmeta失败", postmeta.MetaId, postmeta.MetaValue)
|
||||
continue
|
||||
}
|
||||
r[postmeta.PostId][postmeta.MetaKey] = metadata
|
||||
|
||||
} else {
|
||||
r[postmeta.PostId][postmeta.MetaKey] = postmeta.MetaValue
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -62,7 +59,7 @@ func ToPostThumb(c context.Context, meta map[string]any, host string) (r models.
|
||||
if ok {
|
||||
metadata, ok := x.(models.WpAttachmentMetadata)
|
||||
if ok {
|
||||
r = plugins.Thumbnail(metadata, "post-thumbnail", host, "thumbnail")
|
||||
r = wpconfig.Thumbnail(metadata, "post-thumbnail", host, "thumbnail")
|
||||
}
|
||||
}
|
||||
}
|
204
app/pkg/dao/posts.go
Normal file
204
app/pkg/dao/posts.go
Normal file
@ -0,0 +1,204 @@
|
||||
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
|
||||
}
|
32
app/pkg/dao/users.go
Normal file
32
app/pkg/dao/users.go
Normal file
@ -0,0 +1,32 @@
|
||||
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
|
||||
}
|
77
app/pkg/db/db.go
Normal file
77
app/pkg/db/db.go
Normal file
@ -0,0 +1,77 @@
|
||||
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
|
||||
}
|
76
app/pkg/logs/log.go
Normal file
76
app/pkg/logs/log.go
Normal file
@ -0,0 +1,76 @@
|
||||
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...)
|
||||
}
|
19
app/pkg/models/relation/posts.go
Normal file
19
app/pkg/models/relation/posts.go
Normal file
@ -0,0 +1,19 @@
|
||||
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",
|
||||
})
|
@ -19,7 +19,8 @@ type Comments struct {
|
||||
CommentParent uint64 `gorm:"column:comment_parent" db:"comment_parent" json:"comment_parent" form:"comment_parent"`
|
||||
UserId uint64 `gorm:"column:user_id" db:"user_id" json:"user_id" form:"user_id"`
|
||||
//扩展字段
|
||||
PostTitle string `db:"post_title"`
|
||||
PostTitle string `db:"post_title"`
|
||||
UpdateTime time.Time `gorm:"update_time" form:"update_time" json:"update_time" db:"update_time"`
|
||||
}
|
||||
|
||||
func (w Comments) PrimaryKey() string {
|
||||
@ -29,3 +30,8 @@ func (w Comments) PrimaryKey() string {
|
||||
func (w Comments) Table() string {
|
||||
return "wp_comments"
|
||||
}
|
||||
|
||||
type PostComments struct {
|
||||
Comments
|
||||
Children []uint64
|
||||
}
|
@ -22,6 +22,7 @@ type WpAttachmentMetadata struct {
|
||||
FileSize int `json:"filesize,omitempty"`
|
||||
Sizes map[string]MetaDataFileSize `json:"sizes,omitempty"`
|
||||
ImageMeta ImageMeta `json:"image_meta"`
|
||||
VideoMeta
|
||||
}
|
||||
|
||||
type ImageMeta struct {
|
||||
@ -39,6 +40,16 @@ type ImageMeta struct {
|
||||
Keywords []string `json:"keywords,omitempty"`
|
||||
}
|
||||
|
||||
type VideoMeta struct {
|
||||
Bitrate int `json:"bitrate,omitempty"`
|
||||
MimeType string `json:"mime_type,omitempty"`
|
||||
Length int `json:"length,omitempty"`
|
||||
LengthFormatted string `json:"length_formatted,omitempty"`
|
||||
FileFormat string `json:"fileformat,omitempty"`
|
||||
DataFormat string `json:"dataformat,omitempty"`
|
||||
CreatedTimestamp int64 `json:"created_timestamp"`
|
||||
}
|
||||
|
||||
type MetaDataFileSize struct {
|
||||
File string `json:"file,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
@ -3,6 +3,7 @@ package models
|
||||
import "time"
|
||||
|
||||
type Posts struct {
|
||||
post
|
||||
Id uint64 `gorm:"column:ID" db:"ID" json:"ID" form:"ID"`
|
||||
PostAuthor uint64 `gorm:"column:post_author" db:"post_author" json:"post_author" form:"post_author"`
|
||||
PostDate time.Time `gorm:"column:post_date" db:"post_date" json:"post_date" form:"post_date"`
|
||||
@ -28,14 +29,19 @@ type Posts struct {
|
||||
CommentCount int64 `gorm:"column:comment_count" db:"comment_count" json:"comment_count" form:"comment_count"`
|
||||
|
||||
//扩展字段
|
||||
TermsId uint64 `db:"terms_id" json:"terms_id"`
|
||||
TermIds []uint64 `db:"term_ids" json:"term_ids"`
|
||||
Taxonomy string `db:"taxonomy" json:"taxonomy"`
|
||||
CategoryName string `db:"category_name" json:"category_name"`
|
||||
Categories []string `json:"categories"`
|
||||
Tags []string `json:"tags"`
|
||||
CategoriesHtml string
|
||||
TagsHtml string
|
||||
IsSticky bool
|
||||
Thumbnail PostThumbnail
|
||||
AttachmentMetadata WpAttachmentMetadata
|
||||
Metas map[string]any
|
||||
Author *Users
|
||||
}
|
||||
|
||||
type PostThumbnail struct {
|
||||
@ -47,23 +53,19 @@ type PostThumbnail struct {
|
||||
OriginAttachmentData WpAttachmentMetadata
|
||||
}
|
||||
|
||||
func (w Posts) PrimaryKey() string {
|
||||
type post struct {
|
||||
}
|
||||
|
||||
func (w post) PrimaryKey() string {
|
||||
return "ID"
|
||||
}
|
||||
|
||||
func (w Posts) Table() string {
|
||||
return "wp_posts"
|
||||
}
|
||||
|
||||
func (w PostArchive) PrimaryKey() string {
|
||||
return "ID"
|
||||
}
|
||||
|
||||
func (w PostArchive) Table() string {
|
||||
func (w post) Table() string {
|
||||
return "wp_posts"
|
||||
}
|
||||
|
||||
type PostArchive struct {
|
||||
post
|
||||
Year string `db:"year"`
|
||||
Month string `db:"month"`
|
||||
Posts int `db:"posts"`
|
@ -1,21 +1,25 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"github.com/fthvgb1/wp-go/internal/pkg/models"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CommentHandler struct {
|
||||
*gin.Context
|
||||
comments []*Comments
|
||||
maxDepth int
|
||||
depth int
|
||||
isTls bool
|
||||
i CommentHtml
|
||||
comments []*Comments
|
||||
maxDepth int
|
||||
depth int
|
||||
isTls bool
|
||||
i CommentHtml
|
||||
isThreadComments bool
|
||||
}
|
||||
|
||||
type Comments struct {
|
||||
@ -24,19 +28,18 @@ type Comments struct {
|
||||
}
|
||||
|
||||
type CommentHtml interface {
|
||||
Sort(i, j *Comments) bool
|
||||
FormatLi(c *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string
|
||||
FormatLi(c context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string
|
||||
FloorOrder(i, j models.Comments) bool
|
||||
}
|
||||
|
||||
func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, maxDepth int) string {
|
||||
tree := treeComments(comments)
|
||||
u := c.Request.Header.Get("Referer")
|
||||
|
||||
var isTls bool
|
||||
if u != "" {
|
||||
uu, _ := url.Parse(u)
|
||||
if uu.Scheme == "https" {
|
||||
isTls = true
|
||||
}
|
||||
if c.Request.TLS != nil {
|
||||
isTls = true
|
||||
} else {
|
||||
isTls = "https" == strings.ToLower(c.Request.Header.Get("X-Forwarded-Proto"))
|
||||
}
|
||||
h := CommentHandler{
|
||||
Context: c,
|
||||
@ -46,15 +49,21 @@ func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, m
|
||||
isTls: isTls,
|
||||
i: i,
|
||||
}
|
||||
return h.formatComment(h.comments, true)
|
||||
return h.formatComment(h.comments)
|
||||
}
|
||||
|
||||
func (d CommentHandler) formatComment(comments []*Comments, isTop bool) (html string) {
|
||||
s := strings.Builder{}
|
||||
if d.depth > d.maxDepth {
|
||||
func (d CommentHandler) formatComment(comments []*Comments) (html string) {
|
||||
s := str.NewBuilder()
|
||||
if d.depth >= d.maxDepth {
|
||||
comments = d.findComments(comments)
|
||||
}
|
||||
slice.SortSelf(comments, d.i.Sort)
|
||||
order := wpconfig.GetOption("comment_order")
|
||||
slice.Sort(comments, func(i, j *Comments) bool {
|
||||
if order == "asc" {
|
||||
return i.CommentDate.Sub(j.CommentDate) < 0
|
||||
}
|
||||
return i.CommentDate.Sub(j.CommentDate) > 0
|
||||
})
|
||||
for i, comment := range comments {
|
||||
eo := "even"
|
||||
if (i+1)%2 == 0 {
|
||||
@ -66,15 +75,11 @@ func (d CommentHandler) formatComment(comments []*Comments, isTop bool) (html st
|
||||
parent = "parent"
|
||||
fl = true
|
||||
}
|
||||
s.WriteString(d.i.FormatLi(d.Context, comment.Comments, d.depth, d.isTls, eo, parent))
|
||||
s.WriteString(d.i.FormatLi(d.Context, comment.Comments, d.depth, d.maxDepth, 1, d.isTls, d.isThreadComments, eo, parent))
|
||||
if fl {
|
||||
d.depth++
|
||||
s.WriteString(`<ol class="children">`)
|
||||
s.WriteString(d.formatComment(comment.Children, false))
|
||||
s.WriteString(`</ol>`)
|
||||
if isTop {
|
||||
d.depth = 1
|
||||
}
|
||||
s.WriteString(`<ol class="children">`, d.formatComment(comment.Children), `</ol>`)
|
||||
d.depth--
|
||||
}
|
||||
s.WriteString("</li><!-- #comment-## -->")
|
||||
}
|
||||
@ -87,7 +92,7 @@ func (d CommentHandler) findComments(comments []*Comments) []*Comments {
|
||||
var r []*Comments
|
||||
for _, comment := range comments {
|
||||
tmp := *comment
|
||||
comment.Children = nil
|
||||
tmp.Children = nil
|
||||
r = append(r, &tmp)
|
||||
if len(comment.Children) > 0 {
|
||||
t := d.findComments(comment.Children)
|
||||
@ -137,12 +142,35 @@ func CommentRender() CommonCommentFormat {
|
||||
type CommonCommentFormat struct {
|
||||
}
|
||||
|
||||
func (c CommonCommentFormat) Sort(i, j *Comments) bool {
|
||||
return i.CommentDate.UnixNano() < j.CommentDate.UnixNano()
|
||||
func (c CommonCommentFormat) FormatLi(_ context.Context, m models.Comments, currentDepth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
|
||||
return FormatLi(li, m, respondsFn, currentDepth, maxDepth, page, isTls, isThreadComments, eo, parent)
|
||||
}
|
||||
|
||||
func (c CommonCommentFormat) FormatLi(ctx *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string {
|
||||
return FormatLi(CommonLi(), ctx, m, depth, isTls, eo, parent)
|
||||
func (c CommonCommentFormat) FloorOrder(i, j models.Comments) bool {
|
||||
return i.CommentId > j.CommentId
|
||||
}
|
||||
|
||||
type RespondFn func(m models.Comments, depth, maxDepth int, isThreadComments bool) string
|
||||
|
||||
var respondsFn = Responds(respondTml)
|
||||
|
||||
func RespondsFn() RespondFn {
|
||||
return respondsFn
|
||||
}
|
||||
|
||||
func Responds(respondTml string) RespondFn {
|
||||
return func(m models.Comments, depth, maxDepth int, isThreadComments bool) string {
|
||||
if !isThreadComments || depth >= maxDepth {
|
||||
return ""
|
||||
}
|
||||
pId := number.IntToString(m.CommentPostId)
|
||||
cId := number.IntToString(m.CommentId)
|
||||
return str.Replace(respondTml, map[string]string{
|
||||
"{{PostId}}": pId,
|
||||
"{{CommentId}}": cId,
|
||||
"{{CommentAuthor}}": m.CommentAuthor,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var li = `
|
||||
@ -154,14 +182,11 @@ var li = `
|
||||
src="{{Gravatar}}"
|
||||
srcset="{{Gravatar}} 2x"
|
||||
class="avatar avatar-56 photo" height="56" width="56" loading="lazy">
|
||||
<b class="fn">
|
||||
<a href="{{CommentAuthorUrl}}" rel="external nofollow ugc"
|
||||
class="url">{{CommentAuthor}}</a>
|
||||
</b>
|
||||
<b class="fn">{{CommentAuthor}}</b>
|
||||
<span class="says">说道:</span></div><!-- .comment-author -->
|
||||
|
||||
<div class="comment-metadata">
|
||||
<a href="/p/{{PostId}}#comment-{{CommentId}}">
|
||||
<a href="/p/{{PostId}}/comment-page-{{page}}#comment-{{CommentId}}">
|
||||
<time datetime="{{CommentDateGmt}}">{{CommentDate}}</time>
|
||||
</a></div><!-- .comment-metadata -->
|
||||
|
||||
@ -171,30 +196,34 @@ var li = `
|
||||
<p>{{CommentContent}}</p>
|
||||
</div><!-- .comment-content -->
|
||||
|
||||
<div class="reply">
|
||||
{{respond}}
|
||||
</article><!-- .comment-body -->
|
||||
|
||||
`
|
||||
|
||||
var respondTml = `<div class="reply">
|
||||
<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}}">回复</a>
|
||||
</div>
|
||||
</article><!-- .comment-body -->
|
||||
</div>`
|
||||
|
||||
`
|
||||
|
||||
func FormatLi(li string, c *gin.Context, comments models.Comments, depth int, isTls bool, eo, parent string) string {
|
||||
func FormatLi(li string, comments models.Comments, respond RespondFn, currentDepth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
|
||||
for k, v := range map[string]string{
|
||||
"{{CommentId}}": strconv.FormatUint(comments.CommentId, 10),
|
||||
"{{Depth}}": strconv.Itoa(depth),
|
||||
"{{Depth}}": strconv.Itoa(currentDepth),
|
||||
"{{Gravatar}}": Gravatar(comments.CommentAuthorEmail, isTls),
|
||||
"{{CommentAuthorUrl}}": comments.CommentAuthorUrl,
|
||||
"{{CommentAuthor}}": comments.CommentAuthor,
|
||||
"{{PostId}}": strconv.FormatUint(comments.CommentPostId, 10),
|
||||
"{{page}}": strconv.Itoa(page),
|
||||
"{{CommentDateGmt}}": comments.CommentDateGmt.String(),
|
||||
"{{CommentDate}}": comments.CommentDate.Format("2006-01-02 15:04"),
|
||||
"{{CommentContent}}": comments.CommentContent,
|
||||
"{{eo}}": eo,
|
||||
"{{parent}}": parent,
|
||||
"{{respond}}": respond(comments, currentDepth, maxDepth, isThreadComments),
|
||||
} {
|
||||
li = strings.Replace(li, k, v, -1)
|
||||
}
|
9
app/plugins/devexample/plugintt/a.gohtml
Normal file
9
app/plugins/devexample/plugintt/a.gohtml
Normal file
@ -0,0 +1,9 @@
|
||||
<div>
|
||||
{{.aa}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ range $k,$v := .posts}}
|
||||
<h2>{{$v.PostTitle}} </h2>
|
||||
{{end}}
|
||||
</div>
|
81
app/plugins/devexample/plugintt/main.go.dev
Normal file
81
app/plugins/devexample/plugintt/main.go.dev
Normal file
@ -0,0 +1,81 @@
|
||||
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()
|
||||
})
|
||||
}
|
7
app/plugins/devexample/plugintt/make.sh
Normal file
7
app/plugins/devexample/plugintt/make.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#/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
|
141
app/plugins/devexample/plugintt/redisCache.go.dev
Normal file
141
app/plugins/devexample/plugintt/redisCache.go.dev
Normal file
@ -0,0 +1,141 @@
|
||||
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")
|
||||
}
|
50
app/plugins/devexample/plugintt/redisCache.go.mod.dev
Normal file
50
app/plugins/devexample/plugintt/redisCache.go.mod.dev
Normal file
@ -0,0 +1,50 @@
|
||||
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
|
||||
)
|
11
app/plugins/devexample/plugintt/xx/oo.go.dev
Normal file
11
app/plugins/devexample/plugintt/xx/oo.go.dev
Normal file
@ -0,0 +1,11 @@
|
||||
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())
|
||||
}
|
187
app/plugins/digest.go
Normal file
187
app/plugins/digest.go
Normal file
@ -0,0 +1,187 @@
|
||||
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)
|
||||
}
|
@ -2,9 +2,9 @@ package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/internal/wpconfig"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
@ -25,7 +25,7 @@ func Gravatar(email string, isTls bool) (u string) {
|
||||
q := url.Values{}
|
||||
q.Add("s", "112")
|
||||
q.Add("d", "mm")
|
||||
q.Add("r", strings.ToLower(wpconfig.Options.Value("avatar_rating")))
|
||||
q.Add("r", strings.ToLower(wpconfig.GetOption("avatar_rating")))
|
||||
u = fmt.Sprintf("%s?%s", u, q.Encode())
|
||||
return
|
||||
}
|
176
app/plugins/pagination.go
Normal file
176
app/plugins/pagination.go
Normal file
@ -0,0 +1,176 @@
|
||||
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)
|
||||
}
|
30
app/plugins/wphandle/apply/apply.go
Normal file
30
app/plugins/wphandle/apply/apply.go
Normal file
@ -0,0 +1,30 @@
|
||||
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
|
||||
}
|
83
app/plugins/wphandle/enlightjs/enlighterjs.go
Normal file
83
app/plugins/wphandle/enlightjs/enlighterjs.go
Normal file
@ -0,0 +1,83 @@
|
||||
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>`
|
72
app/plugins/wphandle/handle.go
Normal file
72
app/plugins/wphandle/handle.go
Normal file
@ -0,0 +1,72 @@
|
||||
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)
|
||||
})
|
||||
}
|
17
app/plugins/wphandle/hiddenlogin/hiddenlogin.go
Normal file
17
app/plugins/wphandle/hiddenlogin/hiddenlogin.go
Normal file
@ -0,0 +1,17 @@
|
||||
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>`: "",
|
||||
})
|
||||
})
|
||||
}
|
22
app/plugins/wphandle/tests/tt.go
Normal file
22
app/plugins/wphandle/tests/tt.go
Normal file
@ -0,0 +1,22 @@
|
||||
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")
|
||||
}))
|
||||
}
|
19
app/plugins/wpposts/posts.go
Normal file
19
app/plugins/wpposts/posts.go
Normal file
@ -0,0 +1,19 @@
|
||||
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)
|
||||
}
|
147
app/route/route.go
Normal file
147
app/route/route.go
Normal file
@ -0,0 +1,147 @@
|
||||
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
|
||||
}
|
82
app/theme/fs.go
Normal file
82
app/theme/fs.go
Normal file
@ -0,0 +1,82 @@
|
||||
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)
|
||||
}
|
35
app/theme/hook.go
Normal file
35
app/theme/hook.go
Normal file
@ -0,0 +1,35 @@
|
||||
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"))
|
||||
}
|
37
app/theme/templateFuncs.go
Normal file
37
app/theme/templateFuncs.go
Normal file
@ -0,0 +1,37 @@
|
||||
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))
|
||||
},
|
||||
}
|
||||
}
|
24
app/theme/theme.go
Normal file
24
app/theme/theme.go
Normal file
@ -0,0 +1,24 @@
|
||||
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
|
||||
}
|
614
app/theme/twentyfifteen/colorschemecss.go
Normal file
614
app/theme/twentyfifteen/colorschemecss.go
Normal file
@ -0,0 +1,614 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
`
|
65
app/theme/twentyfifteen/custombackground.go
Normal file
65
app/theme/twentyfifteen/custombackground.go
Normal file
@ -0,0 +1,65 @@
|
||||
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
|
||||
}
|
123
app/theme/twentyfifteen/customheader.go
Normal file
123
app/theme/twentyfifteen/customheader.go
Normal file
@ -0,0 +1,123 @@
|
||||
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
|
||||
}
|
||||
}
|
@ -1,12 +1,9 @@
|
||||
{{ define "layout/base"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{getLang}}" class="no-js">
|
||||
<head>
|
||||
{{template "layout/head" .}}
|
||||
{{block "head" .}}
|
||||
{{end}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "layout/head" .}}
|
||||
|
||||
<body class="{{.calBodyClass|exec}}">
|
||||
{{template "svg"}}
|
||||
<div id="page" class="hfeed site">
|
||||
<a class="skip-link screen-reader-text" href="#content">
|
||||
@ -16,6 +13,7 @@
|
||||
<div id="sidebar" class="sidebar" style="position: relative; ">
|
||||
<header id="masthead" class="site-header">
|
||||
<div class="site-branding">
|
||||
{{.customLogo|exec}}
|
||||
<h1 class="site-title">
|
||||
<a href="/" rel="home">{{ "blogname"| getOption }}</a>
|
||||
</h1>
|
||||
@ -33,11 +31,7 @@
|
||||
|
||||
{{end}}
|
||||
</div>
|
||||
<footer id="colophon" class="site-footer">
|
||||
<div class="site-info">
|
||||
<a href="https://cn.wordpress.org/" class="imprint">自豪地采用WordPress</a>
|
||||
</div>
|
||||
</footer>
|
||||
{{template "common/colophon" .}}
|
||||
|
||||
</div>
|
||||
{{template "layout/footer" .}}
|
@ -21,7 +21,7 @@
|
||||
这儿似乎什么都没有,试试搜索?
|
||||
{{end}}
|
||||
</p>
|
||||
<form role="search" method="get" class="search-form" action="https://www.xloyy.com/">
|
||||
<form role="search" method="get" class="search-form" action="/">
|
||||
<label>
|
||||
<span class="screen-reader-text">搜索:</span>
|
||||
<input type="search" class="search-field" placeholder="搜索…" value="{{.search}}" name="s">
|
@ -8,10 +8,8 @@
|
||||
var screenReaderText = {"expand":"<span class=\"screen-reader-text\">\u5c55\u5f00\u5b50\u83dc\u5355<\/span>","collapse":"<span class=\"screen-reader-text\">\u6298\u53e0\u5b50\u83dc\u5355<\/span>"};
|
||||
</script>
|
||||
<script src='/wp-content/themes/twentyfifteen/js/functions.js?ver=20220524' id='twentyfifteen-script-js'></script>
|
||||
<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={"selectors":{"block":"pre.EnlighterJSRAW","inline":"code.EnlighterJSRAW"},"options":{"indent":4,"ampersandCleanup":true,"linehover":true,"rawcodeDbclick":false,"textOverflow":"break","linenumbers":true,"theme":"enlighter","language":"generic","retainCssClasses":false,"collapse":false,"toolbarOuter":"","toolbarTop":"{BTN_RAW}{BTN_COPY}{BTN_WINDOW}{BTN_WEBSITE}","toolbarBottom":""}};(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>
|
||||
{{ block "footer" .}}
|
||||
{{end}}
|
||||
|
||||
{{template "common/footer" .}}
|
||||
{{ block "footer" .}}
|
||||
{{end}}
|
||||
{{end}}
|
File diff suppressed because one or more lines are too long
5
app/theme/twentyfifteen/layout/sidebar.gohtml
Normal file
5
app/theme/twentyfifteen/layout/sidebar.gohtml
Normal file
@ -0,0 +1,5 @@
|
||||
{{define "layout/sidebar" }}
|
||||
<div id="widget-area" class="widget-area" role="complementary">
|
||||
{{template "common/sidebarWidget" .}}
|
||||
</div>
|
||||
{{end}}
|
@ -1,11 +1,15 @@
|
||||
{{template "layout/base" .}}
|
||||
|
||||
{{define "content"}}
|
||||
{{ if .post.PostContent}}
|
||||
{{ if and (.post) (gt .post.Id 0)}}
|
||||
<div id="primary" class="content-area">
|
||||
<main id="main" class="site-main">
|
||||
<article id="post-{{.post.Id}}"
|
||||
class="post-{{.post.Id}} post type-post status-publish format-standard hentry category-uncategorized">
|
||||
<article class="{{ .post|postsFn .calPostClass}}">
|
||||
{{if .post.Thumbnail.Path }}
|
||||
<div class="post-thumbnail">
|
||||
<img width="{{.post.Thumbnail.Width}}" height="{{.post.Thumbnail.Height}}" src="{{.post.Thumbnail.Path}}" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="" decoding="async">
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<header class="entry-header">
|
||||
<h1 class="entry-title">{{.post.PostTitle}}</h1></header><!-- .entry-header -->
|
||||
@ -15,32 +19,31 @@
|
||||
</div><!-- .entry-content -->
|
||||
|
||||
<footer class="entry-footer">
|
||||
<span class="posted-on">
|
||||
<span class="screen-reader-text">发布于 </span>
|
||||
<a href="/p/{{.post.Id}}" rel="bookmark">
|
||||
<time class="entry-date published updated"
|
||||
datetime="{{.post.PostDateGmt}}">{{.post.PostDate|dateCh}}
|
||||
</time>
|
||||
</a>
|
||||
</span>
|
||||
<span class="byline">
|
||||
<span class="author vcard">
|
||||
<span class="screen-reader-text">作者 </span>
|
||||
<a class="url fn n" href="/p/author/{{.user.UserLogin}}">{{.user.UserLogin}}</a>
|
||||
</span>
|
||||
</span>
|
||||
<span class="posted-on">
|
||||
<span class="screen-reader-text">发布于 </span>
|
||||
<a href="/p/{{.post.Id}}" rel="bookmark">
|
||||
<time class="entry-date published updated"
|
||||
datetime="{{.post.PostDateGmt}}">{{.post.PostDate|dateCh}}
|
||||
</time>
|
||||
</a>
|
||||
</span>
|
||||
<span class="byline">
|
||||
<span class="author vcard">
|
||||
<span class="screen-reader-text">作者 </span>
|
||||
<a class="url fn n" href="/p/author/{{.user.UserLogin}}">{{.user.UserLogin}}</a>
|
||||
</span>
|
||||
</span>
|
||||
{{if .post.CategoriesHtml}}
|
||||
<span class="cat-links">
|
||||
<span class="screen-reader-text">分类 </span>
|
||||
{{.post.CategoriesHtml|unescaped}}
|
||||
</span>
|
||||
<span class="screen-reader-text">分类 </span>
|
||||
{{.post.CategoriesHtml|unescaped}}
|
||||
</span>
|
||||
{{end}}
|
||||
|
||||
{{if .post.TagsHtml}}
|
||||
<span class="tags-links">
|
||||
<span class="screen-reader-text">标签 </span>
|
||||
{{.post.TagsHtml|unescaped}}
|
||||
</span>
|
||||
<span class="screen-reader-text">标签 </span>{{.post.TagsHtml|unescaped}}
|
||||
</span>
|
||||
{{end}}
|
||||
</footer>
|
||||
<!-- .entry-footer -->
|
||||
@ -49,11 +52,27 @@
|
||||
|
||||
{{ if .showComment}}
|
||||
<div id="comments" class="comments-area">
|
||||
{{ if gt .post.CommentCount 0}}
|
||||
<h2 class="comments-title">《{{.post.PostTitle}}》上有{{.post.CommentCount}}条评论 </h2>
|
||||
<ol class="comment-list">
|
||||
{{.comments|unescaped}}
|
||||
</ol>
|
||||
{{ if ne .comments ""}}
|
||||
<h2 class="comments-title">《{{.post.PostTitle}}》上有{{.totalCommentNum}}条评论 </h2>
|
||||
{{if gt .totalCommentPage 1}}
|
||||
<nav class="navigation comment-navigation">
|
||||
<h2 class="screen-reader-text">评论导航</h2>
|
||||
<div class="nav-links">
|
||||
{{ .commentPageNav|unescaped}}
|
||||
</div><!-- .nav-links -->
|
||||
</nav>
|
||||
{{end}}
|
||||
<ol class="comment-list">
|
||||
{{.comments|unescaped}}
|
||||
</ol>
|
||||
{{if gt .totalCommentPage 1}}
|
||||
<nav class="navigation comment-navigation">
|
||||
<h2 class="screen-reader-text">评论导航</h2>
|
||||
<div class="nav-links">
|
||||
{{ .commentPageNav|unescaped}}
|
||||
</div><!-- .nav-links -->
|
||||
</nav>
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
{{if eq .post.CommentStatus "open"}}
|
||||
@ -129,7 +148,4 @@
|
||||
{{else}}
|
||||
{{template "layout/empty"}}
|
||||
{{end }}
|
||||
{{end}}
|
||||
{{ define "footer"}}
|
||||
<script src='/wp-includes/js/comment-reply.min.js?ver=6.0.2' id='comment-reply-js'></script>
|
||||
{{end}}
|
@ -7,17 +7,12 @@
|
||||
{{if .header}}
|
||||
<header class="page-header">
|
||||
<h1 class="page-title">
|
||||
{{if .search}}
|
||||
{{.header}}
|
||||
{{else}}
|
||||
{{.header |unescaped}}
|
||||
{{end}}
|
||||
{{.header |unescaped}}
|
||||
</h1>
|
||||
</header>
|
||||
{{end}}
|
||||
{{ range $k,$v:=.posts}}
|
||||
<article id="post-{{$v.Id}}"
|
||||
class="post-{{$v.Id}} post {{if $v.Thumbnail.Path}}has-post-thumbnail{{end}} type-post status-publish format-standard hentry category">
|
||||
<article class="{{ $v|postsFn $.calPostClass}}">
|
||||
{{if $v.Thumbnail.Path}}
|
||||
<a class="post-thumbnail" href="/p/{{$v.Id}}" aria-hidden="true">
|
||||
<img width="{{$v.Thumbnail.Width}}" height="{{$v.Thumbnail.Height}}" src="{{$v.Thumbnail.Path}}" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="{{$v.PostTitle}}" decoding="async">
|
||||
@ -36,32 +31,35 @@
|
||||
</div><!-- .entry-content -->
|
||||
|
||||
<footer class="entry-footer">
|
||||
<span class="posted-on">
|
||||
<span class="screen-reader-text">发布于 </span>
|
||||
<a href="/p/{{$v.Id}}" rel="bookmark">
|
||||
<time class="entry-date published updated" datetime="{{$v.PostDateGmt}}">{{$v.PostDate|dateCh}}
|
||||
</time>
|
||||
</a>
|
||||
</span>
|
||||
{{if $v.IsSticky}}
|
||||
<span class="sticky-post">特色</span>
|
||||
{{end}}
|
||||
<span class="posted-on">
|
||||
<span class="screen-reader-text">发布于 </span>
|
||||
<a href="/p/{{$v.Id}}" rel="bookmark">
|
||||
<time class="entry-date published updated" datetime="{{$v.PostDateGmt}}">{{$v.PostDate|dateCh}}
|
||||
</time>
|
||||
</a>
|
||||
</span>
|
||||
{{if $v.CategoriesHtml}}
|
||||
<span class="cat-links">
|
||||
<span class="screen-reader-text">分类 </span>
|
||||
{{$v.CategoriesHtml|unescaped}}
|
||||
</span>
|
||||
<span class="screen-reader-text">分类 </span>
|
||||
{{$v.CategoriesHtml|unescaped}}
|
||||
</span>
|
||||
{{end}}
|
||||
|
||||
{{if $v.TagsHtml}}
|
||||
<span class="tags-links">
|
||||
<span class="screen-reader-text">标签 </span>
|
||||
{{$v.TagsHtml|unescaped}}
|
||||
</span>
|
||||
<span class="screen-reader-text">标签 </span>
|
||||
{{$v.TagsHtml|unescaped}}
|
||||
</span>
|
||||
{{end}}
|
||||
{{if gt $v.CommentCount 0}}
|
||||
<span class="comments-link">
|
||||
<a href="/p/{{$v.Id}}#comments">
|
||||
<span class="screen-reader-text">{{$v.PostTitle}}</span>有{{$v.CommentCount}}条评论
|
||||
</a>
|
||||
</span>
|
||||
<a href="/p/{{$v.Id}}#comments">
|
||||
<span class="screen-reader-text">{{$v.PostTitle}}</span>有{{$v.CommentCount}}条评论
|
||||
</a>
|
||||
</span>
|
||||
{{end}}
|
||||
|
||||
</footer><!-- .entry-footer -->
|
295
app/theme/twentyfifteen/themesupport.go
Normal file
295
app/theme/twentyfifteen/themesupport.go
Normal file
@ -0,0 +1,295 @@
|
||||
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{}{}
|
||||
}()
|
63
app/theme/twentyfifteen/twentyfifteen.go
Normal file
63
app/theme/twentyfifteen/twentyfifteen.go
Normal file
@ -0,0 +1,63 @@
|
||||
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))
|
||||
}
|
574
app/theme/twentyseventeen/colorscheme.go
Normal file
574
app/theme/twentyseventeen/colorscheme.go
Normal file
@ -0,0 +1,574 @@
|
||||
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; */
|
||||
}
|
||||
}
|
||||
`
|
48
app/theme/twentyseventeen/customheader.go
Normal file
48
app/theme/twentyseventeen/customheader.go
Normal file
@ -0,0 +1,48 @@
|
||||
package twentyseventeen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
)
|
||||
|
||||
func customHeader(h *wp.Handle) (r string) {
|
||||
themeMods := h.CommonThemeMods()
|
||||
headerTextColor := themeMods.HeaderTextcolor
|
||||
if headerTextColor == "" || headerTextColor == themeMods.ThemeSupport.CustomHeader.DefaultTextColor {
|
||||
return
|
||||
}
|
||||
css := `
|
||||
.site-title,
|
||||
.site-description {
|
||||
position: absolute;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
}`
|
||||
if headerTextColor != "blank" {
|
||||
css = fmt.Sprintf(customHeaderCss, headerTextColor)
|
||||
}
|
||||
r = fmt.Sprintf(`<style id="twentyseventeen-custom-header-styles" type="text/css">%s</style>`, css)
|
||||
return
|
||||
}
|
||||
|
||||
var customHeaderCss = `
|
||||
.site-title a,
|
||||
.colors-dark .site-title a,
|
||||
.colors-custom .site-title a,
|
||||
body.has-header-image .site-title a,
|
||||
body.has-header-video .site-title a,
|
||||
body.has-header-image.colors-dark .site-title a,
|
||||
body.has-header-video.colors-dark .site-title a,
|
||||
body.has-header-image.colors-custom .site-title a,
|
||||
body.has-header-video.colors-custom .site-title a,
|
||||
.site-description,
|
||||
.colors-dark .site-description,
|
||||
.colors-custom .site-description,
|
||||
body.has-header-image .site-description,
|
||||
body.has-header-video .site-description,
|
||||
body.has-header-image.colors-dark .site-description,
|
||||
body.has-header-video.colors-dark .site-description,
|
||||
body.has-header-image.colors-custom .site-description,
|
||||
body.has-header-video.colors-custom .site-description {
|
||||
color: #%s;
|
||||
}
|
||||
`
|
@ -1,19 +1,17 @@
|
||||
{{ define "layout/base"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{getLang}}" class="no-js no-svg">
|
||||
<head>
|
||||
{{template "layout/head" .}}
|
||||
{{block "head" .}}
|
||||
{{end}}
|
||||
</head>
|
||||
<body class="{{.bodyClass}} wp-embed-responsive hfeed has-header-image has-sidebar colors-light">
|
||||
|
||||
{{template "layout/head" .}}
|
||||
|
||||
<body class="{{.calBodyClass|exec}}">
|
||||
{{template "svg"}}
|
||||
<div id="page" class="site">
|
||||
<a class="skip-link screen-reader-text" href="#content">跳至内容</a>
|
||||
|
||||
<header id="masthead" class="site-header">
|
||||
|
||||
<div class="custom-header" style="margin-bottom: 0px;">
|
||||
<div class="custom-header" style="margin-bottom: 0;">
|
||||
<div class="custom-header-media">
|
||||
<div id="wp-custom-header" class="wp-custom-header">
|
||||
<img src="{{.HeaderImage.Path}}" width="{{.HeaderImage.Width}}" height="{{.HeaderImage.Height}}" alt="" {{if .HeaderImage.Srcset}}srcset="{{.HeaderImage.Srcset}}" {{end}} {{if .HeaderImage.Sizes}}sizes="{{.HeaderImage.Sizes}}" {{end}}>
|
||||
@ -22,41 +20,32 @@
|
||||
|
||||
<div class="site-branding" style="margin-bottom: 0px;">
|
||||
<div class="wrap">
|
||||
{{.customLogo|exec}}
|
||||
<div class="site-branding-text">
|
||||
<h1 class="site-title">
|
||||
<a href="/" rel="home">{{ "blogname"| getOption }}</a>
|
||||
</h1>
|
||||
<p class="site-description">{{"blogdescription"| getOption}}</p>
|
||||
</div><!-- .site-branding-text -->
|
||||
{{if eq .scene "Home"}}
|
||||
<a href="#content" class="menu-scroll-down">
|
||||
<svg class="icon icon-arrow-right" aria-hidden="true" role="img">
|
||||
<use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use>
|
||||
</svg>
|
||||
<span class="screen-reader-text">向下滚动到内容</span>
|
||||
</a>
|
||||
{{end}}
|
||||
|
||||
<a href="#content" class="menu-scroll-down">
|
||||
<svg class="icon icon-arrow-right" aria-hidden="true" role="img">
|
||||
<use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use>
|
||||
</svg>
|
||||
<span class="screen-reader-text">向下滚动到内容</span>
|
||||
</a>
|
||||
</div><!-- .wrap -->
|
||||
</div><!-- .site-branding -->
|
||||
|
||||
</div><!-- .custom-header -->
|
||||
|
||||
|
||||
</header>
|
||||
|
||||
|
||||
{{block "content" .}}
|
||||
|
||||
{{end}}
|
||||
|
||||
<footer id="colophon" class="site-footer">
|
||||
<div class="wrap">
|
||||
<div class="site-info">
|
||||
<a href="https://cn.wordpress.org/" class="imprint">自豪地采用WordPress</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
</div>
|
||||
{{template "layout/footer" .}}
|
||||
</body>
|
48
app/theme/twentyseventeen/layout/empty.gohtml
Normal file
48
app/theme/twentyseventeen/layout/empty.gohtml
Normal file
@ -0,0 +1,48 @@
|
||||
{{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}}
|
5
app/theme/twentyseventeen/layout/footer.gohtml
Normal file
5
app/theme/twentyseventeen/layout/footer.gohtml
Normal file
@ -0,0 +1,5 @@
|
||||
{{define "layout/footer"}}
|
||||
{{template "common/footer" .}}
|
||||
{{ block "footer" .}}
|
||||
{{end}}
|
||||
{{end}}
|
@ -161,7 +161,4 @@
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
{{block "footerx" .}}
|
||||
{{end}}
|
||||
{{end}}
|
@ -1,11 +1,9 @@
|
||||
{{define "layout/head"}}
|
||||
<head>
|
||||
<meta charset="{{"blog_charset"| getOption}}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="profile" href="https://gmpg.org/xfn/11">
|
||||
<link rel="pingback" href="/xmlrpc.php">
|
||||
<!--[if lt IE 9]>
|
||||
<script src="/wp-content/themes/twentyfifteen/js/html5.js?ver=3.7.0"></script>
|
||||
<![endif]-->
|
||||
<script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script>
|
||||
<title>{{ .title }}</title>
|
||||
<meta name='robots' content='max-image-preview:large' />
|
||||
@ -47,21 +45,8 @@
|
||||
.wp-block-pullquote{font-size: 1.5em;line-height: 1.6;}
|
||||
</style>
|
||||
|
||||
<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]-->
|
||||
<link rel='stylesheet' id='enlighterjs-css' href='/wp-content/plugins/enlighter/cache/enlighterjs.min.css?ver=0A0B0C' media='all' />
|
||||
<!--[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>
|
||||
{{template "common/head" .}}
|
||||
{{block "head" .}}
|
||||
{{end}}
|
||||
</head>
|
||||
{{end}}
|
3
app/theme/twentyseventeen/layout/sidebar.gohtml
Normal file
3
app/theme/twentyseventeen/layout/sidebar.gohtml
Normal file
@ -0,0 +1,3 @@
|
||||
{{define "layout/sidebar" }}
|
||||
{{template "common/sidebarWidget" .}}
|
||||
{{end}}
|
@ -1,7 +1,7 @@
|
||||
{{template "layout/base" .}}
|
||||
|
||||
{{define "content"}}
|
||||
{{ if .post.PostContent}}
|
||||
{{ if and (.post) (gt .post.Id 0) }}
|
||||
{{if .post.Thumbnail.Path}}
|
||||
<div class="single-featured-image-header">
|
||||
<img width="{{.post.Thumbnail.OriginAttachmentData.Width}}" height="{{.post.Thumbnail.OriginAttachmentData.Height}}" src="{{.post.Thumbnail.Path}}" class="attachment-twentyseventeen-featured-image size-twentyseventeen-featured-image wp-post-image" alt="{{.post.PostTitle}}" decoding="async" loading="lazy" srcset="{{.post.Thumbnail.Srcset}}" sizes="{{.post.Thumbnail.Sizes}}">
|
||||
@ -12,7 +12,7 @@
|
||||
<div class="wrap">
|
||||
<div id="primary" class="content-area">
|
||||
<main id="main" class="site-main">
|
||||
<article id="post-{{.post.Id}}" class="post-{{.post.Id}} post type-post status-publish format-standard hentry {{if .post.Thumbnail.Path}}has-post-thumbnail{{end}} category-uncategorized">
|
||||
<article id="post-{{.post.Id}}" class="{{ .post|postsFn .calPostClass}}">
|
||||
|
||||
<header class="entry-header">
|
||||
<div class="entry-meta">
|
||||
@ -64,12 +64,21 @@
|
||||
|
||||
{{ if .showComment}}
|
||||
<div id="comments" class="comments-area">
|
||||
{{ if gt .post.CommentCount 0}}
|
||||
<h2 class="comments-title">“{{.post.PostTitle}}”的{{.post.CommentCount}}个回复 </h2>
|
||||
{{ if ne .comments ""}}
|
||||
<h2 class="comments-title">“{{.post.PostTitle}}”的{{.totalCommentNum}}个回复 </h2>
|
||||
<ol class="comment-list">
|
||||
{{.comments|unescaped}}
|
||||
</ol>
|
||||
|
||||
{{if gt .totalCommentPage 1}}
|
||||
<nav class="navigation comments-pagination" aria-label="评论">
|
||||
<h2 class="screen-reader-text">评论导航</h2>
|
||||
<div class="nav-links">
|
||||
{{ .commentPageNav|unescaped}}
|
||||
</div><!-- .nav-links -->
|
||||
</nav>
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
{{if eq .post.CommentStatus "open"}}
|
||||
{{template "respond" .}}
|
||||
@ -122,6 +131,9 @@
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "common/colophon" .}}
|
||||
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
@ -129,8 +141,3 @@
|
||||
{{end }}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{ define "footerx"}}
|
||||
<script src='/wp-includes/js/comment-reply.min.js?ver=6.0.2' id='comment-reply-js'></script>
|
||||
{{end}}
|
||||
|
16
app/theme/twentyseventeen/posts/error.gohtml
Normal file
16
app/theme/twentyseventeen/posts/error.gohtml
Normal file
@ -0,0 +1,16 @@
|
||||
{{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}}
|
||||
|
@ -7,26 +7,22 @@
|
||||
|
||||
<div class="wrap">
|
||||
<header class="page-header">
|
||||
<h2 class="page-title">文章</h2>
|
||||
{{if .header}}
|
||||
<h1 class="page-title">{{.header | unescaped}}</h1>
|
||||
{{else}}
|
||||
<h2 class="page-title">文章</h2>
|
||||
{{end}}
|
||||
</header>
|
||||
|
||||
<div id="primary" class="content-area">
|
||||
<main id="main" class="site-main">
|
||||
{{if .header}}
|
||||
<header class="page-header">
|
||||
<h1 class="page-title">
|
||||
{{if .search}}
|
||||
{{.header}}
|
||||
{{else}}
|
||||
{{.header |unescaped}}
|
||||
{{end}}
|
||||
</h1>
|
||||
</header>
|
||||
{{end}}
|
||||
{{ range $k,$v:=.posts}}
|
||||
<article id="post-{{$v.Id}}"
|
||||
class="post-{{$v.Id}} post {{if $v.Thumbnail.Path}}has-post-thumbnail{{end}} type-post status-publish format-standard hentry category">
|
||||
|
||||
<article id="post-{{$v.Id}}" class="{{ $v|postsFn $.calPostClass}}">
|
||||
{{if $v.IsSticky}}
|
||||
<svg class="icon icon-thumb-tack" aria-hidden="true" role="img">
|
||||
<use href="#icon-thumb-tack" xlink:href="#icon-thumb-tack"></use>
|
||||
</svg>
|
||||
{{end}}
|
||||
<header class="entry-header">
|
||||
<div class="entry-meta">
|
||||
<span class="screen-reader-text">发布于 </span>
|
||||
@ -80,7 +76,7 @@
|
||||
{{template "layout/empty" .}}
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
{{template "common/colophon" .}}
|
||||
</div>
|
||||
{{end}}
|
43
app/theme/twentyseventeen/script.go
Normal file
43
app/theme/twentyseventeen/script.go
Normal file
@ -0,0 +1,43 @@
|
||||
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>`
|
212
app/theme/twentyseventeen/themesupport.go
Normal file
212
app/theme/twentyseventeen/themesupport.go
Normal file
@ -0,0 +1,212 @@
|
||||
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{}{}
|
||||
}()
|
229
app/theme/twentyseventeen/twentyseventeen.go
Normal file
229
app/theme/twentyseventeen/twentyseventeen.go
Normal file
@ -0,0 +1,229 @@
|
||||
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 ""
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user