Compare commits
No commits in common. "master" and "mapsafe" have entirely different histories.
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,7 +1,3 @@
|
|||||||
.idea
|
.idea
|
||||||
/wp-go.iml
|
wp-go.iml
|
||||||
/config.yaml
|
config.yaml
|
||||||
err.log
|
|
||||||
/plugins/
|
|
||||||
/config.json
|
|
||||||
go.sum
|
|
@ -1,9 +0,0 @@
|
|||||||
FROM golang:1.22.2-alpine as gobulidIso
|
|
||||||
COPY ./ /go/src/wp-go
|
|
||||||
WORKDIR /go/src/wp-go
|
|
||||||
RUN go build -ldflags "-w" -tags netgo -o wp-go app/cmd/main.go
|
|
||||||
|
|
||||||
FROM alpine:latest
|
|
||||||
WORKDIR /opt/wp-go
|
|
||||||
COPY --from=gobulidIso /go/src/wp-go/wp-go ./
|
|
||||||
ENTRYPOINT ["./wp-go"]
|
|
22
LICENSE
22
LICENSE
@ -1,22 +0,0 @@
|
|||||||
Copyright (c) 2023 星
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
|
||||||
obtaining a copy of this software and associated documentation
|
|
||||||
files (the "Software"), to deal in the Software without
|
|
||||||
restriction, including without limitation the rights to use,
|
|
||||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following
|
|
||||||
conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
||||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
||||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
OTHER DEALINGS IN THE SOFTWARE.
|
|
87
README.md
87
README.md
@ -1,87 +0,0 @@
|
|||||||
## wp-go
|
|
||||||
|
|
||||||
[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 清空缓存
|
|
||||||
|
|
||||||
#### 运行
|
|
||||||
```
|
|
||||||
go run app/cmd/main.go [-c configpath] [-p port]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 数据显示支持程度
|
|
||||||
|
|
||||||
| 页表 | 支持程度 |
|
|
||||||
|-----|---------------------------------------------|
|
|
||||||
| 列表页 | 首页/搜索/归档/分类/标签/作者 分页列表 |
|
|
||||||
| 详情页 | 显示内容、评论并可以添加评论(转发的php处理,需要配置php版的添加评论的url) |
|
|
||||||
| 侧边栏 | 支持旧版 近期文章、近期评论、规档、分类、其它操作 显示及设置, 支持新版 分类 |
|
|
||||||
|
|
||||||
#### 后台设置支持程度
|
|
||||||
|
|
||||||
- 仪表盘
|
|
||||||
- 外观
|
|
||||||
- 小工具
|
|
||||||
- 搜索
|
|
||||||
- 规档
|
|
||||||
- 近期文章
|
|
||||||
- 近期评论
|
|
||||||
- 分类
|
|
||||||
- 其它操作
|
|
||||||
|
|
||||||
- 设置-
|
|
||||||
- 常规
|
|
||||||
- 站点标题
|
|
||||||
- 副标题
|
|
||||||
- 阅读
|
|
||||||
- 博客页面至多显示数量
|
|
||||||
- Feed中显示最近数量
|
|
||||||
- 讨论
|
|
||||||
- 其他评论设置
|
|
||||||
- `启用|禁止`评论嵌套,最多嵌套层数
|
|
||||||
- 分页显示评论,每页显示评论条数,默认显示`最前/后`页
|
|
||||||
- 在每个页面顶部显示 `新旧`评论
|
|
||||||
|
|
||||||
#### 主题支持程度
|
|
||||||
|
|
||||||
| twentyfifteen | twentyseventeen |
|
|
||||||
|---------------|-----------------|
|
|
||||||
| 站点身份 | 站点身份 |
|
|
||||||
| 颜色 | 颜色 |
|
|
||||||
| 页眉图片 | 页眉媒体 |
|
|
||||||
| 背景图片 | 额外css |
|
|
||||||
| 额外css | |
|
|
||||||
|
|
||||||
#### 插件机制
|
|
||||||
|
|
||||||
分为对列表页文章数据的修改的插件和对影响整个程序表现的插件
|
|
||||||
|
|
||||||
| 列表页文章数据插件 | 整个程序表现的插件 |
|
|
||||||
|---------------------|--------------------------------------|
|
|
||||||
| digest 自动生成指定长度的摘要 | enlighter 代码高亮(需要在后台安装enlighterjs插件) |
|
|
||||||
| | hiddenLogin 隐藏登录入口 |
|
|
||||||
|
|
||||||
#### 其它
|
|
||||||
|
|
||||||
用的gin框架和sqlx,在外面封装了层查询的方法。
|
|
||||||
|
|
||||||
#### 鸣谢
|
|
||||||
|
|
||||||
<a href="https://jb.gg/OpenSourceSupport"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main) logo." width="30%"></a>
|
|
||||||
|
|
53
actions/comment.go
Normal file
53
actions/comment.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github/fthvgb1/wp-go/vars"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PostComment(c *gin.Context) {
|
||||||
|
jar, _ := cookiejar.New(nil)
|
||||||
|
cli := &http.Client{
|
||||||
|
Jar: jar,
|
||||||
|
Timeout: time.Second * 3,
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(c.Request.Body)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusConflict, err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", vars.Conf.PostCommentUrl, strings.NewReader(string(body)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer req.Body.Close()
|
||||||
|
for k, v := range c.Request.Header {
|
||||||
|
req.Header.Set(k, v[0])
|
||||||
|
}
|
||||||
|
res, err := cli.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if res.StatusCode == http.StatusOK && res.Request.Response.StatusCode == http.StatusFound {
|
||||||
|
for _, cookie := range res.Request.Response.Cookies() {
|
||||||
|
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
|
||||||
|
}
|
||||||
|
c.Redirect(http.StatusFound, res.Request.Response.Header.Get("Location"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = errors.New(string(s))
|
||||||
|
}
|
74
actions/common/comments.go
Normal file
74
actions/common/comments.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github/fthvgb1/wp-go/helper"
|
||||||
|
"github/fthvgb1/wp-go/logs"
|
||||||
|
"github/fthvgb1/wp-go/models"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RecentComments(ctx context.Context, n int) (r []models.WpComments) {
|
||||||
|
r, err := recentCommentsCaches.GetCache(ctx, time.Second)
|
||||||
|
if len(r) > n {
|
||||||
|
r = r[0:n]
|
||||||
|
}
|
||||||
|
logs.ErrPrintln(err, "get recent comment")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func recentComments(...any) (r []models.WpComments, err error) {
|
||||||
|
return models.Find[models.WpComments](models.SqlBuilder{
|
||||||
|
{"comment_approved", "1"},
|
||||||
|
{"post_status", "publish"},
|
||||||
|
}, "comment_ID,comment_author,comment_post_ID,post_title", "", models.SqlBuilder{{"comment_date_gmt", "desc"}}, models.SqlBuilder{
|
||||||
|
{"a", "left join", "wp_posts b", "a.comment_post_ID=b.ID"},
|
||||||
|
}, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostComments(ctx context.Context, Id uint64) ([]models.WpComments, error) {
|
||||||
|
ids, err := postCommentCaches.GetCache(ctx, Id, time.Second, Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return GetCommentByIds(ctx, ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func postComments(args ...any) ([]uint64, error) {
|
||||||
|
postId := args[0].(uint64)
|
||||||
|
r, err := models.Find[models.WpComments](models.SqlBuilder{
|
||||||
|
{"comment_approved", "1"},
|
||||||
|
{"comment_post_ID", "=", strconv.FormatUint(postId, 10), "int"},
|
||||||
|
}, "comment_ID", "", models.SqlBuilder{
|
||||||
|
{"comment_date_gmt", "asc"},
|
||||||
|
{"comment_ID", "asc"},
|
||||||
|
}, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return helper.SliceMap(r, func(t models.WpComments) uint64 {
|
||||||
|
return t.CommentId
|
||||||
|
}), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCommentById(ctx context.Context, id uint64) (models.WpComments, error) {
|
||||||
|
return commentsCache.GetCache(ctx, id, time.Second, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCommentByIds(ctx context.Context, ids []uint64) ([]models.WpComments, error) {
|
||||||
|
return commentsCache.GetCacheBatch(ctx, ids, time.Second, ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCommentByIds(args ...any) (map[uint64]models.WpComments, error) {
|
||||||
|
ids := args[0].([]uint64)
|
||||||
|
m := make(map[uint64]models.WpComments)
|
||||||
|
r, err := models.Find[models.WpComments](models.SqlBuilder{
|
||||||
|
{"comment_ID", "in", ""}, {"comment_approved", "1"},
|
||||||
|
}, "*", "", nil, nil, 0, helper.SliceMap(ids, helper.ToAny[uint64]))
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
return helper.SimpleSliceToMap(r, func(t models.WpComments) uint64 {
|
||||||
|
return t.CommentId
|
||||||
|
}), err
|
||||||
|
}
|
157
actions/common/common.go
Normal file
157
actions/common/common.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github/fthvgb1/wp-go/cache"
|
||||||
|
"github/fthvgb1/wp-go/logs"
|
||||||
|
"github/fthvgb1/wp-go/models"
|
||||||
|
"github/fthvgb1/wp-go/vars"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var postContextCache *cache.MapCache[uint64, PostContext]
|
||||||
|
var archivesCaches *Arch
|
||||||
|
var categoryCaches *cache.SliceCache[models.WpTermsMy]
|
||||||
|
var recentPostsCaches *cache.SliceCache[models.WpPosts]
|
||||||
|
var recentCommentsCaches *cache.SliceCache[models.WpComments]
|
||||||
|
var postCommentCaches *cache.MapCache[uint64, []uint64]
|
||||||
|
var postsCache *cache.MapCache[uint64, models.WpPosts]
|
||||||
|
var monthPostsCache *cache.MapCache[string, []uint64]
|
||||||
|
var postListIdsCache *cache.MapCache[string, PostIds]
|
||||||
|
var searchPostIdsCache *cache.MapCache[string, PostIds]
|
||||||
|
var maxPostIdCache *cache.SliceCache[uint64]
|
||||||
|
var TotalRaw int
|
||||||
|
var usersCache *cache.MapCache[uint64, models.WpUsers]
|
||||||
|
var commentsCache *cache.MapCache[uint64, models.WpComments]
|
||||||
|
|
||||||
|
func InitActionsCommonCache() {
|
||||||
|
archivesCaches = &Arch{
|
||||||
|
mutex: &sync.Mutex{},
|
||||||
|
setCacheFunc: archives,
|
||||||
|
}
|
||||||
|
|
||||||
|
searchPostIdsCache = cache.NewMapCacheByFn[string, PostIds](searchPostIds, vars.Conf.SearchPostCacheTime)
|
||||||
|
|
||||||
|
postListIdsCache = cache.NewMapCacheByFn[string, PostIds](searchPostIds, vars.Conf.PostListCacheTime)
|
||||||
|
|
||||||
|
monthPostsCache = cache.NewMapCacheByFn[string, []uint64](monthPost, vars.Conf.MonthPostCacheTime)
|
||||||
|
|
||||||
|
postContextCache = cache.NewMapCacheByFn[uint64, PostContext](getPostContext, vars.Conf.ContextPostCacheTime)
|
||||||
|
|
||||||
|
postsCache = cache.NewMapCacheByBatchFn[uint64, models.WpPosts](getPostsByIds, vars.Conf.PostDataCacheTime)
|
||||||
|
|
||||||
|
categoryCaches = cache.NewSliceCache[models.WpTermsMy](categories, vars.Conf.CategoryCacheTime)
|
||||||
|
|
||||||
|
recentPostsCaches = cache.NewSliceCache[models.WpPosts](recentPosts, vars.Conf.RecentPostCacheTime)
|
||||||
|
|
||||||
|
recentCommentsCaches = cache.NewSliceCache[models.WpComments](recentComments, vars.Conf.RecentCommentsCacheTime)
|
||||||
|
|
||||||
|
postCommentCaches = cache.NewMapCacheByFn[uint64, []uint64](postComments, vars.Conf.PostCommentsCacheTime)
|
||||||
|
|
||||||
|
maxPostIdCache = cache.NewSliceCache[uint64](getMaxPostId, vars.Conf.MaxPostIdCacheTime)
|
||||||
|
|
||||||
|
usersCache = cache.NewMapCacheByBatchFn[uint64, models.WpUsers](getUsers, vars.Conf.UserInfoCacheTime)
|
||||||
|
|
||||||
|
commentsCache = cache.NewMapCacheByBatchFn[uint64, models.WpComments](getCommentByIds, vars.Conf.CommentsCacheTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearCache() {
|
||||||
|
searchPostIdsCache.ClearExpired()
|
||||||
|
postsCache.ClearExpired()
|
||||||
|
postsCache.ClearExpired()
|
||||||
|
postListIdsCache.ClearExpired()
|
||||||
|
monthPostsCache.ClearExpired()
|
||||||
|
postContextCache.ClearExpired()
|
||||||
|
usersCache.ClearExpired()
|
||||||
|
commentsCache.ClearExpired()
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostIds struct {
|
||||||
|
Ids []uint64
|
||||||
|
Length int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Arch struct {
|
||||||
|
data []models.PostArchive
|
||||||
|
mutex *sync.Mutex
|
||||||
|
setCacheFunc func() ([]models.PostArchive, error)
|
||||||
|
month time.Month
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Arch) getArchiveCache() []models.PostArchive {
|
||||||
|
l := len(c.data)
|
||||||
|
m := time.Now().Month()
|
||||||
|
if l > 0 && c.month != m || l < 1 {
|
||||||
|
r, err := c.setCacheFunc()
|
||||||
|
if err != nil {
|
||||||
|
logs.ErrPrintln(err, "set cache err[%s]")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
c.month = m
|
||||||
|
c.data = r
|
||||||
|
}
|
||||||
|
return c.data
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostContext struct {
|
||||||
|
prev models.WpPosts
|
||||||
|
next models.WpPosts
|
||||||
|
}
|
||||||
|
|
||||||
|
func archives() ([]models.PostArchive, error) {
|
||||||
|
return models.Find[models.PostArchive](models.SqlBuilder{
|
||||||
|
{"post_type", "post"}, {"post_status", "publish"},
|
||||||
|
}, "YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, count(ID) as posts", "year,month", models.SqlBuilder{{"year", "desc"}, {"month", "desc"}}, nil, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Archives() (r []models.PostArchive) {
|
||||||
|
return archivesCaches.getArchiveCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Categories(ctx context.Context) []models.WpTermsMy {
|
||||||
|
r, err := categoryCaches.GetCache(ctx, time.Second)
|
||||||
|
logs.ErrPrintln(err, "get category ")
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func categories(...any) (terms []models.WpTermsMy, err error) {
|
||||||
|
var in = []any{"category"}
|
||||||
|
terms, err = models.Find[models.WpTermsMy](models.SqlBuilder{
|
||||||
|
{"tt.count", ">", "0", "int"},
|
||||||
|
{"tt.taxonomy", "in", ""},
|
||||||
|
}, "t.term_id", "", models.SqlBuilder{
|
||||||
|
{"t.name", "asc"},
|
||||||
|
}, models.SqlBuilder{
|
||||||
|
{"t", "inner join", "wp_term_taxonomy tt", "t.term_id = tt.term_id"},
|
||||||
|
}, 0, in)
|
||||||
|
for i := 0; i < len(terms); i++ {
|
||||||
|
if v, ok := models.Terms[terms[i].WpTerms.TermId]; ok {
|
||||||
|
terms[i].WpTerms = v
|
||||||
|
}
|
||||||
|
if v, ok := models.TermTaxonomy[terms[i].WpTerms.TermId]; ok {
|
||||||
|
terms[i].WpTermTaxonomy = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func PasswordProjectTitle(post *models.WpPosts) {
|
||||||
|
if post.PostPassword != "" {
|
||||||
|
post.PostTitle = fmt.Sprintf("密码保护:%s", post.PostTitle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PasswdProjectContent(post *models.WpPosts) {
|
||||||
|
if post.PostContent != "" {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
218
actions/common/posts.go
Normal file
218
actions/common/posts.go
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github/fthvgb1/wp-go/helper"
|
||||||
|
"github/fthvgb1/wp-go/logs"
|
||||||
|
"github/fthvgb1/wp-go/models"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPostById(ctx context.Context, id uint64) (models.WpPosts, error) {
|
||||||
|
return postsCache.GetCache(ctx, id, time.Second, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPostsByIds(ctx context.Context, ids []uint64) ([]models.WpPosts, error) {
|
||||||
|
return postsCache.GetCacheBatch(ctx, ids, time.Second, ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SearchPost(ctx context.Context, key string, args ...any) (r []models.WpPosts, total int, err error) {
|
||||||
|
ids, err := searchPostIdsCache.GetCache(ctx, key, time.Second, args...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
total = ids.Length
|
||||||
|
r, err = GetPostsByIds(ctx, ids.Ids)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPostsByIds(ids ...any) (m map[uint64]models.WpPosts, err error) {
|
||||||
|
m = make(map[uint64]models.WpPosts)
|
||||||
|
id := ids[0].([]uint64)
|
||||||
|
arg := helper.SliceMap(id, helper.ToAny[uint64])
|
||||||
|
rawPosts, err := models.Find[models.WpPosts](models.SqlBuilder{{
|
||||||
|
"Id", "in", "",
|
||||||
|
}}, "a.*,ifnull(d.name,'') category_name,ifnull(taxonomy,'') `taxonomy`", "", nil, models.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",
|
||||||
|
}}, 0, arg)
|
||||||
|
if err != nil {
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
postsMap := make(map[uint64]models.WpPosts)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
postsMap[post.Id] = v
|
||||||
|
}
|
||||||
|
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, "、")
|
||||||
|
}
|
||||||
|
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 PostLists(ctx context.Context, key string, args ...any) (r []models.WpPosts, total int, err error) {
|
||||||
|
ids, err := postListIdsCache.GetCache(ctx, key, time.Second, args...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
total = ids.Length
|
||||||
|
r, err = GetPostsByIds(ctx, ids.Ids)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchPostIds(args ...any) (ids PostIds, err error) {
|
||||||
|
where := args[0].(models.SqlBuilder)
|
||||||
|
page := args[1].(int)
|
||||||
|
limit := args[2].(int)
|
||||||
|
order := args[3].(models.SqlBuilder)
|
||||||
|
join := args[4].(models.SqlBuilder)
|
||||||
|
postType := args[5].([]any)
|
||||||
|
postStatus := args[6].([]any)
|
||||||
|
res, total, err := models.SimplePagination[models.WpPosts](where, "ID", "", page, limit, order, join, postType, postStatus)
|
||||||
|
for _, posts := range res {
|
||||||
|
ids.Ids = append(ids.Ids, posts.Id)
|
||||||
|
}
|
||||||
|
ids.Length = total
|
||||||
|
if total > TotalRaw {
|
||||||
|
TotalRaw = total
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMaxPostId(...any) ([]uint64, error) {
|
||||||
|
r, err := models.Find[models.WpPosts](models.SqlBuilder{{"post_type", "post"}, {"post_status", "publish"}}, "max(ID) ID", "", nil, nil, 0)
|
||||||
|
var id uint64
|
||||||
|
if len(r) > 0 {
|
||||||
|
id = r[0].Id
|
||||||
|
}
|
||||||
|
return []uint64{id}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMaxPostId(ctx *gin.Context) (uint64, error) {
|
||||||
|
Id, err := maxPostIdCache.GetCache(ctx, time.Second)
|
||||||
|
return Id[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func RecentPosts(ctx context.Context, n int) (r []models.WpPosts) {
|
||||||
|
r, err := recentPostsCaches.GetCache(ctx, time.Second)
|
||||||
|
if n < len(r) {
|
||||||
|
r = r[:n]
|
||||||
|
}
|
||||||
|
logs.ErrPrintln(err, "get recent post")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func recentPosts(...any) (r []models.WpPosts, err error) {
|
||||||
|
r, err = models.Find[models.WpPosts](models.SqlBuilder{{
|
||||||
|
"post_type", "post",
|
||||||
|
}, {"post_status", "publish"}}, "ID,post_title,post_password", "", models.SqlBuilder{{"post_date", "desc"}}, nil, 10)
|
||||||
|
for i, post := range r {
|
||||||
|
if post.PostPassword != "" {
|
||||||
|
PasswordProjectTitle(&r[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next models.WpPosts, err error) {
|
||||||
|
postCtx, err := postContextCache.GetCache(ctx, id, time.Second, date)
|
||||||
|
if err != nil {
|
||||||
|
return models.WpPosts{}, models.WpPosts{}, err
|
||||||
|
}
|
||||||
|
prev = postCtx.prev
|
||||||
|
next = postCtx.next
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPostContext(arg ...any) (r PostContext, err error) {
|
||||||
|
t := arg[0].(time.Time)
|
||||||
|
next, err := models.FirstOne[models.WpPosts](models.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 err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prev, err := models.FirstOne[models.WpPosts](models.SqlBuilder{
|
||||||
|
{"post_date", "<", t.Format("2006-01-02 15:04:05")},
|
||||||
|
{"post_status", "in", ""},
|
||||||
|
{"post_type", "post"},
|
||||||
|
}, "ID,post_title", models.SqlBuilder{{"post_date", "desc"}}, []any{"publish"})
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r = PostContext{
|
||||||
|
prev: prev,
|
||||||
|
next: next,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMonthPostIds(ctx context.Context, year, month string, page, limit int, order string) (r []models.WpPosts, total int, err error) {
|
||||||
|
res, err := monthPostsCache.GetCache(ctx, fmt.Sprintf("%s%s", year, month), time.Second, year, month)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if order == "desc" {
|
||||||
|
res = helper.SliceReverse(res)
|
||||||
|
}
|
||||||
|
total = len(res)
|
||||||
|
rr := helper.SlicePagination(res, page, limit)
|
||||||
|
r, err = GetPostsByIds(ctx, rr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func monthPost(args ...any) (r []uint64, err error) {
|
||||||
|
year, month := args[0].(string), args[1].(string)
|
||||||
|
where := models.SqlBuilder{
|
||||||
|
{"post_type", "in", ""},
|
||||||
|
{"post_status", "in", ""},
|
||||||
|
{"year(post_date)", year},
|
||||||
|
{"month(post_date)", month},
|
||||||
|
}
|
||||||
|
postType := []any{"post"}
|
||||||
|
status := []any{"publish"}
|
||||||
|
ids, err := models.Find[models.WpPosts](where, "ID", "", models.SqlBuilder{{"Id", "asc"}}, nil, 0, postType, status)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, post := range ids {
|
||||||
|
r = append(r, post.Id)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
23
actions/common/users.go
Normal file
23
actions/common/users.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github/fthvgb1/wp-go/logs"
|
||||||
|
"github/fthvgb1/wp-go/models"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getUsers(...any) (m map[uint64]models.WpUsers, err error) {
|
||||||
|
m = make(map[uint64]models.WpUsers)
|
||||||
|
r, err := models.Find[models.WpUsers](nil, "*", "", nil, nil, 0)
|
||||||
|
for _, user := range r {
|
||||||
|
m[user.Id] = user
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUser(ctx *gin.Context, uid uint64) models.WpUsers {
|
||||||
|
r, err := usersCache.GetCache(ctx, uid, time.Second, uid)
|
||||||
|
logs.ErrPrintln(err, "get user", uid)
|
||||||
|
return r
|
||||||
|
}
|
262
actions/detail.go
Normal file
262
actions/detail.go
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github/fthvgb1/wp-go/actions/common"
|
||||||
|
"github/fthvgb1/wp-go/helper"
|
||||||
|
"github/fthvgb1/wp-go/logs"
|
||||||
|
"github/fthvgb1/wp-go/models"
|
||||||
|
"github/fthvgb1/wp-go/plugins"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type detailHandler struct {
|
||||||
|
*gin.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func Detail(c *gin.Context) {
|
||||||
|
var err error
|
||||||
|
hh := detailHandler{
|
||||||
|
c,
|
||||||
|
}
|
||||||
|
recent := common.RecentPosts(c, 5)
|
||||||
|
archive := common.Archives()
|
||||||
|
categoryItems := common.Categories(c)
|
||||||
|
recentComments := common.RecentComments(c, 5)
|
||||||
|
var h = gin.H{
|
||||||
|
"title": models.Options["blogname"],
|
||||||
|
"options": models.Options,
|
||||||
|
"recentPosts": recent,
|
||||||
|
"archives": archive,
|
||||||
|
"categories": categoryItems,
|
||||||
|
"recentComments": recentComments,
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
status := http.StatusOK
|
||||||
|
if err != nil {
|
||||||
|
status = http.StatusInternalServerError
|
||||||
|
c.Error(err)
|
||||||
|
}
|
||||||
|
c.HTML(status, "posts/detail.gohtml", h)
|
||||||
|
}()
|
||||||
|
id := c.Param("id")
|
||||||
|
Id := 0
|
||||||
|
if id != "" {
|
||||||
|
Id, err = strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ID := uint64(Id)
|
||||||
|
maxId, err := common.GetMaxPostId(c)
|
||||||
|
logs.ErrPrintln(err, "get max post id")
|
||||||
|
if ID > maxId || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
post, err := common.GetPostById(c, ID)
|
||||||
|
if post.Id == 0 || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pw := sessions.Default(c).Get("post_password")
|
||||||
|
showComment := false
|
||||||
|
if post.CommentCount > 0 || post.CommentStatus == "open" {
|
||||||
|
showComment = true
|
||||||
|
}
|
||||||
|
common.PasswordProjectTitle(&post)
|
||||||
|
if post.PostPassword != "" && pw != post.PostPassword {
|
||||||
|
common.PasswdProjectContent(&post)
|
||||||
|
showComment = false
|
||||||
|
}
|
||||||
|
plugins.ApplyPlugin(plugins.NewPostPlugin(c, plugins.Detail), &post)
|
||||||
|
comments, err := common.PostComments(c, post.Id)
|
||||||
|
logs.ErrPrintln(err, "get post comment", post.Id)
|
||||||
|
commentss := treeComments(comments)
|
||||||
|
prev, next, err := common.GetContextPost(c, post.Id, post.PostDate)
|
||||||
|
logs.ErrPrintln(err, "get pre and next post", post.Id, post.PostDate)
|
||||||
|
h["title"] = fmt.Sprintf("%s-%s", post.PostTitle, models.Options["blogname"])
|
||||||
|
h["post"] = post
|
||||||
|
h["showComment"] = showComment
|
||||||
|
h["prev"] = prev
|
||||||
|
h["comments"] = hh.formatComment(commentss, 1, 5)
|
||||||
|
h["next"] = next
|
||||||
|
}
|
||||||
|
|
||||||
|
type Comment struct {
|
||||||
|
models.WpComments
|
||||||
|
Children []*Comment
|
||||||
|
}
|
||||||
|
|
||||||
|
type Comments []*Comment
|
||||||
|
|
||||||
|
func (c Comments) Len() int {
|
||||||
|
return len(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Comments) Less(i, j int) bool {
|
||||||
|
return c[i].CommentId < c[j].CommentId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Comments) Swap(i, j int) {
|
||||||
|
c[i], c[j] = c[j], c[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d detailHandler) formatComment(comments Comments, depth, maxDepth int) (html string) {
|
||||||
|
s := strings.Builder{}
|
||||||
|
if depth > maxDepth {
|
||||||
|
comments = findComments(comments)
|
||||||
|
}
|
||||||
|
sort.Sort(comments)
|
||||||
|
for i, comment := range comments {
|
||||||
|
eo := "even"
|
||||||
|
if (i+1)%2 == 0 {
|
||||||
|
eo = "odd"
|
||||||
|
}
|
||||||
|
p := ""
|
||||||
|
fl := false
|
||||||
|
if len(comment.Children) > 0 && depth < maxDepth+1 {
|
||||||
|
p = "parent"
|
||||||
|
fl = true
|
||||||
|
}
|
||||||
|
s.WriteString(d.formatLi(comment.WpComments, depth, eo, p))
|
||||||
|
if fl {
|
||||||
|
depth++
|
||||||
|
s.WriteString(`<ol class="children">`)
|
||||||
|
s.WriteString(d.formatComment(comment.Children, depth, maxDepth))
|
||||||
|
s.WriteString(`</ol>`)
|
||||||
|
}
|
||||||
|
s.WriteString("</li><!-- #comment-## -->")
|
||||||
|
i++
|
||||||
|
if i >= len(comments) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html = s.String()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func findComments(comments Comments) Comments {
|
||||||
|
var r Comments
|
||||||
|
for _, comment := range comments {
|
||||||
|
tmp := *comment
|
||||||
|
tmp.Children = nil
|
||||||
|
r = append(r, &tmp)
|
||||||
|
if len(comment.Children) > 0 {
|
||||||
|
t := findComments(comment.Children)
|
||||||
|
r = append(r, t...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func treeComments(comments []models.WpComments) Comments {
|
||||||
|
var r = map[uint64]*Comment{
|
||||||
|
0: {
|
||||||
|
WpComments: models.WpComments{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var top []*Comment
|
||||||
|
for _, comment := range comments {
|
||||||
|
c := Comment{
|
||||||
|
WpComments: comment,
|
||||||
|
Children: []*Comment{},
|
||||||
|
}
|
||||||
|
r[comment.CommentId] = &c
|
||||||
|
if comment.CommentParent == 0 {
|
||||||
|
top = append(top, &c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id, son := range r {
|
||||||
|
if id == son.CommentParent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parent := r[son.CommentParent]
|
||||||
|
parent.Children = append(parent.Children, son)
|
||||||
|
}
|
||||||
|
return top
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d detailHandler) formatLi(comments models.WpComments, depth int, eo, parent string) string {
|
||||||
|
li := `
|
||||||
|
<li id="comment-{{CommentId}}" class="comment {{eo}} thread-even depth-{{Depth}} {{parent}}">
|
||||||
|
<article id="div-comment-{{CommentId}}" class="comment-body">
|
||||||
|
<footer class="comment-meta">
|
||||||
|
<div class="comment-author vcard">
|
||||||
|
<img alt=""
|
||||||
|
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>
|
||||||
|
<span class="says">说道:</span></div><!-- .comment-author -->
|
||||||
|
|
||||||
|
<div class="comment-metadata">
|
||||||
|
<a href="/p/{{PostId}}#comment-{{CommentId}}">
|
||||||
|
<time datetime="{{CommentDateGmt}}">{{CommentDate}}</time>
|
||||||
|
</a></div><!-- .comment-metadata -->
|
||||||
|
|
||||||
|
</footer><!-- .comment-meta -->
|
||||||
|
|
||||||
|
<div class="comment-content">
|
||||||
|
<p>{{CommentContent}}</p>
|
||||||
|
</div><!-- .comment-content -->
|
||||||
|
|
||||||
|
<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 -->
|
||||||
|
|
||||||
|
`
|
||||||
|
for k, v := range map[string]string{
|
||||||
|
"{{CommentId}}": strconv.FormatUint(comments.CommentId, 10),
|
||||||
|
"{{Depth}}": strconv.Itoa(depth),
|
||||||
|
"{{Gravatar}}": gravatar(d.Context, comments.CommentAuthorEmail),
|
||||||
|
"{{CommentAuthorUrl}}": comments.CommentAuthorUrl,
|
||||||
|
"{{CommentAuthor}}": comments.CommentAuthor,
|
||||||
|
"{{PostId}}": strconv.FormatUint(comments.CommentPostId, 10),
|
||||||
|
"{{CommentDateGmt}}": comments.CommentDateGmt.String(),
|
||||||
|
"{{CommentDate}}": comments.CommentDate.Format("2006-01-02 15:04"),
|
||||||
|
"{{CommentContent}}": comments.CommentContent,
|
||||||
|
"{{eo}}": eo,
|
||||||
|
"{{parent}}": parent,
|
||||||
|
} {
|
||||||
|
li = strings.Replace(li, k, v, -1)
|
||||||
|
}
|
||||||
|
return li
|
||||||
|
}
|
||||||
|
|
||||||
|
func gravatar(c *gin.Context, email string) (u string) {
|
||||||
|
email = strings.Trim(email, " \t\n\r\000\x0B")
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
num := rand.Intn(3)
|
||||||
|
h := ""
|
||||||
|
if email != "" {
|
||||||
|
h = helper.StringMd5(strings.ToLower(email))
|
||||||
|
num = int(h[0] % 3)
|
||||||
|
}
|
||||||
|
if c.Request.TLS != nil {
|
||||||
|
u = fmt.Sprintf("%s%s", "https://secure.gravatar.com/avatar/", h)
|
||||||
|
} else {
|
||||||
|
u = fmt.Sprintf("http://%d.gravatar.com/avatar/%s", num, h)
|
||||||
|
}
|
||||||
|
q := url.Values{}
|
||||||
|
q.Add("s", "112")
|
||||||
|
q.Add("d", "mm")
|
||||||
|
q.Add("r", "g")
|
||||||
|
u = fmt.Sprintf("%s?%s", u, q.Encode())
|
||||||
|
return
|
||||||
|
}
|
259
actions/feed.go
Normal file
259
actions/feed.go
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github/fthvgb1/wp-go/actions/common"
|
||||||
|
"github/fthvgb1/wp-go/cache"
|
||||||
|
"github/fthvgb1/wp-go/helper"
|
||||||
|
"github/fthvgb1/wp-go/logs"
|
||||||
|
"github/fthvgb1/wp-go/models"
|
||||||
|
"github/fthvgb1/wp-go/plugins"
|
||||||
|
"github/fthvgb1/wp-go/rss2"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var feedCache = cache.NewSliceCache(feed, time.Hour)
|
||||||
|
var postFeedCache = cache.NewMapCacheByFn[string, string](postFeed, time.Hour)
|
||||||
|
var tmp = "Mon, 02 Jan 2006 15:04:05 GMT"
|
||||||
|
var timeFormat = "Mon, 02 Jan 2006 15:04:05 +0000"
|
||||||
|
var templateRss rss2.Rss2
|
||||||
|
var commentsFeedCache = cache.NewSliceCache(commentsFeed, time.Hour)
|
||||||
|
|
||||||
|
func InitFeed() {
|
||||||
|
templateRss = rss2.Rss2{
|
||||||
|
Title: models.Options["blogname"],
|
||||||
|
AtomLink: fmt.Sprintf("%s/feed", models.Options["home"]),
|
||||||
|
Link: models.Options["siteurl"],
|
||||||
|
Description: models.Options["blogdescription"],
|
||||||
|
Language: "zh-CN",
|
||||||
|
UpdatePeriod: "hourly",
|
||||||
|
UpdateFrequency: 1,
|
||||||
|
Generator: models.Options["home"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearCache() {
|
||||||
|
postFeedCache.ClearExpired()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCacheExpired(c *gin.Context, lastTime time.Time) bool {
|
||||||
|
eTag := helper.StringMd5(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 Feed(c *gin.Context) {
|
||||||
|
if !isCacheExpired(c, feedCache.SetTime()) {
|
||||||
|
c.Status(http.StatusNotModified)
|
||||||
|
} else {
|
||||||
|
r, err := feedCache.GetCache(c, time.Second, c)
|
||||||
|
if err != nil {
|
||||||
|
c.Status(http.StatusInternalServerError)
|
||||||
|
c.Abort()
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setFeed(r[0], c, feedCache.SetTime())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func feed(arg ...any) (xml []string, err error) {
|
||||||
|
c := arg[0].(*gin.Context)
|
||||||
|
r := common.RecentPosts(c, 10)
|
||||||
|
ids := helper.SliceMap(r, func(t models.WpPosts) uint64 {
|
||||||
|
return t.Id
|
||||||
|
})
|
||||||
|
posts, err := common.GetPostsByIds(c, ids)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rs := templateRss
|
||||||
|
rs.LastBuildDate = time.Now().Format(timeFormat)
|
||||||
|
rs.Items = helper.SliceMap(posts, func(t models.WpPosts) rss2.Item {
|
||||||
|
desc := "无法提供摘要。这是一篇受保护的文章。"
|
||||||
|
common.PasswordProjectTitle(&t)
|
||||||
|
if t.PostPassword != "" {
|
||||||
|
common.PasswdProjectContent(&t)
|
||||||
|
} else {
|
||||||
|
desc = plugins.DigestRaw(t.PostContent, 55, fmt.Sprintf("/p/%d", t.Id))
|
||||||
|
}
|
||||||
|
l := ""
|
||||||
|
if t.CommentStatus == "open" && t.CommentCount > 0 {
|
||||||
|
l = fmt.Sprintf("%s/p/%d#comments", models.Options["siteurl"], t.Id)
|
||||||
|
} else if t.CommentStatus == "open" && t.CommentCount == 0 {
|
||||||
|
l = fmt.Sprintf("%s/p/%d#respond", models.Options["siteurl"], t.Id)
|
||||||
|
}
|
||||||
|
user := common.GetUser(c, t.PostAuthor)
|
||||||
|
|
||||||
|
return rss2.Item{
|
||||||
|
Title: t.PostTitle,
|
||||||
|
Creator: user.DisplayName,
|
||||||
|
Guid: t.Guid,
|
||||||
|
SlashComments: int(t.CommentCount),
|
||||||
|
Content: t.PostContent,
|
||||||
|
Category: strings.Join(t.Categories, "、"),
|
||||||
|
CommentLink: l,
|
||||||
|
CommentRss: fmt.Sprintf("%s/p/%d/feed", models.Options["siteurl"], t.Id),
|
||||||
|
Link: fmt.Sprintf("%s/p/%d", models.Options["siteurl"], t.Id),
|
||||||
|
Description: desc,
|
||||||
|
PubDate: t.PostDateGmt.Format(timeFormat),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
xml = []string{rs.GetXML()}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFeed(s string, c *gin.Context, t time.Time) {
|
||||||
|
lastTimeGMT := t.Format(tmp)
|
||||||
|
eTag := helper.StringMd5(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")
|
||||||
|
if !isCacheExpired(c, postFeedCache.GetSetTime(id)) {
|
||||||
|
c.Status(http.StatusNotModified)
|
||||||
|
} else {
|
||||||
|
s, err := postFeedCache.GetCache(c, id, time.Second, c, id)
|
||||||
|
if err != nil {
|
||||||
|
c.Status(http.StatusInternalServerError)
|
||||||
|
c.Abort()
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setFeed(s, c, postFeedCache.GetSetTime(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
maxId, err := common.GetMaxPostId(c)
|
||||||
|
logs.ErrPrintln(err, "get max post id")
|
||||||
|
if ID > maxId || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
post, err := common.GetPostById(c, ID)
|
||||||
|
if post.Id == 0 || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.PasswordProjectTitle(&post)
|
||||||
|
comments, err := common.PostComments(c, post.Id)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rs := templateRss
|
||||||
|
|
||||||
|
rs.Title = fmt.Sprintf("《%s》的评论", post.PostTitle)
|
||||||
|
rs.AtomLink = fmt.Sprintf("%s/p/%d/feed", models.Options["siteurl"], post.Id)
|
||||||
|
rs.Link = fmt.Sprintf("%s/p/%d", models.Options["siteurl"], post.Id)
|
||||||
|
rs.LastBuildDate = time.Now().Format(timeFormat)
|
||||||
|
if post.PostPassword != "" {
|
||||||
|
if len(comments) > 0 {
|
||||||
|
common.PasswdProjectContent(&post)
|
||||||
|
t := comments[len(comments)-1]
|
||||||
|
rs.Items = []rss2.Item{
|
||||||
|
{
|
||||||
|
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
|
||||||
|
Link: fmt.Sprintf("%s/p/%d#comment-%d", models.Options["siteurl"], post.Id, t.CommentId),
|
||||||
|
Creator: t.CommentAuthor,
|
||||||
|
PubDate: t.CommentDateGmt.Format(timeFormat),
|
||||||
|
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
|
||||||
|
Description: "评论受保护:要查看请输入密码。",
|
||||||
|
Content: post.PostContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rs.Items = helper.SliceMap(comments, func(t models.WpComments) rss2.Item {
|
||||||
|
return rss2.Item{
|
||||||
|
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
|
||||||
|
Link: fmt.Sprintf("%s/p/%d#comment-%d", models.Options["siteurl"], post.Id, t.CommentId),
|
||||||
|
Creator: t.CommentAuthor,
|
||||||
|
PubDate: t.CommentDateGmt.Format(timeFormat),
|
||||||
|
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
|
||||||
|
Content: t.CommentContent,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
x = rs.GetXML()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CommentsFeed(c *gin.Context) {
|
||||||
|
if !isCacheExpired(c, commentsFeedCache.SetTime()) {
|
||||||
|
c.Status(http.StatusNotModified)
|
||||||
|
} else {
|
||||||
|
r, err := commentsFeedCache.GetCache(c, time.Second, c)
|
||||||
|
if err != nil {
|
||||||
|
c.Status(http.StatusInternalServerError)
|
||||||
|
c.Abort()
|
||||||
|
c.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setFeed(r[0], c, commentsFeedCache.SetTime())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func commentsFeed(args ...any) (r []string, err error) {
|
||||||
|
c := args[0].(*gin.Context)
|
||||||
|
commens := common.RecentComments(c, 10)
|
||||||
|
rs := templateRss
|
||||||
|
rs.Title = fmt.Sprintf("\"%s\"的评论", models.Options["blogname"])
|
||||||
|
rs.LastBuildDate = time.Now().Format(timeFormat)
|
||||||
|
rs.AtomLink = fmt.Sprintf("%s/comments/feed", models.Options["siteurl"])
|
||||||
|
com, err := common.GetCommentByIds(c, helper.SliceMap(commens, func(t models.WpComments) uint64 {
|
||||||
|
return t.CommentId
|
||||||
|
}))
|
||||||
|
if nil != err {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
rs.Items = helper.SliceMap(com, func(t models.WpComments) rss2.Item {
|
||||||
|
post, _ := common.GetPostById(c, t.CommentPostId)
|
||||||
|
common.PasswordProjectTitle(&post)
|
||||||
|
desc := "评论受保护:要查看请输入密码。"
|
||||||
|
content := t.CommentContent
|
||||||
|
if post.PostPassword != "" {
|
||||||
|
common.PasswdProjectContent(&post)
|
||||||
|
content = post.PostContent
|
||||||
|
} else {
|
||||||
|
desc = plugins.ClearHtml(t.CommentContent)
|
||||||
|
content = desc
|
||||||
|
}
|
||||||
|
return rss2.Item{
|
||||||
|
Title: fmt.Sprintf("%s对《%s》的评论", t.CommentAuthor, post.PostTitle),
|
||||||
|
Link: fmt.Sprintf("%s/p/%d#comment-%d", models.Options["siteurl"], post.Id, t.CommentId),
|
||||||
|
Creator: t.CommentAuthor,
|
||||||
|
Description: desc,
|
||||||
|
PubDate: t.CommentDateGmt.Format(timeFormat),
|
||||||
|
Guid: fmt.Sprintf("%s#commment-%d", post.Guid, t.CommentId),
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
r = []string{rs.GetXML()}
|
||||||
|
return
|
||||||
|
}
|
318
actions/index.go
Normal file
318
actions/index.go
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github/fthvgb1/wp-go/actions/common"
|
||||||
|
"github/fthvgb1/wp-go/helper"
|
||||||
|
"github/fthvgb1/wp-go/models"
|
||||||
|
"github/fthvgb1/wp-go/plugins"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type indexHandle struct {
|
||||||
|
c *gin.Context
|
||||||
|
session sessions.Session
|
||||||
|
page int
|
||||||
|
pageSize int
|
||||||
|
title string
|
||||||
|
titleL string
|
||||||
|
titleR string
|
||||||
|
search string
|
||||||
|
totalPage int
|
||||||
|
category string
|
||||||
|
categoryType string
|
||||||
|
where models.SqlBuilder
|
||||||
|
orderBy string
|
||||||
|
order string
|
||||||
|
join models.SqlBuilder
|
||||||
|
postType []any
|
||||||
|
status []any
|
||||||
|
header string
|
||||||
|
paginationStep int
|
||||||
|
scene uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIndexHandle(ctx *gin.Context) *indexHandle {
|
||||||
|
return &indexHandle{
|
||||||
|
c: ctx,
|
||||||
|
session: sessions.Default(ctx),
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
paginationStep: 1,
|
||||||
|
titleL: models.Options["blogname"],
|
||||||
|
titleR: models.Options["blogdescription"],
|
||||||
|
where: models.SqlBuilder{
|
||||||
|
{"post_type", "in", ""},
|
||||||
|
{"post_status", "in", ""},
|
||||||
|
},
|
||||||
|
orderBy: "post_date",
|
||||||
|
join: models.SqlBuilder{},
|
||||||
|
postType: []any{"post"},
|
||||||
|
status: []any{"publish"},
|
||||||
|
scene: plugins.Home,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (h *indexHandle) setTitleLR(l, r string) {
|
||||||
|
h.titleL = l
|
||||||
|
h.titleR = r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *indexHandle) getTitle() string {
|
||||||
|
h.title = fmt.Sprintf("%s-%s", h.titleL, h.titleR)
|
||||||
|
return h.title
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *indexHandle) getSearchKey() string {
|
||||||
|
return fmt.Sprintf("action:%s|%s|%s|%s|%s|%d|%d", h.search, h.orderBy, h.order, h.category, h.categoryType, h.page, h.pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *indexHandle) parseParams() {
|
||||||
|
h.order = h.c.Query("order")
|
||||||
|
if !helper.IsContainInArr(h.order, []string{"asc", "desc"}) {
|
||||||
|
h.order = "asc"
|
||||||
|
}
|
||||||
|
year := h.c.Param("year")
|
||||||
|
if year != "" {
|
||||||
|
h.where = append(h.where, []string{
|
||||||
|
"year(post_date)", year,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
month := h.c.Param("month")
|
||||||
|
if month != "" {
|
||||||
|
h.where = append(h.where, []string{
|
||||||
|
"month(post_date)", month,
|
||||||
|
})
|
||||||
|
ss := fmt.Sprintf("%s年%s月", year, strings.TrimLeft(month, "0"))
|
||||||
|
h.header = fmt.Sprintf("月度归档: <span>%s</span>", ss)
|
||||||
|
h.setTitleLR(ss, models.Options["blogname"])
|
||||||
|
h.scene = plugins.Archive
|
||||||
|
}
|
||||||
|
category := h.c.Param("category")
|
||||||
|
if category == "" {
|
||||||
|
category = h.c.Param("tag")
|
||||||
|
if category != "" {
|
||||||
|
h.categoryType = "post_tag"
|
||||||
|
h.header = fmt.Sprintf("标签: <span>%s</span>", category)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.categoryType = "category"
|
||||||
|
h.header = fmt.Sprintf("分类: <span>%s</span>", category)
|
||||||
|
}
|
||||||
|
h.category = category
|
||||||
|
|
||||||
|
if category != "" {
|
||||||
|
h.where = append(h.where, []string{
|
||||||
|
"d.name", category,
|
||||||
|
}, []string{"taxonomy", h.categoryType})
|
||||||
|
h.join = append(h.join, []string{
|
||||||
|
"a", "left join", "wp_term_relationships b", "a.Id=b.object_id",
|
||||||
|
}, []string{
|
||||||
|
"left join", "wp_term_taxonomy c", "b.term_taxonomy_id=c.term_taxonomy_id",
|
||||||
|
}, []string{
|
||||||
|
"left join", "wp_terms d", "c.term_id=d.term_id",
|
||||||
|
})
|
||||||
|
h.setTitleLR(category, models.Options["blogname"])
|
||||||
|
h.scene = plugins.Category
|
||||||
|
}
|
||||||
|
s := h.c.Query("s")
|
||||||
|
if s != "" && strings.Replace(s, " ", "", -1) != "" {
|
||||||
|
q := helper.StrJoin("%", s, "%")
|
||||||
|
h.where = append(h.where, []string{
|
||||||
|
"and", "post_title", "like", q, "",
|
||||||
|
"or", "post_content", "like", q, "",
|
||||||
|
"or", "post_excerpt", "like", q, "",
|
||||||
|
}, []string{"post_password", ""})
|
||||||
|
h.postType = append(h.postType, "page", "attachment")
|
||||||
|
h.header = fmt.Sprintf("%s的搜索结果", s)
|
||||||
|
h.setTitleLR(helper.StrJoin(`"`, s, `"`, "的搜索结果"), models.Options["blogname"])
|
||||||
|
h.search = s
|
||||||
|
h.scene = plugins.Search
|
||||||
|
}
|
||||||
|
p := h.c.Query("paged")
|
||||||
|
if p == "" {
|
||||||
|
p = h.c.Param("page")
|
||||||
|
}
|
||||||
|
if p != "" {
|
||||||
|
if pa, err := strconv.Atoi(p); err == nil {
|
||||||
|
h.page = pa
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if common.TotalRaw > 0 && common.TotalRaw < (h.page-1)*h.pageSize {
|
||||||
|
h.page = 1
|
||||||
|
}
|
||||||
|
if h.page > 1 && (h.category != "" || h.search != "" || month != "") {
|
||||||
|
h.setTitleLR(fmt.Sprintf("%s-第%d页", h.titleL, h.page), models.Options["blogname"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *indexHandle) getTotalPage(totalRaws int) int {
|
||||||
|
h.totalPage = int(math.Ceil(float64(totalRaws) / float64(h.pageSize)))
|
||||||
|
return h.totalPage
|
||||||
|
}
|
||||||
|
|
||||||
|
func Index(c *gin.Context) {
|
||||||
|
h := newIndexHandle(c)
|
||||||
|
h.parseParams()
|
||||||
|
archive := common.Archives()
|
||||||
|
recent := common.RecentPosts(c, 5)
|
||||||
|
categoryItems := common.Categories(c)
|
||||||
|
recentComments := common.RecentComments(c, 5)
|
||||||
|
ginH := gin.H{
|
||||||
|
"options": models.Options,
|
||||||
|
"recentPosts": recent,
|
||||||
|
"archives": archive,
|
||||||
|
"categories": categoryItems,
|
||||||
|
"search": h.search,
|
||||||
|
"header": h.header,
|
||||||
|
"title": h.getTitle(),
|
||||||
|
"recentComments": recentComments,
|
||||||
|
}
|
||||||
|
var postIds []models.WpPosts
|
||||||
|
var totalRaw int
|
||||||
|
var err error
|
||||||
|
if c.Param("month") != "" {
|
||||||
|
postIds, totalRaw, err = common.GetMonthPostIds(c, c.Param("year"), c.Param("month"), h.page, h.pageSize, h.order)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if h.search != "" {
|
||||||
|
postIds, totalRaw, err = common.SearchPost(c, h.getSearchKey(), h.where, h.page, h.pageSize, models.SqlBuilder{{h.orderBy, h.order}}, h.join, h.postType, h.status)
|
||||||
|
} else {
|
||||||
|
postIds, totalRaw, err = common.PostLists(c, h.getSearchKey(), h.where, h.page, h.pageSize, models.SqlBuilder{{h.orderBy, h.order}}, h.join, h.postType, h.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
stat := http.StatusOK
|
||||||
|
if err != nil {
|
||||||
|
c.Error(err)
|
||||||
|
stat = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
c.HTML(stat, "posts/index.gohtml", ginH)
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(postIds) < 1 && h.category != "" {
|
||||||
|
h.titleL = "未找到页面"
|
||||||
|
}
|
||||||
|
|
||||||
|
pw := h.session.Get("post_password")
|
||||||
|
plug := plugins.NewPostPlugin(c, h.scene)
|
||||||
|
for i, post := range postIds {
|
||||||
|
common.PasswordProjectTitle(&postIds[i])
|
||||||
|
if post.PostPassword != "" && pw != post.PostPassword {
|
||||||
|
common.PasswdProjectContent(&postIds[i])
|
||||||
|
} else {
|
||||||
|
plugins.ApplyPlugin(plug, &postIds[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, post := range recent {
|
||||||
|
if post.PostPassword != "" && pw != post.PostPassword {
|
||||||
|
common.PasswdProjectContent(&recent[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q := c.Request.URL.Query().Encode()
|
||||||
|
if q != "" {
|
||||||
|
q = fmt.Sprintf("?%s", q)
|
||||||
|
}
|
||||||
|
ginH["posts"] = postIds
|
||||||
|
ginH["totalPage"] = h.getTotalPage(totalRaw)
|
||||||
|
ginH["pagination"] = pagination(h.page, h.totalPage, h.paginationStep, c.Request.URL.Path, q)
|
||||||
|
ginH["title"] = h.getTitle()
|
||||||
|
}
|
||||||
|
|
||||||
|
var complie = regexp.MustCompile(`(/page)/(\d+)`)
|
||||||
|
|
||||||
|
func pagination(currentPage, totalPage, step int, path, query string) (html string) {
|
||||||
|
if totalPage < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pathx := path
|
||||||
|
if !strings.Contains(path, "/page/") {
|
||||||
|
pathx = fmt.Sprintf("%s%s", path, "/page/1")
|
||||||
|
}
|
||||||
|
s := strings.Builder{}
|
||||||
|
if currentPage > totalPage {
|
||||||
|
currentPage = totalPage
|
||||||
|
}
|
||||||
|
r := complie
|
||||||
|
start := currentPage - step
|
||||||
|
end := currentPage + step
|
||||||
|
if start < 1 {
|
||||||
|
start = 1
|
||||||
|
}
|
||||||
|
if currentPage > 1 {
|
||||||
|
pp := ""
|
||||||
|
if currentPage >= 2 {
|
||||||
|
pp = replacePage(r, pathx, currentPage-1)
|
||||||
|
}
|
||||||
|
s.WriteString(fmt.Sprintf(`<a class="prev page-numbers" href="%s%s">上一页</a>`, pp, query))
|
||||||
|
}
|
||||||
|
if currentPage >= step+2 {
|
||||||
|
d := ""
|
||||||
|
if currentPage > step+2 {
|
||||||
|
d = `<span class="page-numbers dots">…</span>`
|
||||||
|
}
|
||||||
|
e := replacePage(r, path, 1)
|
||||||
|
s.WriteString(fmt.Sprintf(`
|
||||||
|
<a class="page-numbers" href="%s%s"><span class="meta-nav screen-reader-text">页 </span>1</a>
|
||||||
|
%s
|
||||||
|
`, e, query, d))
|
||||||
|
}
|
||||||
|
if totalPage < end {
|
||||||
|
end = totalPage
|
||||||
|
}
|
||||||
|
|
||||||
|
for page := start; page <= end; page++ {
|
||||||
|
h := ""
|
||||||
|
if currentPage == page {
|
||||||
|
h = fmt.Sprintf(`
|
||||||
|
<span aria-current="page" class="page-numbers current">
|
||||||
|
<span class="meta-nav screen-reader-text">页 </span>%d</span>
|
||||||
|
`, page)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
d := replacePage(r, pathx, page)
|
||||||
|
h = fmt.Sprintf(`
|
||||||
|
<a class="page-numbers" href="%s%s">
|
||||||
|
<span class="meta-nav screen-reader-text">页 </span>%d</a>
|
||||||
|
`, d, query, page)
|
||||||
|
}
|
||||||
|
s.WriteString(h)
|
||||||
|
|
||||||
|
}
|
||||||
|
if totalPage >= currentPage+step+1 {
|
||||||
|
if totalPage > currentPage+step+1 {
|
||||||
|
s.WriteString(`<span class="page-numbers dots">…</span>`)
|
||||||
|
}
|
||||||
|
dd := replacePage(r, pathx, totalPage)
|
||||||
|
s.WriteString(fmt.Sprintf(`
|
||||||
|
<a class="page-numbers" href="%s%s"><span class="meta-nav screen-reader-text">页 </span>%d</a>`, dd, query, totalPage))
|
||||||
|
}
|
||||||
|
if currentPage < totalPage {
|
||||||
|
dd := replacePage(r, pathx, currentPage+1)
|
||||||
|
s.WriteString(fmt.Sprintf(`<a class="next page-numbers" href="%s%s">下一页</a>`, dd, query))
|
||||||
|
}
|
||||||
|
html = s.String()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func replacePage(r *regexp.Regexp, path string, page int) (src string) {
|
||||||
|
if page == 1 {
|
||||||
|
src = r.ReplaceAllString(path, "")
|
||||||
|
} else {
|
||||||
|
s := fmt.Sprintf("$1/%d", page)
|
||||||
|
src = r.ReplaceAllString(path, s)
|
||||||
|
}
|
||||||
|
src = strings.Replace(src, "//", "/", -1)
|
||||||
|
if src == "" {
|
||||||
|
src = "/"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -1,10 +1,6 @@
|
|||||||
package actions
|
package actions
|
||||||
|
|
||||||
import (
|
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/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -28,13 +24,5 @@ func Login(c *gin.Context) {
|
|||||||
c.Error(err)
|
c.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pass, err := phphelper.NewPasswordHash(8, true).HashPassword(password)
|
|
||||||
if err != nil {
|
|
||||||
c.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cohash := fmt.Sprintf("wp-postpass_%s", str.Md5(wpconfig.GetOption("siteurl")))
|
|
||||||
c.SetCookie(cohash, pass, 24*3600, "/", "", false, false)
|
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, ref)
|
c.Redirect(http.StatusFound, ref)
|
||||||
}
|
}
|
@ -1,293 +0,0 @@
|
|||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/mail"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommentForm struct {
|
|
||||||
CommentPostId uint64 `form:"comment_post_ID" binding:"required" json:"comment_post_ID"`
|
|
||||||
Author string `form:"author" binding:"required" label:"显示名称" json:"author"`
|
|
||||||
Email string `form:"email" binding:"required,email"`
|
|
||||||
Comment string `form:"comment" binding:"required" label:"评论" json:"comment"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostComment(c *gin.Context) {
|
|
||||||
cli := &http.Client{
|
|
||||||
Timeout: time.Second * 3,
|
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data, err := c.GetRawData()
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
c.Writer.WriteHeader(http.StatusConflict)
|
|
||||||
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
var v validator.ValidationErrors
|
|
||||||
if errors.As(err, &v) {
|
|
||||||
e := v.Translate(config.GetZh())
|
|
||||||
for _, v := range e {
|
|
||||||
fmt.Fprintf(c.Writer, fail, v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.Writer.WriteString("评论出错,请联系管理员或稍后再度")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
conf := config.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "获取评论数据错误")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
|
|
||||||
var comment CommentForm
|
|
||||||
if err = c.ShouldBind(&comment); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
|
|
||||||
req, err := http.NewRequest("POST", conf.PostCommentUrl, strings.NewReader(c.Request.PostForm.Encode()))
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "创建评论请求错误")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer req.Body.Close()
|
|
||||||
req.Header = c.Request.Header.Clone()
|
|
||||||
home, err := url.Parse(wpconfig.GetOption("siteurl"))
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "解析评论接口错误")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
req.Host = home.Host
|
|
||||||
res, err := cli.Do(req)
|
|
||||||
if err != nil && !errors.Is(err, http.ErrUseLastResponse) {
|
|
||||||
logs.Error(err, "请求评论接口错误")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if res.StatusCode == http.StatusFound {
|
|
||||||
for _, cookie := range res.Cookies() {
|
|
||||||
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
|
|
||||||
}
|
|
||||||
u := res.Header.Get("Location")
|
|
||||||
up, er := url.Parse(u)
|
|
||||||
if er != nil {
|
|
||||||
err = er
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cu, er := url.Parse(conf.PostCommentUrl)
|
|
||||||
if er != nil {
|
|
||||||
err = er
|
|
||||||
return
|
|
||||||
}
|
|
||||||
up.Host = cu.Host
|
|
||||||
up.Scheme = "http"
|
|
||||||
newReq, _ := http.NewRequest("GET", up.String(), nil)
|
|
||||||
newReq.Host = home.Host
|
|
||||||
newReq.Header.Set("Cookie", strings.Join(slice.Map(c.Request.Cookies(), func(t *http.Cookie) string {
|
|
||||||
return fmt.Sprintf("%s=%s", t.Name, t.Value)
|
|
||||||
}), "; "))
|
|
||||||
ress, er := http.DefaultClient.Do(newReq)
|
|
||||||
if er != nil {
|
|
||||||
err = er
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cc := c.Copy()
|
|
||||||
go func() {
|
|
||||||
if gin.Mode() != gin.ReleaseMode {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id := comment.CommentPostId
|
|
||||||
if id <= 0 {
|
|
||||||
logs.Error(errors.New("获取文档id错误"), "", comment.CommentPostId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
post, err := cache.GetPostById(cc, id)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "获取文档错误", id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
su := fmt.Sprintf("%s: %s[%s]发表了评论对文档[%v]的评论", wpconfig.GetOption("siteurl"), comment.Author, comment.Email, post.PostTitle)
|
|
||||||
err = mail.SendMail([]string{conf.Mail.User}, su, comment.Comment)
|
|
||||||
logs.IfError(err, "发送邮件", conf.Mail.User, su, comment)
|
|
||||||
}()
|
|
||||||
|
|
||||||
s, er := io.ReadAll(ress.Body)
|
|
||||||
if er != nil {
|
|
||||||
err = er
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uuu := ""
|
|
||||||
uu, _ := url.Parse(res.Header.Get("Location"))
|
|
||||||
if up.RawQuery != "" {
|
|
||||||
cache.NewCommentCache().Set(c, up.RawQuery, string(s))
|
|
||||||
uuu = str.Join(uu.Path, "?", uu.RawQuery)
|
|
||||||
} else {
|
|
||||||
uuu = str.Join(uu.Path)
|
|
||||||
}
|
|
||||||
c.Redirect(http.StatusFound, uuu)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var r io.Reader
|
|
||||||
if res.Header.Get("Content-Encoding") == "gzip" {
|
|
||||||
r, err = gzip.NewReader(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "gzip解压错误")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
r = res.Body
|
|
||||||
}
|
|
||||||
s, err := io.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "读取结果错误")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
c.Writer.WriteHeader(res.StatusCode)
|
|
||||||
_, _ = c.Writer.Write(s)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var fail = `
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
<meta name='robots' content='max-image-preview:large, noindex, follow' />
|
|
||||||
<title>评论提交失败</title>
|
|
||||||
<style type="text/css">
|
|
||||||
html {
|
|
||||||
background: #f1f1f1;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #ccd0d4;
|
|
||||||
color: #444;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
|
||||||
margin: 2em auto;
|
|
||||||
padding: 1em 2em;
|
|
||||||
max-width: 700px;
|
|
||||||
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
|
|
||||||
box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
border-bottom: 1px solid #dadada;
|
|
||||||
clear: both;
|
|
||||||
color: #666;
|
|
||||||
font-size: 24px;
|
|
||||||
margin: 30px 0 0 0;
|
|
||||||
padding: 0;
|
|
||||||
padding-bottom: 7px;
|
|
||||||
}
|
|
||||||
#error-page {
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
#error-page p,
|
|
||||||
#error-page .wp-die-message {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
margin: 25px 0 20px;
|
|
||||||
}
|
|
||||||
#error-page code {
|
|
||||||
font-family: Consolas, Monaco, monospace;
|
|
||||||
}
|
|
||||||
ul li {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 14px ;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #0073aa;
|
|
||||||
}
|
|
||||||
a:hover,
|
|
||||||
a:active {
|
|
||||||
color: #006799;
|
|
||||||
}
|
|
||||||
a:focus {
|
|
||||||
color: #124964;
|
|
||||||
-webkit-box-shadow:
|
|
||||||
0 0 0 1px #5b9dd9,
|
|
||||||
0 0 2px 1px rgba(30, 140, 190, 0.8);
|
|
||||||
box-shadow:
|
|
||||||
0 0 0 1px #5b9dd9,
|
|
||||||
0 0 2px 1px rgba(30, 140, 190, 0.8);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
.button {
|
|
||||||
background: #f3f5f6;
|
|
||||||
border: 1px solid #016087;
|
|
||||||
color: #016087;
|
|
||||||
display: inline-block;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 2;
|
|
||||||
height: 28px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 10px 1px;
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-border-radius: 3px;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
border-radius: 3px;
|
|
||||||
white-space: nowrap;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button.button-large {
|
|
||||||
line-height: 2.30769231;
|
|
||||||
min-height: 32px;
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover,
|
|
||||||
.button:focus {
|
|
||||||
background: #f1f1f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:focus {
|
|
||||||
background: #f3f5f6;
|
|
||||||
border-color: #007cba;
|
|
||||||
-webkit-box-shadow: 0 0 0 1px #007cba;
|
|
||||||
box-shadow: 0 0 0 1px #007cba;
|
|
||||||
color: #016087;
|
|
||||||
outline: 2px solid transparent;
|
|
||||||
outline-offset: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:active {
|
|
||||||
background: #f3f5f6;
|
|
||||||
border-color: #7e8993;
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body id="error-page">
|
|
||||||
<div class="wp-die-message"><p><strong>错误:</strong>%s</p></div>
|
|
||||||
<p><a href='javascript:history.back()'>« 返回</a></p></body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
`
|
|
@ -1,108 +0,0 @@
|
|||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var tmp = "Mon, 02 Jan 2006 15:04:05 GMT"
|
|
||||||
|
|
||||||
func Feed(c *gin.Context) {
|
|
||||||
v, ok := c.GetQuery("feed")
|
|
||||||
if !ok || v == "" {
|
|
||||||
c.Next()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch v {
|
|
||||||
case "rss2":
|
|
||||||
p, ok := c.GetQuery("p")
|
|
||||||
if ok && p != "" {
|
|
||||||
c.AddParam("id", p)
|
|
||||||
PostFeed(c)
|
|
||||||
} else {
|
|
||||||
SiteFeed(c)
|
|
||||||
}
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
case "comments-rss2":
|
|
||||||
CommentsFeed(c)
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isCacheExpired(c *gin.Context, lastTime time.Time) bool {
|
|
||||||
eTag := str.Md5(lastTime.Format(tmp))
|
|
||||||
since := c.Request.Header.Get("If-Modified-Since")
|
|
||||||
cTag := c.Request.Header.Get("If-None-Match")
|
|
||||||
if since != "" && cTag != "" {
|
|
||||||
cGMT, err := time.Parse(tmp, since)
|
|
||||||
if err == nil && lastTime.Unix() <= cGMT.Unix() && eTag == cTag {
|
|
||||||
c.Status(http.StatusNotModified)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func SiteFeed(c *gin.Context) {
|
|
||||||
feed := cache.FeedCache()
|
|
||||||
if !isCacheExpired(c, feed.GetLastSetTime(c)) {
|
|
||||||
c.Status(http.StatusNotModified)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := feed.GetCache(c, time.Second, c)
|
|
||||||
if err != nil {
|
|
||||||
c.Status(http.StatusInternalServerError)
|
|
||||||
c.Abort()
|
|
||||||
c.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setFeed(r[0], c, feed.GetLastSetTime(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
func setFeed(s string, c *gin.Context, t time.Time) {
|
|
||||||
lastTimeGMT := t.Format(tmp)
|
|
||||||
eTag := str.Md5(lastTimeGMT)
|
|
||||||
c.Header("Content-Type", "application/rss+xml; charset=UTF-8")
|
|
||||||
c.Header("Last-Modified", lastTimeGMT)
|
|
||||||
c.Header("ETag", eTag)
|
|
||||||
c.String(http.StatusOK, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostFeed(c *gin.Context) {
|
|
||||||
id := c.Param("id")
|
|
||||||
postFeed := cache.PostFeedCache()
|
|
||||||
if !isCacheExpired(c, postFeed.GetLastSetTime(c, id)) {
|
|
||||||
c.Status(http.StatusNotModified)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s, err := postFeed.GetCache(c, id, time.Second)
|
|
||||||
if err != nil {
|
|
||||||
c.Status(http.StatusInternalServerError)
|
|
||||||
c.Abort()
|
|
||||||
c.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setFeed(s, c, postFeed.GetLastSetTime(c, id))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CommentsFeed(c *gin.Context) {
|
|
||||||
feed := cache.CommentsFeedCache()
|
|
||||||
if !isCacheExpired(c, feed.GetLastSetTime(c)) {
|
|
||||||
c.Status(http.StatusNotModified)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r, err := feed.GetCache(c, time.Second, c)
|
|
||||||
if err != nil {
|
|
||||||
c.Status(http.StatusInternalServerError)
|
|
||||||
c.Abort()
|
|
||||||
c.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setFeed(r[0], c, feed.GetLastSetTime(c))
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package actions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ThemeHook(scene string) func(*gin.Context) {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
t := theme.GetCurrentTheme()
|
|
||||||
h := wp.NewHandle(c, scene, t)
|
|
||||||
theme.Hook(t, h)
|
|
||||||
}
|
|
||||||
}
|
|
111
app/cmd/main.go
111
app/cmd/main.go
@ -1,111 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/ossigns"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/db"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/plugins"
|
|
||||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
|
|
||||||
"github.com/fthvgb1/wp-go/app/route"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"github.com/fthvgb1/wp-go/model"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var confPath string
|
|
||||||
var address string
|
|
||||||
var intReg = regexp.MustCompile(`^\d`)
|
|
||||||
|
|
||||||
func inits() {
|
|
||||||
flag.StringVar(&confPath, "c", "config.yaml", "config file support json,yaml or url")
|
|
||||||
flag.StringVar(&address, "p", "", "listen address and port")
|
|
||||||
flag.Parse()
|
|
||||||
if address == "" && os.Getenv("PORT") == "" {
|
|
||||||
address = "80"
|
|
||||||
}
|
|
||||||
if intReg.MatchString(address) && !strings.Contains(address, ":") {
|
|
||||||
address = ":" + address
|
|
||||||
}
|
|
||||||
err := initConf(confPath)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
cache.InitActionsCommonCache()
|
|
||||||
plugins.InitDigestCache()
|
|
||||||
theme.InitTheme()
|
|
||||||
go cronClearCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
func initConf(c string) (err error) {
|
|
||||||
err = config.InitConfig(c)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = config.InitTrans()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = logs.InitLogger()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
database, err := db.InitDb()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
model.InitDB(db.QueryDb(database))
|
|
||||||
err = wpconfig.InitOptions()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = wpconfig.InitTerms()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wphandle.LoadPlugins()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func cronClearCache() {
|
|
||||||
t := time.NewTicker(config.GetConfig().CacheTime.CrontabClearCacheTime)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
cachemanager.ClearExpired()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
fmt.Println(r)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
inits()
|
|
||||||
ossigns.SetConfPath(confPath)
|
|
||||||
go ossigns.SignalNotify()
|
|
||||||
Gin := route.SetupRouter()
|
|
||||||
c := config.GetConfig()
|
|
||||||
if c.Ssl.Key != "" && c.Ssl.Cert != "" {
|
|
||||||
err := Gin.RunTLS(address, c.Ssl.Cert, c.Ssl.Key)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err := Gin.Run(address)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package mail
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"gopkg.in/gomail.v2"
|
|
||||||
"mime"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AttacheFile struct {
|
|
||||||
Name string
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func SendMail(mailTo []string, subject string, body string, files ...string) error {
|
|
||||||
m := gomail.NewMessage(
|
|
||||||
gomail.SetEncoding(gomail.Base64),
|
|
||||||
)
|
|
||||||
c := config.GetConfig()
|
|
||||||
m.SetHeader("From",
|
|
||||||
m.FormatAddress(c.Mail.User,
|
|
||||||
c.Mail.Alias,
|
|
||||||
))
|
|
||||||
m.SetHeader("To", mailTo...)
|
|
||||||
m.SetHeader("Subject", subject)
|
|
||||||
m.SetBody("text/html", body)
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
_, f := path.Split(file)
|
|
||||||
m.Attach(file,
|
|
||||||
gomail.Rename(f), //重命名
|
|
||||||
gomail.SetHeader(map[string][]string{
|
|
||||||
"Content-Disposition": {
|
|
||||||
fmt.Sprintf(`attachment; filename="%s"`, mime.QEncoding.Encode("UTF-8", f)),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
d := gomail.NewDialer(
|
|
||||||
c.Mail.Host,
|
|
||||||
c.Mail.Port,
|
|
||||||
c.Mail.User,
|
|
||||||
c.Mail.Pass,
|
|
||||||
)
|
|
||||||
if c.Mail.InsecureSkipVerify {
|
|
||||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
|
||||||
}
|
|
||||||
err := d.DialAndSend(m)
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package mail
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSendMail(t *testing.T) {
|
|
||||||
err := config.InitConfig("../config.yaml")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
mailTo []string
|
|
||||||
subject string
|
|
||||||
body string
|
|
||||||
files []string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "t1",
|
|
||||||
args: args{
|
|
||||||
mailTo: []string{"fthvgb1@163.com"},
|
|
||||||
subject: "测试发邮件",
|
|
||||||
body: "测试发邮件",
|
|
||||||
files: []string{"/home/xing/Downloads/favicon.ico"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := SendMail(tt.args.mailTo, tt.args.subject, tt.args.body, tt.args.files...); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("SendMail() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SearchLimit(num int64) func(ctx *gin.Context) {
|
|
||||||
fn, reFn := IpLimit(num)
|
|
||||||
reload.Append(func() {
|
|
||||||
reFn(config.GetConfig().SingleIpSearchNum)
|
|
||||||
}, "search-ip-limit-number")
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
if c.Query("s") != "" {
|
|
||||||
fn(c)
|
|
||||||
} else {
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"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"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RecoverAndSendMail(w io.Writer) func(ctx *gin.Context) {
|
|
||||||
return gin.CustomRecoveryWithWriter(w, func(ctx *gin.Context, err any) {
|
|
||||||
c := ctx.Copy()
|
|
||||||
stack := stack(4)
|
|
||||||
if gin.Mode() == gin.ReleaseMode {
|
|
||||||
go func() {
|
|
||||||
httpRequest, _ := httputil.DumpRequest(c.Request, true)
|
|
||||||
headers := strings.Split(string(httpRequest), "\r\n")
|
|
||||||
for idx, header := range headers {
|
|
||||||
current := strings.Split(header, ":")
|
|
||||||
if current[0] == "Authorization" {
|
|
||||||
headers[idx] = current[0] + ": *"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
headersToStr := strings.Join(headers, "<br/>")
|
|
||||||
content := `<dl><dt>err:</dt><dd>%v</dd><hr/>
|
|
||||||
<dt>stack: </dt><dd>%v</dd><hr/>
|
|
||||||
<dt>headers: </dt><dd>%s</dd></dl>
|
|
||||||
`
|
|
||||||
content = fmt.Sprintf(content,
|
|
||||||
err,
|
|
||||||
formatStack(string(stack)),
|
|
||||||
headersToStr,
|
|
||||||
)
|
|
||||||
|
|
||||||
er := mail.SendMail(
|
|
||||||
[]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.IfError(er, "recover send mail fail", fmt.Sprintf("%v", err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
dunno = []byte("???")
|
|
||||||
centerDot = []byte("·")
|
|
||||||
dot = []byte(".")
|
|
||||||
slash = []byte("/")
|
|
||||||
)
|
|
||||||
|
|
||||||
func formatStack(s string) (r string) {
|
|
||||||
ss := str.NewBuilder()
|
|
||||||
t := strings.Split(s, "\n")
|
|
||||||
for i, line := range t {
|
|
||||||
if i%2 == 0 {
|
|
||||||
ss.WriteString("<dt>", line, "</dt>")
|
|
||||||
} else {
|
|
||||||
ss.WriteString("<dt>", strings.Trim(line, "\t"), "</dt>")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r = ss.String()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func stack(skip int) []byte {
|
|
||||||
buf := new(bytes.Buffer) // the returned data
|
|
||||||
// As we loop, we open files and read them. These variables record the currently
|
|
||||||
// loaded file.
|
|
||||||
var lines [][]byte
|
|
||||||
var lastFile string
|
|
||||||
for i := skip; ; i++ { // Skip the expected number of frames
|
|
||||||
pc, file, line, ok := runtime.Caller(i)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// 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 := os.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lines = bytes.Split(data, []byte{'\n'})
|
|
||||||
lastFile = file
|
|
||||||
}
|
|
||||||
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
|
|
||||||
}
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// function returns, if possible, the name of the function containing the PC.
|
|
||||||
func function(pc uintptr) []byte {
|
|
||||||
fn := runtime.FuncForPC(pc)
|
|
||||||
if fn == nil {
|
|
||||||
return dunno
|
|
||||||
}
|
|
||||||
name := []byte(fn.Name())
|
|
||||||
// The name includes the path name to the package, which is unnecessary
|
|
||||||
// since the file name is already included. Plus, it has center dots.
|
|
||||||
// That is, we see
|
|
||||||
// runtime/debug.*T·ptrmethod
|
|
||||||
// and want
|
|
||||||
// *T.ptrmethod
|
|
||||||
// Also the package path might contain dot (e.g. code.google.com/...),
|
|
||||||
// so first eliminate the path prefix
|
|
||||||
if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
|
|
||||||
name = name[lastSlash+1:]
|
|
||||||
}
|
|
||||||
if period := bytes.Index(name, dot); period >= 0 {
|
|
||||||
name = name[period+1:]
|
|
||||||
}
|
|
||||||
name = bytes.Replace(name, centerDot, dot, -1)
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// source returns a space-trimmed slice of the n'th line.
|
|
||||||
func source(lines [][]byte, n int) []byte {
|
|
||||||
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
|
||||||
if n < 0 || n >= len(lines) {
|
|
||||||
return dunno
|
|
||||||
}
|
|
||||||
return bytes.TrimSpace(lines[n])
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var path = map[string]struct{}{
|
|
||||||
"wp-includes": {},
|
|
||||||
"wp-content": {},
|
|
||||||
"favicon.ico": {},
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetStaticFileCache(c *gin.Context) {
|
|
||||||
f := strings.Split(strings.TrimLeft(c.FullPath(), "/"), "/")
|
|
||||||
if _, ok := path[f[0]]; ok {
|
|
||||||
if ".php" == filepath.Ext(c.Request.URL.Path) {
|
|
||||||
c.Abort()
|
|
||||||
c.Status(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t := config.GetConfig().CacheTime.CacheControl
|
|
||||||
if t > 0 {
|
|
||||||
c.Header("Cache-Control", fmt.Sprintf("private, max-age=%d", int(t.Seconds())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/maps"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ValidateServerNames() func(ctx *gin.Context) {
|
|
||||||
sites := reload.VarsBy(func() map[string]struct{} {
|
|
||||||
r := config.GetConfig().TrustServerNames
|
|
||||||
m := map[string]struct{}{}
|
|
||||||
if len(r) > 0 {
|
|
||||||
for _, name := range r {
|
|
||||||
m[name] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}, "site-names")
|
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
m := sites.Load()
|
|
||||||
if len(m) > 0 && !maps.IsExists(m, strings.Split(c.Request.Host, ":")[0]) {
|
|
||||||
c.Status(http.StatusForbidden)
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
package ossigns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/mail"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/db"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
"github.com/fthvgb1/wp-go/signs"
|
|
||||||
"log"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
var confPath string
|
|
||||||
|
|
||||||
func SetConfPath(path string) {
|
|
||||||
confPath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func FlushCache() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
err := mail.SendMail([]string{config.GetConfig().Mail.User}, "清空缓存失败", fmt.Sprintf("err:[%s]", r))
|
|
||||||
logs.IfError(err, "发邮件失败")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
cachemanager.Flush()
|
|
||||||
log.Println("all cache flushed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Reloads() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
log.Println(r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err := config.InitConfig(confPath)
|
|
||||||
logs.IfError(err, "获取配置文件失败", confPath)
|
|
||||||
err = logs.InitLogger()
|
|
||||||
logs.IfError(err, "日志配置错误")
|
|
||||||
_, err = db.InitDb()
|
|
||||||
logs.IfError(err, "重新读取db失败", config.GetConfig().Mysql)
|
|
||||||
err = wpconfig.InitOptions()
|
|
||||||
logs.IfError(err, "获取网站设置WpOption失败")
|
|
||||||
err = wpconfig.InitTerms()
|
|
||||||
logs.IfError(err, "获取WpTerms表失败")
|
|
||||||
wphandle.LoadPlugins()
|
|
||||||
reload.Reloads("themeArgAndConfig")
|
|
||||||
FlushCache()
|
|
||||||
log.Println("reload complete")
|
|
||||||
}
|
|
||||||
|
|
||||||
func SignalNotify() {
|
|
||||||
rel := func() bool {
|
|
||||||
go Reloads()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
flu := func() bool {
|
|
||||||
go FlushCache()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
signs.Install(syscall.SIGUSR1, rel, "reload")
|
|
||||||
signs.Install(syscall.SIGUSR2, flu, "flush")
|
|
||||||
signs.Wait()
|
|
||||||
}
|
|
@ -1,251 +0,0 @@
|
|||||||
package phphelper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"fmt"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PasswordHash 目前还不完善,只有 Encode64 getRandomBytes CryptPrivate 方法能用
|
|
||||||
type PasswordHash struct {
|
|
||||||
itoa64 string
|
|
||||||
iterationCountLog2 int
|
|
||||||
portableHashes bool
|
|
||||||
randomState string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPasswordHash(iterationCountLog2 int, portableHashes bool) *PasswordHash {
|
|
||||||
if iterationCountLog2 < 4 || iterationCountLog2 > 31 {
|
|
||||||
iterationCountLog2 = 8
|
|
||||||
}
|
|
||||||
return &PasswordHash{
|
|
||||||
iterationCountLog2: iterationCountLog2,
|
|
||||||
portableHashes: portableHashes,
|
|
||||||
itoa64: "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
||||||
randomState: strconv.Itoa(os.Getgid()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PasswordHash) getRandomBytes(count int) (r string, err error) {
|
|
||||||
urand := "/dev/urandom"
|
|
||||||
f, err := os.OpenFile(urand, os.O_RDWR, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
buf := make([]byte, count)
|
|
||||||
_, err = f.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
r = string(buf)
|
|
||||||
if len(buf) < count {
|
|
||||||
r = ""
|
|
||||||
for i := 0; i < count; i = i + 16 {
|
|
||||||
p.randomState = str.Md5(fmt.Sprintf("%d%s", time.Now().UnixMilli(), p.randomState))
|
|
||||||
|
|
||||||
n, err := md5Raw(p.randomState)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
r = fmt.Sprintf("%s%s", r, n)
|
|
||||||
}
|
|
||||||
r = r[0:count]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PasswordHash) Encode64(input string, count int) (out string) {
|
|
||||||
i := 0
|
|
||||||
s := strings.Builder{}
|
|
||||||
for i < count {
|
|
||||||
v := 0
|
|
||||||
if i < len(input) {
|
|
||||||
v = int(input[i])
|
|
||||||
}
|
|
||||||
s.WriteString(string(p.itoa64[v&0x3f]))
|
|
||||||
i++
|
|
||||||
if i < count {
|
|
||||||
if i < len(input) {
|
|
||||||
v |= int(input[i]) << 8
|
|
||||||
} else {
|
|
||||||
v = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.WriteString(string(p.itoa64[(v>>6)&0x3f]))
|
|
||||||
if i >= count {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
if i < count {
|
|
||||||
if i < len(input) {
|
|
||||||
v |= int(input[i]) << 16
|
|
||||||
} else {
|
|
||||||
v = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.WriteString(string(p.itoa64[(v>>12)&0x3f]))
|
|
||||||
if i >= count {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
s.WriteString(string(p.itoa64[(v>>18)&0x3f]))
|
|
||||||
}
|
|
||||||
out = s.String()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PasswordHash) CryptPrivate(password, set string) (rr string, err error) {
|
|
||||||
rr = "*0"
|
|
||||||
r := []rune(rr)
|
|
||||||
setting := []rune(set)
|
|
||||||
if string(r) == string(setting[0:2]) {
|
|
||||||
rr = "*1"
|
|
||||||
}
|
|
||||||
id := setting[0:3]
|
|
||||||
idx := string(id)
|
|
||||||
if idx != "$P$" && idx != "$H$" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log2 := strings.Index(p.itoa64, string(setting[3]))
|
|
||||||
if log2 < 7 || log2 > 30 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
count := 1 << log2
|
|
||||||
l := 12
|
|
||||||
if len(setting) < 12 {
|
|
||||||
l = len(setting)
|
|
||||||
}
|
|
||||||
salt := setting[4:l]
|
|
||||||
if len(salt) != 8 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hash, err := md5Raw(fmt.Sprintf("%s%s", string(salt), password))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
hash, err = md5Raw(fmt.Sprintf("%s%s", hash, password))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rr = string(setting[0:l])
|
|
||||||
rr = fmt.Sprintf("%s%s", rr, p.Encode64(hash, 16))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PasswordHash) genSaltBlowFish(input string) (out string, err error) {
|
|
||||||
itoa64 := "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
|
||||||
s := strings.Builder{}
|
|
||||||
s.WriteString("$2a$")
|
|
||||||
s.WriteString(fmt.Sprintf("%c", '0'+p.iterationCountLog2/10))
|
|
||||||
s.WriteString(fmt.Sprintf("%c", '0'+p.iterationCountLog2%10))
|
|
||||||
s.WriteString("$")
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
c1 := int(input[i])
|
|
||||||
i++
|
|
||||||
s.WriteString(string(itoa64[c1>>2]))
|
|
||||||
c1 = (c1 & 0x03) << 4
|
|
||||||
if i >= 16 {
|
|
||||||
s.WriteString(string(itoa64[c1]))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
c2 := int(input[i])
|
|
||||||
i++
|
|
||||||
c1 |= c2 >> 4
|
|
||||||
s.WriteString(string(input[c1]))
|
|
||||||
c1 = (c2 & 0x0f) << 2
|
|
||||||
c2 = int(input[i])
|
|
||||||
i++
|
|
||||||
c1 |= c2 >> 6
|
|
||||||
s.WriteString(string(itoa64[c1]))
|
|
||||||
s.WriteString(string(itoa64[c2]))
|
|
||||||
}
|
|
||||||
out = s.String()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PasswordHash) HashPassword(pass string) (r string, err error) {
|
|
||||||
if utf8.RuneCountInString(pass) > 4096 {
|
|
||||||
r = "*"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
random := ""
|
|
||||||
hash := ""
|
|
||||||
if !p.portableHashes {
|
|
||||||
random, err = p.getRandomBytes(16)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h, er := bcrypt.GenerateFromPassword([]byte(pass), 16)
|
|
||||||
if er != nil {
|
|
||||||
err = er
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hash = string(h)
|
|
||||||
if len(hash) == 60 {
|
|
||||||
r = hash
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(random) < 6 {
|
|
||||||
random, err = p.getRandomBytes(6)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
salt := p.genSaltPrivate(random)
|
|
||||||
hash, err = p.CryptPrivate(pass, salt)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(hash) == 34 {
|
|
||||||
r = hash
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r = "*"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PasswordHash) CheckPassword(pass, hash string) bool {
|
|
||||||
if utf8.RuneCountInString(pass) > 4096 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(pass))
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PasswordHash) genSaltPrivate(input string) string {
|
|
||||||
s := strings.Builder{}
|
|
||||||
s.WriteString("$P$")
|
|
||||||
min := 30
|
|
||||||
if p.iterationCountLog2+5 < 30 {
|
|
||||||
min = p.iterationCountLog2 + 5
|
|
||||||
}
|
|
||||||
s.WriteString(string(p.itoa64[min]))
|
|
||||||
s.WriteString(p.Encode64(input, 6))
|
|
||||||
return s.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func md5Raw(s string) (string, error) {
|
|
||||||
h := md5.New()
|
|
||||||
_, err := io.WriteString(h, s)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(h.Sum(nil)), err
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package phphelper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/elliotchance/phpserialize"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnPHPSerializeToStruct 使用 json tag
|
|
||||||
func UnPHPSerializeToStruct[T any](s string) (r T, err error) {
|
|
||||||
var rr map[any]any
|
|
||||||
err = phpserialize.Unmarshal([]byte(s), &rr)
|
|
||||||
if err == nil {
|
|
||||||
rx := maps.AnyAnyToStrAny(rr)
|
|
||||||
r, err = maps.StrAnyMapToStruct[T](rx)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnPHPSerializeToStrAnyMap(s string) (map[string]any, error) {
|
|
||||||
m := map[string]any{}
|
|
||||||
var r map[any]any
|
|
||||||
err := phpserialize.Unmarshal([]byte(s), &r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m = maps.AnyAnyToStrAny(r)
|
|
||||||
return m, err
|
|
||||||
}
|
|
||||||
func UnPHPSerializeToAnyAnyMap(s string) (map[any]any, error) {
|
|
||||||
var r map[any]any
|
|
||||||
err := phpserialize.Unmarshal([]byte(s), &r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return r, err
|
|
||||||
}
|
|
126
app/pkg/cache/cache.go
vendored
126
app/pkg/cache/cache.go
vendored
@ -1,126 +0,0 @@
|
|||||||
package cache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/dao"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitActionsCommonCache() {
|
|
||||||
c := config.GetConfig()
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.SearchPostCacheTime, "searchPostIds", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.SearchPostCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.PostListCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(nil, dao.MonthPost, c.CacheTime.MonthPostCacheTime, "monthPostIds", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.MonthPostCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(nil, dao.GetPostContext, c.CacheTime.ContextPostCacheTime, "postContext", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.ContextPostCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(dao.GetPostsByIds, nil, c.CacheTime.PostDataCacheTime, "postData", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.PostDataCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(dao.GetPostMetaByPostIds, nil, c.CacheTime.PostDataCacheTime, "postMetaData", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.PostDataCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(nil, dao.CategoriesAndTags, c.CacheTime.CategoryCacheTime, "categoryAndTagsData", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.CategoryCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewVarMemoryCache(dao.RecentPosts, c.CacheTime.RecentPostCacheTime, "recentPosts", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.RecentPostCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewVarMemoryCache(RecentComment, c.CacheTime.RecentCommentsCacheTime, "recentComments", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.RecentCommentsCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(nil, dao.CommentNum, 30*time.Second, "commentNumber", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(nil, PostTopComments, 30*time.Second, "PostCommentsIds", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
|
||||||
})
|
|
||||||
cachemanager.NewMemoryMapCache(nil, dao.PostTopCommentNum, 30*time.Second, "postTopCommentsNum", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(dao.GetCommentByIds, nil, time.Hour, "postCommentData", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.CommentsCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(dao.CommentChildren, nil, time.Minute, "commentChildren", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewVarMemoryCache(dao.GetMaxPostId, c.CacheTime.MaxPostIdCacheTime, "maxPostId", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.MaxPostIdCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(nil, dao.GetUserById, c.CacheTime.UserInfoCacheTime, "userData", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.UserInfoCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(nil, dao.GetUserByName, c.CacheTime.UserInfoCacheTime, "usernameToUserData", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.UserInfoCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewVarMemoryCache(dao.AllUsername, c.CacheTime.UserInfoCacheTime, "allUsername", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.UserInfoCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
cachemanager.NewVarMemoryCache(SiteFeed, time.Hour, "siteFeed")
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache(nil, PostFeed, time.Hour, "postFeed")
|
|
||||||
|
|
||||||
cachemanager.NewVarMemoryCache(CommentsFeed, time.Hour, "commentsFeed")
|
|
||||||
|
|
||||||
cachemanager.NewMemoryMapCache[string, string](nil, nil, 15*time.Minute, "NewComment")
|
|
||||||
|
|
||||||
InitFeed()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Arch struct {
|
|
||||||
data []models.PostArchive
|
|
||||||
fn func(context.Context) ([]models.PostArchive, error)
|
|
||||||
month time.Month
|
|
||||||
}
|
|
||||||
|
|
||||||
var arch = reload.Vars(Arch{
|
|
||||||
fn: dao.Archives,
|
|
||||||
}, "archives-year-month-data")
|
|
||||||
|
|
||||||
func Archives(ctx context.Context) []models.PostArchive {
|
|
||||||
a := arch.Load()
|
|
||||||
data := a.data
|
|
||||||
l := len(data)
|
|
||||||
m := time.Now().Month()
|
|
||||||
if l < 1 || a.month != m {
|
|
||||||
r, err := a.fn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "set cache Archives fail")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
a.month = m
|
|
||||||
a.data = r
|
|
||||||
arch.Store(a)
|
|
||||||
data = r
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
39
app/pkg/cache/categoryandtag.go
vendored
39
app/pkg/cache/categoryandtag.go
vendored
@ -1,39 +0,0 @@
|
|||||||
package cache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CategoriesTags get all categories or tags
|
|
||||||
//
|
|
||||||
// query func see dao.CategoriesAndTags
|
|
||||||
//
|
|
||||||
// t is constraints.Tag or constraints.Category
|
|
||||||
func CategoriesTags(ctx context.Context, t ...string) []models.TermsMy {
|
|
||||||
tt := ""
|
|
||||||
if len(t) > 0 {
|
|
||||||
tt = t[0]
|
|
||||||
}
|
|
||||||
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
|
|
||||||
logs.IfError(err, "get category fail")
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
func AllCategoryTagsNames(ctx context.Context, t ...string) map[string]struct{} {
|
|
||||||
tt := ""
|
|
||||||
if len(t) > 0 {
|
|
||||||
tt = t[0]
|
|
||||||
}
|
|
||||||
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "get category fail")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return slice.ToMap(r, func(t models.TermsMy) (string, struct{}) {
|
|
||||||
return t.Name, struct{}{}
|
|
||||||
}, true)
|
|
||||||
}
|
|
124
app/pkg/cache/comments.go
vendored
124
app/pkg/cache/comments.go
vendored
@ -1,124 +0,0 @@
|
|||||||
package cache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/dao"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/cache"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"github.com/fthvgb1/wp-go/helper"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/number"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RecentComments query func see RecentComment
|
|
||||||
func RecentComments(ctx context.Context, n int) (r []models.Comments) {
|
|
||||||
nn := number.Max(n, 10)
|
|
||||||
r, err := cachemanager.GetVarVal[[]models.Comments]("recentComments", ctx, time.Second, ctx, nn)
|
|
||||||
if len(r) > n {
|
|
||||||
r = r[0:n]
|
|
||||||
}
|
|
||||||
logs.IfError(err, "get recent comment fail")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostTopLevelCommentIds query func see PostTopComments
|
|
||||||
func PostTopLevelCommentIds(ctx context.Context, postId uint64, page, limit, total int, order string, a ...any) ([]uint64, error) {
|
|
||||||
var key string
|
|
||||||
if len(a) > 0 {
|
|
||||||
key = helper.ParseArgs("", a...)
|
|
||||||
}
|
|
||||||
if key == "" {
|
|
||||||
key = fmt.Sprintf("%d-%d-%d-%d-%s", postId, page, limit, total, order)
|
|
||||||
}
|
|
||||||
return cachemanager.GetBy[[]uint64]("PostCommentsIds", ctx,
|
|
||||||
key, time.Second, postId, page, limit, 0, order)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCommentById query func see dao.GetCommentByIds
|
|
||||||
func GetCommentById(ctx context.Context, id uint64) (models.Comments, error) {
|
|
||||||
return cachemanager.GetBy[models.Comments]("postCommentData", ctx, id, time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCommentDataByIds query func see dao.GetCommentByIds
|
|
||||||
func GetCommentDataByIds(ctx context.Context, ids []uint64) ([]models.Comments, error) {
|
|
||||||
return cachemanager.GetBatchBy[models.Comments]("postCommentData", ctx, ids, time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCommentCache() *cache.MapCache[string, string] {
|
|
||||||
r, _ := cachemanager.GetMapCache[string, string]("NewComment")
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostTopComments(ctx context.Context, _ string, a ...any) ([]uint64, error) {
|
|
||||||
postId := a[0].(uint64)
|
|
||||||
page := a[1].(int)
|
|
||||||
limit := a[2].(int)
|
|
||||||
total := a[3].(int)
|
|
||||||
order := helper.ParseArgs("", a...)
|
|
||||||
if order == "" {
|
|
||||||
order = wpconfig.GetOption("comment_order")
|
|
||||||
}
|
|
||||||
v, _, err := dao.PostCommentsIds(ctx, postId, page, limit, total, order)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func RecentComment(ctx context.Context, a ...any) (r []models.Comments, err error) {
|
|
||||||
r, err = dao.RecentComments(ctx, a...)
|
|
||||||
if err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
for i, comment := range r {
|
|
||||||
r[i].CommentAuthorUrl, err = GetCommentUrl(ctx, comment.CommentId, comment.CommentPostId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCommentUrl(ctx context.Context, commentId, postId uint64) (string, error) {
|
|
||||||
if wpconfig.GetOption("page_comments") != "1" {
|
|
||||||
return fmt.Sprintf("/p/%d#comment-%d", postId, commentId), nil
|
|
||||||
}
|
|
||||||
commentsPerPage := str.ToInteger(wpconfig.GetOption("comments_per_page"), 5)
|
|
||||||
topCommentId, err := AncestorCommentId(ctx, commentId)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
totalNum, err := cachemanager.GetBy[int]("postTopCommentsNum", ctx, postId, time.Second)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if totalNum <= commentsPerPage {
|
|
||||||
return fmt.Sprintf("/p/%d#comment-%d", postId, commentId), nil
|
|
||||||
}
|
|
||||||
num, err := dao.PreviousCommentNum(ctx, topCommentId, postId)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
order := wpconfig.GetOption("comment_order")
|
|
||||||
page := number.DivideCeil(num+1, commentsPerPage)
|
|
||||||
if order == "desc" {
|
|
||||||
page = number.DivideCeil(totalNum-num, commentsPerPage)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("/p/%d/comment-page-%d/#comment-%d", postId, page, commentId), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func AncestorCommentId(ctx context.Context, commentId uint64) (uint64, error) {
|
|
||||||
comment, err := GetCommentById(ctx, commentId)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if comment.CommentParent == 0 {
|
|
||||||
return comment.CommentId, nil
|
|
||||||
}
|
|
||||||
return AncestorCommentId(ctx, comment.CommentParent)
|
|
||||||
}
|
|
213
app/pkg/cache/feed.go
vendored
213
app/pkg/cache/feed.go
vendored
@ -1,213 +0,0 @@
|
|||||||
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"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/fthvgb1/wp-go/plugin/digest"
|
|
||||||
"github.com/fthvgb1/wp-go/rss2"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var timeFormat = "Mon, 02 Jan 2006 15:04:05 +0000"
|
|
||||||
var templateRss rss2.Rss2
|
|
||||||
|
|
||||||
func InitFeed() {
|
|
||||||
templateRss = rss2.Rss2{
|
|
||||||
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.GetOption("home"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommentsFeedCache query func see CommentsFeed
|
|
||||||
func CommentsFeedCache() *cache.VarCache[[]string] {
|
|
||||||
r, _ := cachemanager.GetVarCache[[]string]("commentsFeed")
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// FeedCache query func see SiteFeed
|
|
||||||
func FeedCache() *cache.VarCache[[]string] {
|
|
||||||
r, _ := cachemanager.GetVarCache[[]string]("siteFeed")
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostFeedCache query func see PostFeed
|
|
||||||
func PostFeedCache() *cache.MapCache[string, string] {
|
|
||||||
r, _ := cachemanager.GetMapCache[string, string]("postFeed")
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
})
|
|
||||||
posts, err := GetPostsByIds(c, ids)
|
|
||||||
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 := "无法提供摘要。这是一篇受保护的文章。"
|
|
||||||
if t.PostPassword != "" {
|
|
||||||
wpposts.PasswordProjectTitle(&t)
|
|
||||||
wpposts.PasswdProjectContent(&t)
|
|
||||||
} else {
|
|
||||||
desc = plugins.Digests(t.PostContent, t.Id, 55, nil)
|
|
||||||
}
|
|
||||||
l := ""
|
|
||||||
if t.CommentStatus == "open" && t.CommentCount > 0 {
|
|
||||||
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", site, t.Id)
|
|
||||||
}
|
|
||||||
user := GetUserById(c, t.PostAuthor)
|
|
||||||
|
|
||||||
return rss2.Item{
|
|
||||||
Title: t.PostTitle,
|
|
||||||
Creator: user.DisplayName,
|
|
||||||
Guid: t.Guid,
|
|
||||||
SlashComments: int(t.CommentCount),
|
|
||||||
Content: t.PostContent,
|
|
||||||
Category: strings.Join(t.Categories, "、"),
|
|
||||||
CommentLink: l,
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
xml = []string{rs.GetXML()}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostFeed(c context.Context, id string, _ ...any) (x string, err error) {
|
|
||||||
ID := str.ToInteger[uint64](id, 0)
|
|
||||||
maxId, err := GetMaxPostId(c)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
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", 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 {
|
|
||||||
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%s", site, u),
|
|
||||||
Creator: t.CommentAuthor,
|
|
||||||
PubDate: t.CommentDateGmt.Format(timeFormat),
|
|
||||||
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
|
|
||||||
Description: "评论受保护:要查看请输入密码。",
|
|
||||||
Content: post.PostContent,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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%s", site, u),
|
|
||||||
Creator: t.CommentAuthor,
|
|
||||||
PubDate: t.CommentDateGmt.Format(timeFormat),
|
|
||||||
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
|
|
||||||
Content: t.CommentContent,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
x = rs.GetXML()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func CommentsFeed(c context.Context, _ ...any) (r []string, err error) {
|
|
||||||
commens := RecentComments(c, 10)
|
|
||||||
rs := templateRss
|
|
||||||
rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.GetOption("blogname"))
|
|
||||||
rs.LastBuildDate = time.Now().Format(timeFormat)
|
|
||||||
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 {
|
|
||||||
return []string{}, err
|
|
||||||
}
|
|
||||||
rs.Items = slice.Map(com, func(t models.Comments) rss2.Item {
|
|
||||||
post, _ := GetPostById(c, t.CommentPostId)
|
|
||||||
desc := "评论受保护:要查看请输入密码。"
|
|
||||||
content := t.CommentContent
|
|
||||||
if post.PostPassword != "" {
|
|
||||||
wpposts.PasswordProjectTitle(&post)
|
|
||||||
wpposts.PasswdProjectContent(&post)
|
|
||||||
content = post.PostContent
|
|
||||||
} else {
|
|
||||||
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: u,
|
|
||||||
Creator: t.CommentAuthor,
|
|
||||||
Description: desc,
|
|
||||||
PubDate: t.CommentDateGmt.Format(timeFormat),
|
|
||||||
Guid: fmt.Sprintf("%s#commment-%d", post.Guid, t.CommentId),
|
|
||||||
Content: content,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
r = []string{rs.GetXML()}
|
|
||||||
return
|
|
||||||
}
|
|
17
app/pkg/cache/postmeta.go
vendored
17
app/pkg/cache/postmeta.go
vendored
@ -1,17 +0,0 @@
|
|||||||
package cache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetPostMetaByPostIds query func see dao.GetPostMetaByPostIds
|
|
||||||
func GetPostMetaByPostIds(ctx context.Context, ids []uint64) ([]map[string]any, error) {
|
|
||||||
return cachemanager.GetBatchBy[map[string]any]("postMetaData", ctx, ids, time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPostMetaByPostId query func see dao.GetPostMetaByPostIds
|
|
||||||
func GetPostMetaByPostId(ctx context.Context, id uint64) (map[string]any, error) {
|
|
||||||
return cachemanager.GetBy[map[string]any]("postMetaData", ctx, id, time.Second)
|
|
||||||
}
|
|
91
app/pkg/cache/posts.go
vendored
91
app/pkg/cache/posts.go
vendored
@ -1,91 +0,0 @@
|
|||||||
package cache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/dao"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/number"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetPostById query func see dao.GetPostsByIds
|
|
||||||
func GetPostById(ctx context.Context, id uint64) (models.Posts, error) {
|
|
||||||
return cachemanager.GetBy[models.Posts]("postData", ctx, id, time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPostsByIds query func see dao.GetPostsByIds
|
|
||||||
func GetPostsByIds(ctx context.Context, ids []uint64) ([]models.Posts, error) {
|
|
||||||
return cachemanager.GetBatchBy[models.Posts]("postData", ctx, ids, time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SearchPost query func see dao.SearchPostIds
|
|
||||||
func SearchPost(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
|
|
||||||
ids, err := cachemanager.GetBy[dao.PostIds]("searchPostIds", ctx, key, time.Second, args...)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
total = ids.Length
|
|
||||||
r, err = GetPostsByIds(ctx, ids.Ids)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostLists query func see dao.SearchPostIds
|
|
||||||
func PostLists(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
|
|
||||||
ids, err := cachemanager.GetBy[dao.PostIds]("listPostIds", ctx, key, time.Second, args...)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
total = ids.Length
|
|
||||||
r, err = GetPostsByIds(ctx, ids.Ids)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMaxPostId query func see dao.GetMaxPostId
|
|
||||||
func GetMaxPostId(ctx context.Context) (uint64, error) {
|
|
||||||
return cachemanager.GetVarVal[uint64]("maxPostId", ctx, time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecentPosts query func see dao.RecentPosts
|
|
||||||
func RecentPosts(ctx context.Context, n int) (r []models.Posts) {
|
|
||||||
nn := n
|
|
||||||
feedNum := str.ToInteger(wpconfig.GetOption("posts_per_rss"), 10)
|
|
||||||
nn = number.Max(n, feedNum)
|
|
||||||
r, err := cachemanager.GetVarVal[[]models.Posts]("recentPosts", ctx, time.Second, nn)
|
|
||||||
if n < len(r) {
|
|
||||||
r = r[:n]
|
|
||||||
}
|
|
||||||
logs.IfError(err, "get recent post")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContextPost query func see dao.GetPostContext
|
|
||||||
func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next models.Posts, err error) {
|
|
||||||
postCtx, err := cachemanager.GetBy[dao.PostContext]("postContext", ctx, id, time.Second, date)
|
|
||||||
if err != nil {
|
|
||||||
return models.Posts{}, models.Posts{}, err
|
|
||||||
}
|
|
||||||
prev = postCtx.Prev
|
|
||||||
next = postCtx.Next
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMonthPostIds query func see dao.MonthPost
|
|
||||||
func GetMonthPostIds(ctx context.Context, year, month string, page, limit int, order string) (r []models.Posts, total int, err error) {
|
|
||||||
res, err := cachemanager.GetBy[[]uint64]("monthPostIds", ctx, fmt.Sprintf("%s%s", year, month), time.Second, year, month)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if order == "desc" {
|
|
||||||
res = slice.Reverse(res)
|
|
||||||
}
|
|
||||||
total = len(res)
|
|
||||||
rr := slice.Pagination(res, page, limit)
|
|
||||||
r, err = GetPostsByIds(ctx, rr)
|
|
||||||
return
|
|
||||||
}
|
|
26
app/pkg/cache/users.go
vendored
26
app/pkg/cache/users.go
vendored
@ -1,26 +0,0 @@
|
|||||||
package cache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetUserByName query func see dao.GetUserByName
|
|
||||||
func GetUserByName(ctx context.Context, username string) (models.Users, error) {
|
|
||||||
return cachemanager.GetBy[models.Users]("usernameToUserData", ctx, username, time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllUsername query func see dao.AllUsername
|
|
||||||
func GetAllUsername(ctx context.Context) (map[string]uint64, error) {
|
|
||||||
return cachemanager.GetVarVal[map[string]uint64]("allUsername", ctx, time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserById query func see dao.GetUserById
|
|
||||||
func GetUserById(ctx context.Context, uid uint64) models.Users {
|
|
||||||
r, err := cachemanager.GetBy[models.Users]("userData", ctx, uid, time.Second)
|
|
||||||
logs.IfError(err, "get user", uid)
|
|
||||||
return r
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/safety"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var config safety.Var[Config]
|
|
||||||
|
|
||||||
func GetConfig() Config {
|
|
||||||
return config.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Ssl Ssl `yaml:"ssl" json:"ssl"`
|
|
||||||
Mysql Mysql `yaml:"mysql" json:"mysql"`
|
|
||||||
Mail Mail `yaml:"mail" json:"mail"`
|
|
||||||
CacheTime CacheTime `yaml:"cacheTime" json:"cacheTime"`
|
|
||||||
PluginPath string `yaml:"pluginPath" json:"pluginPath"`
|
|
||||||
ExternScript []string `json:"externScript" yaml:"externScript"`
|
|
||||||
DigestWordCount int `yaml:"digestWordCount" json:"digestWordCount,omitempty"`
|
|
||||||
DigestAllowTag string `yaml:"digestAllowTag" json:"digestAllowTag"`
|
|
||||||
MaxRequestSleepNum int64 `yaml:"maxRequestSleepNum" json:"maxRequestSleepNum,omitempty"`
|
|
||||||
MaxRequestNum int64 `yaml:"maxRequestNum" json:"maxRequestNum,omitempty"`
|
|
||||||
SingleIpSearchNum int64 `yaml:"singleIpSearchNum" json:"singleIpSearchNum,omitempty"`
|
|
||||||
Gzip bool `yaml:"gzip" json:"gzip,omitempty"`
|
|
||||||
PostCommentUrl string `yaml:"postCommentUrl" json:"postCommentUrl,omitempty"`
|
|
||||||
TrustIps []string `yaml:"trustIps" json:"trustIps,omitempty"`
|
|
||||||
TrustServerNames []string `yaml:"trustServerNames" json:"trustServerNames,omitempty"`
|
|
||||||
Theme string `yaml:"theme" json:"theme,omitempty"`
|
|
||||||
PostOrder string `yaml:"postOrder" json:"postOrder,omitempty"`
|
|
||||||
UploadDir string `yaml:"uploadDir" json:"uploadDir,omitempty"`
|
|
||||||
Pprof string `yaml:"pprof" json:"pprof,omitempty"`
|
|
||||||
ListPagePlugins []string `yaml:"listPagePlugins" json:"listPagePlugins,omitempty"`
|
|
||||||
PaginationStep int `yaml:"paginationStep" json:"paginationStep,omitempty"`
|
|
||||||
ShowQuerySql bool `yaml:"showQuerySql" json:"showQuerySql,omitempty"`
|
|
||||||
Plugins []string `yaml:"plugins" json:"plugins,omitempty"`
|
|
||||||
LogOutput string `yaml:"logOutput" json:"logOutput,omitempty"`
|
|
||||||
WpDir string `yaml:"wpDir" json:"wpDir"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CacheTime struct {
|
|
||||||
CacheControl time.Duration `yaml:"cacheControl" json:"cacheControl,omitempty"`
|
|
||||||
RecentPostCacheTime time.Duration `yaml:"recentPostCacheTime" json:"recentPostCacheTime,omitempty"`
|
|
||||||
CategoryCacheTime time.Duration `yaml:"categoryCacheTime" json:"categoryCacheTime,omitempty"`
|
|
||||||
ArchiveCacheTime time.Duration `yaml:"archiveCacheTime" json:"archiveCacheTime,omitempty"`
|
|
||||||
ContextPostCacheTime time.Duration `yaml:"contextPostCacheTime" json:"contextPostCacheTime,omitempty"`
|
|
||||||
RecentCommentsCacheTime time.Duration `yaml:"recentCommentsCacheTime" json:"recentCommentsCacheTime,omitempty"`
|
|
||||||
DigestCacheTime time.Duration `yaml:"digestCacheTime" json:"digestCacheTime,omitempty"`
|
|
||||||
PostListCacheTime time.Duration `yaml:"postListCacheTime" json:"postListCacheTime,omitempty"`
|
|
||||||
SearchPostCacheTime time.Duration `yaml:"searchPostCacheTime" json:"searchPostCacheTime,omitempty"`
|
|
||||||
MonthPostCacheTime time.Duration `yaml:"monthPostCacheTime" json:"monthPostCacheTime,omitempty"`
|
|
||||||
PostDataCacheTime time.Duration `yaml:"postDataCacheTime" json:"postDataCacheTime,omitempty"`
|
|
||||||
PostCommentsCacheTime time.Duration `yaml:"postCommentsCacheTime" json:"postCommentsCacheTime,omitempty"`
|
|
||||||
CrontabClearCacheTime time.Duration `yaml:"crontabClearCacheTime" json:"crontabClearCacheTime,omitempty"`
|
|
||||||
MaxPostIdCacheTime time.Duration `yaml:"maxPostIdCacheTime" json:"maxPostIdCacheTime,omitempty"`
|
|
||||||
UserInfoCacheTime time.Duration `yaml:"userInfoCacheTime" json:"userInfoCacheTime,omitempty"`
|
|
||||||
CommentsCacheTime time.Duration `yaml:"commentsCacheTime" json:"commentsCacheTime,omitempty"`
|
|
||||||
SleepTime []time.Duration `yaml:"sleepTime" json:"sleepTime,omitempty"`
|
|
||||||
CommentsIncreaseUpdateTime time.Duration `yaml:"commentsIncreaseUpdateTime" json:"commentsIncreaseUpdateTime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Ssl struct {
|
|
||||||
Cert string `yaml:"cert" json:"cert,omitempty"`
|
|
||||||
Key string `yaml:"key" json:"key,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mail struct {
|
|
||||||
User string `yaml:"user" json:"user,omitempty"`
|
|
||||||
Alias string `yaml:"alias" json:"alias,omitempty"`
|
|
||||||
Pass string `yaml:"pass" json:"pass,omitempty"`
|
|
||||||
Host string `yaml:"host" json:"host,omitempty"`
|
|
||||||
Port int `yaml:"port" json:"port,omitempty"`
|
|
||||||
InsecureSkipVerify bool `yaml:"insecureSkipVerify" json:"insecureSkipVerify,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mysql struct {
|
|
||||||
Dsn Dsn `yaml:"dsn" json:"dsn"`
|
|
||||||
Pool Pool `yaml:"pool" json:"pool"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCustomizedConfig[T any]() (T, error) {
|
|
||||||
var r T
|
|
||||||
err := yaml.Unmarshal(fileData.Load(), &r)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileData = safety.NewVar([]byte{})
|
|
||||||
|
|
||||||
func InitConfig(conf string) error {
|
|
||||||
if conf == "" {
|
|
||||||
conf = "config.yaml"
|
|
||||||
}
|
|
||||||
var file []byte
|
|
||||||
var err error
|
|
||||||
if strings.Contains(conf, "http") {
|
|
||||||
get, err := http.Get(conf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer get.Body.Close()
|
|
||||||
file, err = io.ReadAll(get.Body)
|
|
||||||
} else {
|
|
||||||
file, err = os.ReadFile(conf)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fileData.Store(file)
|
|
||||||
var c Config
|
|
||||||
err = yaml.Unmarshal(file, &c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.Store(c)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Dsn struct {
|
|
||||||
Host string `yaml:"host" json:"host,omitempty"`
|
|
||||||
Port string `yaml:"port" json:"port,omitempty"`
|
|
||||||
Db string `yaml:"db" json:"db,omitempty"`
|
|
||||||
User string `yaml:"user" json:"user,omitempty"`
|
|
||||||
Password string `yaml:"password" json:"password,omitempty"`
|
|
||||||
Charset string `yaml:"charset" json:"charset,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Dsn) GetDsn() string {
|
|
||||||
if m.Charset == "" {
|
|
||||||
m.Charset = "utf8"
|
|
||||||
}
|
|
||||||
t := "%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local"
|
|
||||||
return fmt.Sprintf(t, m.User, m.Password, m.Host, m.Port, m.Db, m.Charset)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pool struct {
|
|
||||||
ConnMaxIdleTime time.Duration `yaml:"connMaxIdleTime" json:"connMaxIdleTime,omitempty"`
|
|
||||||
MaxOpenConn int `yaml:"maxOpenConn" json:"maxOpenConn,omitempty"`
|
|
||||||
MaxIdleConn int `yaml:"maxIdleConn" json:"maxIdleConn,omitempty"`
|
|
||||||
ConnMaxLifetime time.Duration `yaml:"connMaxLifetime" json:"connMaxLifetime,omitempty"`
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gin-gonic/gin/binding"
|
|
||||||
"github.com/go-playground/locales/en"
|
|
||||||
"github.com/go-playground/locales/zh"
|
|
||||||
ut "github.com/go-playground/universal-translator"
|
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
enTrans "github.com/go-playground/validator/v10/translations/en"
|
|
||||||
zhTrans "github.com/go-playground/validator/v10/translations/zh"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
var enT ut.Translator
|
|
||||||
var zhT ut.Translator
|
|
||||||
|
|
||||||
func GetZh() ut.Translator {
|
|
||||||
return zhT
|
|
||||||
}
|
|
||||||
func GetEn() ut.Translator {
|
|
||||||
return enT
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitTrans() error {
|
|
||||||
if validate, ok := binding.Validator.Engine().(*validator.Validate); ok {
|
|
||||||
ens := en.New()
|
|
||||||
uni := ut.New(ens, zh.New(), ens)
|
|
||||||
zhT, _ = uni.GetTranslator("zh")
|
|
||||||
enT, _ = uni.GetTranslator("en")
|
|
||||||
err := enTrans.RegisterDefaultTranslations(validate, enT)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
|
|
||||||
return field.Tag.Get("label")
|
|
||||||
})
|
|
||||||
err = zhTrans.RegisterDefaultTranslations(validate, zhT)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package blocks
|
|
||||||
|
|
||||||
const (
|
|
||||||
Search = "block-search"
|
|
||||||
RecentPosts = "block-recent-posts"
|
|
||||||
RecentComments = "block-recent-comments"
|
|
||||||
Archive = "block-archives"
|
|
||||||
Categories = "block-categories"
|
|
||||||
Meta = "block-meta"
|
|
||||||
)
|
|
@ -1,30 +0,0 @@
|
|||||||
package constraints
|
|
||||||
|
|
||||||
const (
|
|
||||||
Home = "Home"
|
|
||||||
Archive = "Archive"
|
|
||||||
Category = "Category"
|
|
||||||
Tag = "Tag"
|
|
||||||
Search = "Search"
|
|
||||||
Author = "Author"
|
|
||||||
Detail = "Detail"
|
|
||||||
|
|
||||||
NoRoute = "NoRoute"
|
|
||||||
|
|
||||||
Ok = "Ok"
|
|
||||||
Error404 = "Error404"
|
|
||||||
ParamError = "ParamError"
|
|
||||||
InternalErr = "InternalErr"
|
|
||||||
AllStats = "AllStats"
|
|
||||||
AllScene = "AllScene"
|
|
||||||
|
|
||||||
PipeData = "PipeData"
|
|
||||||
PipeMiddleware = "PipeMiddleware"
|
|
||||||
PipeRender = "PipeRender"
|
|
||||||
|
|
||||||
Defaults = "default"
|
|
||||||
|
|
||||||
HeadScript = "headScript"
|
|
||||||
FooterScript = "footerScript"
|
|
||||||
SidebarsWidgets = "sidebarsWidgets"
|
|
||||||
)
|
|
@ -1,12 +0,0 @@
|
|||||||
package widgets
|
|
||||||
|
|
||||||
const (
|
|
||||||
Search = "widget-search"
|
|
||||||
RecentPosts = "widget-recent-posts"
|
|
||||||
RecentComments = "widget-recent-comments"
|
|
||||||
Archive = "widget-archives"
|
|
||||||
Categories = "widget-categories"
|
|
||||||
Meta = "widget-meta"
|
|
||||||
|
|
||||||
Widget = "widget"
|
|
||||||
)
|
|
@ -1,183 +0,0 @@
|
|||||||
package dao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/helper"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/number"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/fthvgb1/wp-go/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RecentComments
|
|
||||||
// param context.Context
|
|
||||||
func RecentComments(ctx context.Context, a ...any) (r []models.Comments, err error) {
|
|
||||||
n := helper.ParseArgs(10, a...)
|
|
||||||
return model.Finds[models.Comments](ctx, model.Conditions(
|
|
||||||
model.Where(model.SqlBuilder{
|
|
||||||
{"comment_approved", "1"},
|
|
||||||
{"post_status", "publish"},
|
|
||||||
}),
|
|
||||||
model.Fields("comment_ID,comment_author,comment_post_ID,post_title"),
|
|
||||||
model.Order(model.SqlBuilder{{"comment_date_gmt", "desc"}}),
|
|
||||||
model.Join(model.SqlBuilder{{"a", "left join", "wp_posts b", "a.comment_post_ID=b.ID"}}),
|
|
||||||
model.Limit(n),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PostComments
|
|
||||||
// param1 context.Context
|
|
||||||
// param2 postId
|
|
||||||
func PostComments(ctx context.Context, postId uint64, _ ...any) ([]uint64, error) {
|
|
||||||
r, err := model.ChunkFind[models.Comments](ctx, 300, model.Conditions(
|
|
||||||
model.Where(model.SqlBuilder{
|
|
||||||
{"comment_approved", "1"},
|
|
||||||
{"comment_post_ID", "=", number.IntToString(postId), "int"},
|
|
||||||
}),
|
|
||||||
model.Fields("comment_ID"),
|
|
||||||
model.Order(model.SqlBuilder{
|
|
||||||
{"comment_date_gmt", "asc"},
|
|
||||||
{"comment_ID", "asc"},
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return slice.Map(r, func(t models.Comments) uint64 {
|
|
||||||
return t.CommentId
|
|
||||||
}), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCommentByIds(ctx context.Context, ids []uint64, _ ...any) (map[uint64]models.Comments, error) {
|
|
||||||
if len(ids) < 1 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
m := make(map[uint64]models.Comments)
|
|
||||||
off := 0
|
|
||||||
for {
|
|
||||||
id := slice.Slice(ids, off, 500)
|
|
||||||
if len(id) < 1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r, err := model.Finds[models.Comments](ctx, model.Conditions(
|
|
||||||
model.Where(model.SqlBuilder{
|
|
||||||
{"comment_ID", "in", ""}, {"comment_approved", "1"},
|
|
||||||
}),
|
|
||||||
model.Fields("*"),
|
|
||||||
model.In(slice.ToAnySlice(id)),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return m, err
|
|
||||||
}
|
|
||||||
for _, comments := range r {
|
|
||||||
m[comments.CommentId] = comments
|
|
||||||
}
|
|
||||||
off += 500
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CommentNum(ctx context.Context, postId uint64, _ ...any) (int, error) {
|
|
||||||
n, err := model.GetField[models.Posts](ctx, "comment_count", model.Conditions(
|
|
||||||
model.Where(model.SqlBuilder{{"ID", "=", number.IntToString(postId), "int"}})))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return str.ToInteger(n, 0), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostTopCommentNum(ctx context.Context, postId uint64, _ ...any) (int, error) {
|
|
||||||
v, err := model.GetField[models.Comments](ctx, "count(*) num", model.Conditions(
|
|
||||||
model.Where(postTopCommentNumWhere(postId)),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return str.ToInteger(v, 0), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func postTopCommentNumWhere(postId uint64) model.SqlBuilder {
|
|
||||||
threadComments := wpconfig.GetOption("thread_comments")
|
|
||||||
pageComments := wpconfig.GetOption("page_comments")
|
|
||||||
where := model.SqlBuilder{
|
|
||||||
{"comment_approved", "1"},
|
|
||||||
{"comment_post_ID", "=", number.IntToString(postId), "int"},
|
|
||||||
}
|
|
||||||
if pageComments != "1" || threadComments == "1" || "1" == wpconfig.GetOption("thread_comments_depth") {
|
|
||||||
where = append(where, []string{"comment_parent", "0"})
|
|
||||||
}
|
|
||||||
return where
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostCommentsIds(ctx context.Context, postId uint64, page, limit, totalRaw int, order string) ([]uint64, int, error) {
|
|
||||||
condition := model.Conditions(
|
|
||||||
model.Where(postTopCommentNumWhere(postId)),
|
|
||||||
model.TotalRaw(totalRaw),
|
|
||||||
model.Fields("comment_ID"),
|
|
||||||
model.Order(model.SqlBuilder{
|
|
||||||
{"comment_date_gmt", order},
|
|
||||||
{"comment_ID", "asc"},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
var r []models.Comments
|
|
||||||
var total int
|
|
||||||
var err error
|
|
||||||
if limit < 1 {
|
|
||||||
r, err = model.ChunkFind[models.Comments](ctx, 300, condition)
|
|
||||||
total = len(r)
|
|
||||||
} else {
|
|
||||||
r, total, err = model.Pagination[models.Comments](ctx, condition, page, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && errors.Is(err, sql.ErrNoRows) {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return slice.Map(r, func(t models.Comments) uint64 {
|
|
||||||
return t.CommentId
|
|
||||||
}), total, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func CommentChildren(ctx context.Context, commentIds []uint64, _ ...any) (r map[uint64][]uint64, err error) {
|
|
||||||
rr, err := model.Finds[models.Comments](ctx, model.Conditions(
|
|
||||||
model.Where(model.SqlBuilder{
|
|
||||||
{"comment_parent", "in", ""},
|
|
||||||
{"comment_approved", "1"},
|
|
||||||
}),
|
|
||||||
model.In(slice.ToAnySlice(commentIds)),
|
|
||||||
model.Fields("comment_ID,comment_parent"),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rrr := slice.GroupBy(rr, func(v models.Comments) (uint64, uint64) {
|
|
||||||
return v.CommentParent, v.CommentId
|
|
||||||
})
|
|
||||||
r = make(map[uint64][]uint64)
|
|
||||||
for _, id := range commentIds {
|
|
||||||
r[id] = rrr[id]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func PreviousCommentNum(ctx context.Context, commentId, postId uint64) (int, error) {
|
|
||||||
v, err := model.GetField[models.Comments](ctx, "count(*)", model.Conditions(
|
|
||||||
model.Where(model.SqlBuilder{
|
|
||||||
{"comment_approved", "1"},
|
|
||||||
{"comment_post_ID", "=", number.IntToString(postId), "int"},
|
|
||||||
{"comment_ID", "<", number.IntToString(commentId), "int"},
|
|
||||||
{"comment_parent", "=", "0", "int"},
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return str.ToInteger(v, 0), nil
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
package dao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/helper"
|
|
||||||
"github.com/fthvgb1/wp-go/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TotalRaw int64
|
|
||||||
|
|
||||||
type PostIds struct {
|
|
||||||
Ids []uint64
|
|
||||||
Length int
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostContext struct {
|
|
||||||
Prev models.Posts
|
|
||||||
Next models.Posts
|
|
||||||
}
|
|
||||||
|
|
||||||
func CategoriesAndTags(ctx context.Context, t string, _ ...any) (terms []models.TermsMy, err error) {
|
|
||||||
var in = []any{"category", "post_tag"}
|
|
||||||
switch t {
|
|
||||||
case constraints.Category:
|
|
||||||
in = []any{"category"}
|
|
||||||
case constraints.Tag:
|
|
||||||
in = []any{"post_tag"}
|
|
||||||
}
|
|
||||||
w := model.SqlBuilder{
|
|
||||||
{"tt.taxonomy", "in", ""},
|
|
||||||
}
|
|
||||||
if helper.GetContextVal(ctx, "showOnlyTopLevel", false) {
|
|
||||||
w = append(w, []string{"tt.parent", "=", "0", "int"})
|
|
||||||
}
|
|
||||||
if !helper.GetContextVal(ctx, "showEmpty", false) {
|
|
||||||
w = append(w, []string{"tt.count", ">", "0", "int"})
|
|
||||||
}
|
|
||||||
order := []string{"name", "asc"}
|
|
||||||
ord := helper.GetContextVal[[]string](ctx, "order", nil)
|
|
||||||
if ord != nil {
|
|
||||||
order = ord
|
|
||||||
}
|
|
||||||
terms, err = model.Finds[models.TermsMy](ctx, model.Conditions(
|
|
||||||
model.Where(w),
|
|
||||||
model.Fields("t.term_id"),
|
|
||||||
model.Order(model.SqlBuilder{order}),
|
|
||||||
model.Join(model.SqlBuilder{
|
|
||||||
{"t", "inner join", "wp_term_taxonomy tt", "t.term_id = tt.term_id"},
|
|
||||||
}),
|
|
||||||
model.In(in),
|
|
||||||
))
|
|
||||||
for i := 0; i < len(terms); i++ {
|
|
||||||
if v, ok := wpconfig.GetTerm(terms[i].Terms.TermId); ok {
|
|
||||||
terms[i].Terms = v
|
|
||||||
}
|
|
||||||
if v, ok := wpconfig.GetTermTaxonomy(terms[i].Terms.TermId); ok {
|
|
||||||
terms[i].TermTaxonomy = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func Archives(ctx context.Context) ([]models.PostArchive, error) {
|
|
||||||
return model.Finds[models.PostArchive](ctx, model.Conditions(
|
|
||||||
model.Where(model.SqlBuilder{
|
|
||||||
{"post_type", "post"},
|
|
||||||
{"post_status", "publish"},
|
|
||||||
}),
|
|
||||||
model.Fields("YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, count(ID) as posts"),
|
|
||||||
model.Group("year,month"),
|
|
||||||
model.Order(model.SqlBuilder{{"year", "desc"}, {"month", "desc"}}),
|
|
||||||
))
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
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/model"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetPostMetaByPostIds(ctx context.Context, ids []uint64, _ ...any) (r map[uint64]map[string]any, err error) {
|
|
||||||
r = make(map[uint64]map[string]any)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
for _, postmeta := range rr {
|
|
||||||
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 := phphelper.UnPHPSerializeToStruct[models.WpAttachmentMetadata](postmeta.MetaValue)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "解析postmeta失败", postmeta.MetaId, postmeta.MetaValue)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r[postmeta.PostId][postmeta.MetaKey] = metadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToPostThumb(c context.Context, meta map[string]any, host string) (r models.PostThumbnail) {
|
|
||||||
if meta != nil {
|
|
||||||
m, ok := meta["_thumbnail_id"]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id, err := strconv.ParseUint(m.(string), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mx, err := GetPostMetaByPostIds(c, []uint64{id})
|
|
||||||
if err != nil || mx == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mm, ok := mx[id]
|
|
||||||
if !ok || mm == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
x, ok := mm["_wp_attachment_metadata"]
|
|
||||||
if ok {
|
|
||||||
metadata, ok := x.(models.WpAttachmentMetadata)
|
|
||||||
if ok {
|
|
||||||
r = wpconfig.Thumbnail(metadata, "post-thumbnail", host, "thumbnail")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,204 +0,0 @@
|
|||||||
package dao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models/relation"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/helper"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
"github.com/fthvgb1/wp-go/model"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetPostsByIds(ctx context.Context, ids []uint64, _ ...any) (m map[uint64]models.Posts, err error) {
|
|
||||||
m = make(map[uint64]models.Posts)
|
|
||||||
q := model.Conditions(
|
|
||||||
model.Where(model.SqlBuilder{{"Id", "in", ""}}),
|
|
||||||
model.Join(model.SqlBuilder{
|
|
||||||
{"a", "left join", "wp_term_relationships b", "a.Id=b.object_id"},
|
|
||||||
{"left join", "wp_term_taxonomy c", "b.term_taxonomy_id=c.term_taxonomy_id"},
|
|
||||||
{"left join", "wp_terms d", "c.term_id=d.term_id"},
|
|
||||||
}),
|
|
||||||
model.Fields("a.*,ifnull(d.name,'') category_name,ifnull(c.term_id,0) terms_id,ifnull(taxonomy,'') `taxonomy`"),
|
|
||||||
model.In(slice.ToAnySlice(ids)),
|
|
||||||
)
|
|
||||||
if helper.GetContextVal(ctx, "getPostAuthor", false) {
|
|
||||||
q.RelationFn = append(q.RelationFn, model.AddRelationFn(true, false, helper.GetContextVal[*model.QueryCondition](ctx, "postAuthorQueryCondition", nil), relation.PostsWithAuthor))
|
|
||||||
}
|
|
||||||
rawPosts, err := model.Finds[models.Posts](ctx, q)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return m, err
|
|
||||||
}
|
|
||||||
postsMap := make(map[uint64]models.Posts)
|
|
||||||
for i, post := range rawPosts {
|
|
||||||
v, ok := postsMap[post.Id]
|
|
||||||
if !ok {
|
|
||||||
v = rawPosts[i]
|
|
||||||
}
|
|
||||||
if post.Taxonomy == "category" {
|
|
||||||
v.Categories = append(v.Categories, post.CategoryName)
|
|
||||||
} else if post.Taxonomy == "post_tag" {
|
|
||||||
v.Tags = append(v.Tags, post.CategoryName)
|
|
||||||
}
|
|
||||||
if post.TermsId > 0 {
|
|
||||||
v.TermIds = append(v.TermIds, post.TermsId)
|
|
||||||
}
|
|
||||||
postsMap[post.Id] = v
|
|
||||||
}
|
|
||||||
//host, _ := wpconfig.Options.Load("siteurl")
|
|
||||||
host := ""
|
|
||||||
meta, _ := GetPostMetaByPostIds(ctx, ids)
|
|
||||||
for k, pp := range postsMap {
|
|
||||||
if len(pp.Categories) > 0 {
|
|
||||||
t := make([]string, 0, len(pp.Categories))
|
|
||||||
for _, cat := range pp.Categories {
|
|
||||||
t = append(t, fmt.Sprintf(`<a href="/p/category/%s" rel="category tag">%s</a>`, cat, cat))
|
|
||||||
}
|
|
||||||
pp.CategoriesHtml = strings.Join(t, "、")
|
|
||||||
}
|
|
||||||
mm, ok := meta[pp.Id]
|
|
||||||
if ok {
|
|
||||||
pp.Metas = mm
|
|
||||||
attMeta, ok := mm["_wp_attachment_metadata"]
|
|
||||||
if ok {
|
|
||||||
att, ok := attMeta.(models.WpAttachmentMetadata)
|
|
||||||
if ok {
|
|
||||||
pp.AttachmentMetadata = att
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pp.PostType != "attachment" {
|
|
||||||
thumb := ToPostThumb(ctx, mm, host)
|
|
||||||
if thumb.Path != "" {
|
|
||||||
pp.Thumbnail = thumb
|
|
||||||
}
|
|
||||||
} else if pp.PostType == "attachment" && pp.AttachmentMetadata.File != "" {
|
|
||||||
thumb := wpconfig.Thumbnail(pp.AttachmentMetadata, "thumbnail", host, "thumbnail", "post-thumbnail")
|
|
||||||
if thumb.Path != "" {
|
|
||||||
pp.Thumbnail = thumb
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
if len(pp.Tags) > 0 {
|
|
||||||
t := make([]string, 0, len(pp.Tags))
|
|
||||||
for _, cat := range pp.Tags {
|
|
||||||
t = append(t, fmt.Sprintf(`<a href="/p/tag/%s" rel="tag">%s</a>`, cat, cat))
|
|
||||||
}
|
|
||||||
pp.TagsHtml = strings.Join(t, "、")
|
|
||||||
}
|
|
||||||
m[k] = pp
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func SearchPostIds(ctx context.Context, _ string, args ...any) (ids PostIds, err error) {
|
|
||||||
q := args[0].(*model.QueryCondition)
|
|
||||||
page := args[1].(int)
|
|
||||||
pageSize := args[2].(int)
|
|
||||||
q.Fields = "ID"
|
|
||||||
res, total, err := model.Pagination[models.Posts](ctx, q, page, pageSize)
|
|
||||||
for _, posts := range res {
|
|
||||||
ids.Ids = append(ids.Ids, posts.Id)
|
|
||||||
}
|
|
||||||
ids.Length = total
|
|
||||||
totalR := int(atomic.LoadInt64(&TotalRaw))
|
|
||||||
if total > totalR {
|
|
||||||
tt := int64(total)
|
|
||||||
atomic.StoreInt64(&TotalRaw, tt)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMaxPostId(ctx context.Context, _ ...any) (uint64, error) {
|
|
||||||
r, err := model.Finds[models.Posts](ctx,
|
|
||||||
model.Conditions(
|
|
||||||
model.Where(model.SqlBuilder{
|
|
||||||
{"post_type", "post"},
|
|
||||||
{"post_status", "publish"}},
|
|
||||||
),
|
|
||||||
model.Fields("ID"),
|
|
||||||
model.Order(model.SqlBuilder{
|
|
||||||
{"ID", "desc"},
|
|
||||||
}),
|
|
||||||
model.Limit(1),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
var id uint64
|
|
||||||
if len(r) > 0 {
|
|
||||||
id = r[0].Id
|
|
||||||
}
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func RecentPosts(ctx context.Context, a ...any) (r []models.Posts, err error) {
|
|
||||||
num := helper.ParseArgs(10, a...)
|
|
||||||
r, err = model.Finds[models.Posts](ctx, model.Conditions(
|
|
||||||
model.Where(model.SqlBuilder{
|
|
||||||
{"post_type", "post"},
|
|
||||||
{"post_status", "publish"},
|
|
||||||
}),
|
|
||||||
model.Fields("ID,post_title,post_password,post_date_gmt"),
|
|
||||||
model.Order([][]string{{"post_date", "desc"}}),
|
|
||||||
model.Limit(num),
|
|
||||||
))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPostContext(ctx context.Context, _ uint64, arg ...any) (r PostContext, err error) {
|
|
||||||
t := arg[0].(time.Time)
|
|
||||||
next, err := model.FirstOne[models.Posts](ctx, model.SqlBuilder{
|
|
||||||
{"post_date", ">", t.Format("2006-01-02 15:04:05")},
|
|
||||||
{"post_status", "in", ""},
|
|
||||||
{"post_type", "post"},
|
|
||||||
}, "ID,post_title,post_password", nil, []any{"publish"})
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
prev, err := model.FirstOne[models.Posts](ctx, model.SqlBuilder{
|
|
||||||
{"post_date", "<", t.Format("2006-01-02 15:04:05")},
|
|
||||||
{"post_status", "in", ""},
|
|
||||||
{"post_type", "post"},
|
|
||||||
}, "ID,post_title", model.SqlBuilder{{"post_date", "desc"}}, []any{"publish"})
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r = PostContext{
|
|
||||||
Prev: prev,
|
|
||||||
Next: next,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func MonthPost(ctx context.Context, _ string, args ...any) (r []uint64, err error) {
|
|
||||||
year, month := args[0].(string), args[1].(string)
|
|
||||||
where := model.SqlBuilder{
|
|
||||||
{"post_type", "post"},
|
|
||||||
{"post_status", "publish"},
|
|
||||||
{"year(post_date)", year},
|
|
||||||
{"month(post_date)", month},
|
|
||||||
}
|
|
||||||
r, err = model.Column[models.Posts, uint64](ctx, func(v models.Posts) (uint64, bool) {
|
|
||||||
return v.Id, true
|
|
||||||
}, model.Conditions(
|
|
||||||
model.Fields("ID"),
|
|
||||||
model.Where(where),
|
|
||||||
))
|
|
||||||
l := int64(len(r))
|
|
||||||
if l > atomic.LoadInt64(&TotalRaw) {
|
|
||||||
atomic.StoreInt64(&TotalRaw, l)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package dao
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
"github.com/fthvgb1/wp-go/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetUserById(ctx context.Context, uid uint64, _ ...any) (r models.Users, err error) {
|
|
||||||
r, err = model.FindOneById[models.Users](ctx, uid)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func AllUsername(ctx context.Context, _ ...any) (map[string]uint64, error) {
|
|
||||||
r, err := model.SimpleFind[models.Users](ctx, model.SqlBuilder{
|
|
||||||
{"user_status", "=", "0", "int"},
|
|
||||||
}, "display_name,ID")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return slice.ToMap(r, func(t models.Users) (string, uint64) {
|
|
||||||
return t.DisplayName, t.Id
|
|
||||||
}, true), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserByName(ctx context.Context, u string, _ ...any) (r models.Users, err error) {
|
|
||||||
r, err = model.FirstOne[models.Users](ctx, model.SqlBuilder{{
|
|
||||||
"display_name", u,
|
|
||||||
}}, "*", nil)
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
"github.com/fthvgb1/wp-go/model"
|
|
||||||
"github.com/fthvgb1/wp-go/safety"
|
|
||||||
_ "github.com/go-sql-driver/mysql"
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"log"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
var safeDb = safety.NewVar[*sqlx.DB](nil)
|
|
||||||
var showQuerySql func() bool
|
|
||||||
|
|
||||||
func GetSqlxDB() *sqlx.DB {
|
|
||||||
return safeDb.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitDb() (*safety.Var[*sqlx.DB], error) {
|
|
||||||
c := config.GetConfig()
|
|
||||||
dsn := c.Mysql.Dsn.GetDsn()
|
|
||||||
db, err := sqlx.Open("mysql", dsn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
preDb := safeDb.Load()
|
|
||||||
if c.Mysql.Pool.ConnMaxIdleTime != 0 {
|
|
||||||
db.SetConnMaxIdleTime(c.Mysql.Pool.ConnMaxLifetime)
|
|
||||||
}
|
|
||||||
if c.Mysql.Pool.MaxIdleConn != 0 {
|
|
||||||
db.SetMaxIdleConns(c.Mysql.Pool.MaxIdleConn)
|
|
||||||
}
|
|
||||||
if c.Mysql.Pool.MaxOpenConn != 0 {
|
|
||||||
db.SetMaxOpenConns(c.Mysql.Pool.MaxOpenConn)
|
|
||||||
}
|
|
||||||
if c.Mysql.Pool.ConnMaxLifetime != 0 {
|
|
||||||
db.SetConnMaxLifetime(c.Mysql.Pool.ConnMaxLifetime)
|
|
||||||
}
|
|
||||||
safeDb.Store(db)
|
|
||||||
if preDb != nil {
|
|
||||||
_ = preDb.Close()
|
|
||||||
}
|
|
||||||
if showQuerySql == nil {
|
|
||||||
showQuerySql = reload.BuildFnVal("showQuerySql", false, func() bool {
|
|
||||||
return config.GetConfig().ShowQuerySql
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return safeDb, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func QueryDb(db *safety.Var[*sqlx.DB]) *model.SqlxQuery {
|
|
||||||
query := model.NewSqlxQuery(db, model.NewUniversalDb(
|
|
||||||
nil,
|
|
||||||
nil))
|
|
||||||
model.SetSelect(query, func(ctx context.Context, a any, s string, args ...any) error {
|
|
||||||
if showQuerySql() {
|
|
||||||
_, f, l, _ := runtime.Caller(5)
|
|
||||||
go func() {
|
|
||||||
log.Printf("%v:%v %v\n", f, l, model.FormatSql(s, args...))
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
return query.Selects(ctx, a, s, args...)
|
|
||||||
})
|
|
||||||
model.SetGet(query, func(ctx context.Context, a any, s string, args ...any) error {
|
|
||||||
if showQuerySql() {
|
|
||||||
_, f, l, _ := runtime.Caller(5)
|
|
||||||
go func() {
|
|
||||||
log.Printf("%v:%v %v\n", f, l, model.FormatSql(s, args...))
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
return query.Gets(ctx, a, s, args...)
|
|
||||||
})
|
|
||||||
return query
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
package logs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/safety"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var logs = safety.NewVar[*log.Logger](nil)
|
|
||||||
var logFile = safety.NewVar[*os.File](nil)
|
|
||||||
|
|
||||||
func InitLogger() error {
|
|
||||||
c := config.GetConfig()
|
|
||||||
return SetLogger(c.LogOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetLogger(loggerFile string) error {
|
|
||||||
if loggerFile == "" {
|
|
||||||
loggerFile = "stderr"
|
|
||||||
}
|
|
||||||
preFD := logFile.Load()
|
|
||||||
l := &log.Logger{}
|
|
||||||
var out io.Writer
|
|
||||||
switch loggerFile {
|
|
||||||
case "stdout":
|
|
||||||
out = os.Stdout
|
|
||||||
case "stderr":
|
|
||||||
out = os.Stderr
|
|
||||||
default:
|
|
||||||
file, err := os.OpenFile(loggerFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0777)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
out = file
|
|
||||||
logFile.Store(file)
|
|
||||||
}
|
|
||||||
logs.Store(l)
|
|
||||||
if preFD != nil {
|
|
||||||
_ = preFD.Close()
|
|
||||||
}
|
|
||||||
l.SetFlags(log.Ldate | log.Ltime)
|
|
||||||
l.SetOutput(out)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Errs(err error, depth int, desc string, args ...any) {
|
|
||||||
var pcs [1]uintptr
|
|
||||||
runtime.Callers(depth, pcs[:])
|
|
||||||
f := runtime.CallersFrames([]uintptr{pcs[0]})
|
|
||||||
ff, _ := f.Next()
|
|
||||||
s := strings.Builder{}
|
|
||||||
_, _ = fmt.Fprintf(&s, "%s:%d %s err:[%s]", ff.File, ff.Line, desc, err)
|
|
||||||
if len(args) > 0 {
|
|
||||||
s.WriteString(" args:")
|
|
||||||
for _, arg := range args {
|
|
||||||
_, _ = fmt.Fprintf(&s, "%v", arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logs.Load().Println(s.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func Error(err error, desc string, args ...any) {
|
|
||||||
Errs(err, 3, desc, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IfError(err error, desc string, args ...any) {
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Errs(err, 3, desc, args...)
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package relation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
var PostsWithAuthor = model.RelationHasOne(func(m *models.Posts) uint64 {
|
|
||||||
return m.PostAuthor
|
|
||||||
}, func(p *models.Users) uint64 {
|
|
||||||
return p.Id
|
|
||||||
}, func(m *models.Posts, p *models.Users) {
|
|
||||||
m.Author = p
|
|
||||||
}, model.Relationship{
|
|
||||||
RelationType: model.HasOne,
|
|
||||||
Table: "wp_users user",
|
|
||||||
ForeignKey: "ID",
|
|
||||||
Local: "post_author",
|
|
||||||
})
|
|
@ -1,59 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type PostMeta struct {
|
|
||||||
MetaId uint64 `db:"meta_id" json:"meta_id" form:"meta_id"`
|
|
||||||
PostId uint64 `db:"post_id" json:"post_id" form:"post_id"`
|
|
||||||
MetaKey string `db:"meta_key" json:"meta_key" form:"meta_key"`
|
|
||||||
MetaValue string `db:"meta_value" json:"meta_value" form:"meta_value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PostMeta) PrimaryKey() string {
|
|
||||||
return "meta_id"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PostMeta) Table() string {
|
|
||||||
return "wp_postmeta"
|
|
||||||
}
|
|
||||||
|
|
||||||
type WpAttachmentMetadata struct {
|
|
||||||
Width int `json:"width,omitempty"`
|
|
||||||
Height int `json:"height,omitempty"`
|
|
||||||
File string `json:"file,omitempty"`
|
|
||||||
FileSize int `json:"filesize,omitempty"`
|
|
||||||
Sizes map[string]MetaDataFileSize `json:"sizes,omitempty"`
|
|
||||||
ImageMeta ImageMeta `json:"image_meta"`
|
|
||||||
VideoMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
type ImageMeta struct {
|
|
||||||
Aperture string `json:"aperture,omitempty"`
|
|
||||||
Credit string `json:"credit,omitempty"`
|
|
||||||
Camera string `json:"camera,omitempty"`
|
|
||||||
Caption string `json:"caption,omitempty"`
|
|
||||||
CreatedTimestamp string `json:"created_timestamp,omitempty"`
|
|
||||||
Copyright string `json:"copyright,omitempty"`
|
|
||||||
FocalLength string `json:"focal_length,omitempty"`
|
|
||||||
Iso string `json:"iso,omitempty"`
|
|
||||||
ShutterSpeed string `json:"shutter_speed,omitempty"`
|
|
||||||
Title string `json:"title,omitempty"`
|
|
||||||
Orientation string `json:"orientation,omitempty"`
|
|
||||||
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"`
|
|
||||||
Height int `json:"height,omitempty"`
|
|
||||||
MimeType string `json:"mime-type,omitempty"`
|
|
||||||
FileSize int `json:"filesize,omitempty"`
|
|
||||||
}
|
|
@ -1,231 +0,0 @@
|
|||||||
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"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommentHandler struct {
|
|
||||||
*gin.Context
|
|
||||||
comments []*Comments
|
|
||||||
maxDepth int
|
|
||||||
depth int
|
|
||||||
isTls bool
|
|
||||||
i CommentHtml
|
|
||||||
isThreadComments bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Comments struct {
|
|
||||||
models.Comments
|
|
||||||
Children []*Comments
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommentHtml interface {
|
|
||||||
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)
|
|
||||||
|
|
||||||
var isTls bool
|
|
||||||
if c.Request.TLS != nil {
|
|
||||||
isTls = true
|
|
||||||
} else {
|
|
||||||
isTls = "https" == strings.ToLower(c.Request.Header.Get("X-Forwarded-Proto"))
|
|
||||||
}
|
|
||||||
h := CommentHandler{
|
|
||||||
Context: c,
|
|
||||||
comments: tree,
|
|
||||||
maxDepth: maxDepth,
|
|
||||||
depth: 1,
|
|
||||||
isTls: isTls,
|
|
||||||
i: i,
|
|
||||||
}
|
|
||||||
return h.formatComment(h.comments)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d CommentHandler) formatComment(comments []*Comments) (html string) {
|
|
||||||
s := str.NewBuilder()
|
|
||||||
if d.depth >= d.maxDepth {
|
|
||||||
comments = d.findComments(comments)
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
eo = "odd"
|
|
||||||
}
|
|
||||||
parent := ""
|
|
||||||
fl := false
|
|
||||||
if len(comment.Children) > 0 && d.depth < d.maxDepth+1 {
|
|
||||||
parent = "parent"
|
|
||||||
fl = true
|
|
||||||
}
|
|
||||||
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">`, d.formatComment(comment.Children), `</ol>`)
|
|
||||||
d.depth--
|
|
||||||
}
|
|
||||||
s.WriteString("</li><!-- #comment-## -->")
|
|
||||||
}
|
|
||||||
|
|
||||||
html = s.String()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d CommentHandler) findComments(comments []*Comments) []*Comments {
|
|
||||||
var r []*Comments
|
|
||||||
for _, comment := range comments {
|
|
||||||
tmp := *comment
|
|
||||||
tmp.Children = nil
|
|
||||||
r = append(r, &tmp)
|
|
||||||
if len(comment.Children) > 0 {
|
|
||||||
t := d.findComments(comment.Children)
|
|
||||||
r = append(r, t...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func treeComments(comments []models.Comments) []*Comments {
|
|
||||||
var r = map[uint64]*Comments{
|
|
||||||
0: {
|
|
||||||
Comments: models.Comments{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var top []*Comments
|
|
||||||
for _, comment := range comments {
|
|
||||||
c := Comments{
|
|
||||||
Comments: comment,
|
|
||||||
Children: []*Comments{},
|
|
||||||
}
|
|
||||||
r[comment.CommentId] = &c
|
|
||||||
if comment.CommentParent == 0 {
|
|
||||||
top = append(top, &c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for id, son := range r {
|
|
||||||
if id == son.CommentParent {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parent := r[son.CommentParent]
|
|
||||||
parent.Children = append(parent.Children, son)
|
|
||||||
}
|
|
||||||
return top
|
|
||||||
}
|
|
||||||
|
|
||||||
func CommonLi() string {
|
|
||||||
return li
|
|
||||||
}
|
|
||||||
|
|
||||||
var commonCommentFormat = CommonCommentFormat{}
|
|
||||||
|
|
||||||
func CommentRender() CommonCommentFormat {
|
|
||||||
return commonCommentFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommonCommentFormat struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
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) 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 = `
|
|
||||||
<li id="comment-{{CommentId}}" class="comment {{eo}} thread-even depth-{{Depth}} {{parent}}">
|
|
||||||
<article id="div-comment-{{CommentId}}" class="comment-body">
|
|
||||||
<footer class="comment-meta">
|
|
||||||
<div class="comment-author vcard">
|
|
||||||
<img alt=""
|
|
||||||
src="{{Gravatar}}"
|
|
||||||
srcset="{{Gravatar}} 2x"
|
|
||||||
class="avatar avatar-56 photo" height="56" width="56" loading="lazy">
|
|
||||||
<b class="fn">{{CommentAuthor}}</b>
|
|
||||||
<span class="says">说道:</span></div><!-- .comment-author -->
|
|
||||||
|
|
||||||
<div class="comment-metadata">
|
|
||||||
<a href="/p/{{PostId}}/comment-page-{{page}}#comment-{{CommentId}}">
|
|
||||||
<time datetime="{{CommentDateGmt}}">{{CommentDate}}</time>
|
|
||||||
</a></div><!-- .comment-metadata -->
|
|
||||||
|
|
||||||
</footer><!-- .comment-meta -->
|
|
||||||
|
|
||||||
<div class="comment-content">
|
|
||||||
<p>{{CommentContent}}</p>
|
|
||||||
</div><!-- .comment-content -->
|
|
||||||
|
|
||||||
{{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>`
|
|
||||||
|
|
||||||
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(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)
|
|
||||||
}
|
|
||||||
return li
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
<div>
|
|
||||||
{{.aa}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{{ range $k,$v := .posts}}
|
|
||||||
<h2>{{$v.PostTitle}} </h2>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
@ -1,81 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"github.com/fthvgb1/wp-go/app/cmd/route"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp/components"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget"
|
|
||||||
route2 "github.com/fthvgb1/wp-go/app/theme/wp/route"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"plugintt/xx"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed a.gohtml
|
|
||||||
var em embed.FS
|
|
||||||
var tt *template.Template
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// register as theme
|
|
||||||
theme.AddThemeHookFunc("themename", hook)
|
|
||||||
|
|
||||||
//use the local template
|
|
||||||
//note: must use embed.FS
|
|
||||||
t, err := template.ParseFS(em, "a.gohtml")
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "")
|
|
||||||
}
|
|
||||||
tt = t
|
|
||||||
|
|
||||||
// register gin route. it will be effecting when server restart.
|
|
||||||
route.Hook(func(r *gin.Engine) {
|
|
||||||
r.GET("xx", func(c *gin.Context) {
|
|
||||||
c.String(http.StatusOK, "xxoo")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func hook(h *wp.Handle) {
|
|
||||||
wp.Run(h, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func config(h *wp.Handle) {
|
|
||||||
// same theme config
|
|
||||||
wphandle.UsePlugins(h)
|
|
||||||
wp.InitPipe(h)
|
|
||||||
h.PushHandler(constraints.PipeMiddleware, constraints.Home,
|
|
||||||
wp.NewHandleFn(widget.IsCategory, 100, "widget.IsCategory"))
|
|
||||||
components.WidgetArea(h)
|
|
||||||
h.PushHandler(constraints.PipeRender, constraints.Home, wp.NewHandleFn(func(h *wp.Handle) {
|
|
||||||
h.SetData("aa", "xyxxxx")
|
|
||||||
h.RenderHtml(tt, http.StatusOK, "a.gohtml")
|
|
||||||
h.Abort()
|
|
||||||
h.StopPipe()
|
|
||||||
}, 10, "renderHome"))
|
|
||||||
|
|
||||||
// use simple reg route
|
|
||||||
route2.PushRoute(`(?P<control>\w+)/(?P<method>\w+)`, route2.Route{
|
|
||||||
Path: `(?P<control>\w+)/(?P<method>\w+)`,
|
|
||||||
Scene: constraints.Home,
|
|
||||||
Method: []string{"GET"},
|
|
||||||
Type: "reg",
|
|
||||||
})
|
|
||||||
//...
|
|
||||||
}
|
|
||||||
|
|
||||||
// Xo to be a func when theme init
|
|
||||||
func Xo(h *wp.Handle) {
|
|
||||||
xx.Xo()
|
|
||||||
route2.Delete(`(?P<control>\w+)/(?P<method>\w+)`)
|
|
||||||
h.ReplaceHandle(constraints.PipeRender, "wp.RenderTemplate", func(h *wp.Handle) {
|
|
||||||
h.SetData("aa", "xyxxxx")
|
|
||||||
h.RenderHtml(tt, http.StatusOK, "a.gohtml")
|
|
||||||
h.StopPipe()
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
#/bin/bash
|
|
||||||
|
|
||||||
# copy plugintt to other dir and remove .dev suffix
|
|
||||||
# note the go version and build tool flag must same to server build
|
|
||||||
# eg: -gcflags all="-N -l" --race may used in ide debug
|
|
||||||
go mod tidy
|
|
||||||
go build -buildmode=plugin -o xx.so main.go
|
|
@ -1,141 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/dao"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/number"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"strconv"
|
|
||||||
str "strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RdmCache[K comparable, V any] struct {
|
|
||||||
expired func() time.Duration
|
|
||||||
rdb *redis.Client
|
|
||||||
keyFn func(K) string
|
|
||||||
name string
|
|
||||||
resFn func(map[string]string) V
|
|
||||||
saveData func(V) map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RdmCache[K, V]) SetExpiredTime(f func() time.Duration) {
|
|
||||||
r.expired = f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RdmCache[K, V]) Get(ctx context.Context, key K) (V, bool) {
|
|
||||||
var re V
|
|
||||||
result, err := r.rdb.Exists(ctx, r.keyFn(key)).Result()
|
|
||||||
if result <= 0 || err != nil {
|
|
||||||
return re, false
|
|
||||||
}
|
|
||||||
|
|
||||||
rr, err := r.rdb.HGetAll(ctx, r.keyFn(key)).Result()
|
|
||||||
|
|
||||||
if errors.Is(err, redis.Nil) {
|
|
||||||
return re, false
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return re, false
|
|
||||||
}
|
|
||||||
return r.resFn(rr), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RdmCache[K, V]) Set(ctx context.Context, key K, val V) {
|
|
||||||
k := r.keyFn(key)
|
|
||||||
result, err := r.rdb.HSet(ctx, k, r.saveData(val)).Result()
|
|
||||||
b, err := r.rdb.Expire(ctx, k, r.expired()).Result()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(result, b, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Println(result, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RdmCache[K, V]) GetExpireTime(ctx context.Context) time.Duration {
|
|
||||||
return r.expired()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RdmCache[K, V]) Ttl(ctx context.Context, key K) time.Duration {
|
|
||||||
result, err := r.rdb.TTL(ctx, r.keyFn(key)).Result()
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RdmCache[K, V]) Flush(ctx context.Context) {
|
|
||||||
fmt.Println("flush redis cache")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RdmCache[K, V]) Del(ctx context.Context, key ...K) {
|
|
||||||
r.rdb.Del(ctx, slice.Map(key, r.keyFn)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RdmCache[K, V]) ClearExpired(ctx context.Context) {
|
|
||||||
fmt.Println("clear expired redis cache")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RedisCache use step:
|
|
||||||
// 1 go build -gcflags all="-N -l" --race -buildmode=plugin -o redisCache.so main.go && cp ./redisCache.so ../wp-go/plugins/
|
|
||||||
// 2 wp-go config add redisCache plugin
|
|
||||||
func RedisCache(h *wp.Handle) {
|
|
||||||
vv, ok := cachemanager.GetMapCache[string, dao.PostIds]("listPostIds")
|
|
||||||
if ok {
|
|
||||||
_, ok := any(vv.Cache).(*RdmCache[string, dao.PostIds])
|
|
||||||
if ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reload.AppendOnceFn(func() {
|
|
||||||
err := cachemanager.SetMapCache("listPostIds", vv)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "set recovery listPostIds cache err")
|
|
||||||
} else {
|
|
||||||
cachemanager.PushOrSetFlush(cachemanager.Queue{Name: "listPostIds", Fn: vv.Flush})
|
|
||||||
cachemanager.PushOrSetClearExpired(cachemanager.Queue{Name: "listPostIds", Fn: vv.Flush})
|
|
||||||
fmt.Println("recovery listPostIds cache ok")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
rdm := redis.NewClient(&redis.Options{
|
|
||||||
Addr: "localhost:6379",
|
|
||||||
Password: "", // no password set
|
|
||||||
DB: 0, // use default DB
|
|
||||||
})
|
|
||||||
r := RdmCache[string, dao.PostIds]{
|
|
||||||
expired: func() time.Duration {
|
|
||||||
return time.Minute
|
|
||||||
},
|
|
||||||
keyFn: func(u string) string {
|
|
||||||
return strings.Join("postIds:", u)
|
|
||||||
},
|
|
||||||
rdb: rdm,
|
|
||||||
name: "",
|
|
||||||
resFn: func(m map[string]string) dao.PostIds {
|
|
||||||
return dao.PostIds{
|
|
||||||
Ids: slice.Map(str.Split(m["ids"], ","), strings.ToInt[uint64]),
|
|
||||||
Length: strings.ToInt[int](m["length"]),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
saveData: func(ids dao.PostIds) map[string]string {
|
|
||||||
t := slice.Map(ids.Ids, number.IntToString[uint64])
|
|
||||||
return map[string]string{
|
|
||||||
"ids": str.Join(t, ","),
|
|
||||||
"length": strconv.Itoa(ids.Length),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cachemanager.NewMapCache[string, dao.PostIds](&r, nil, dao.SearchPostIds, config.GetConfig().CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.PostListCacheTime
|
|
||||||
})
|
|
||||||
fmt.Println("redis cache inited ok")
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
module redisCache
|
|
||||||
|
|
||||||
go 1.21
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/fthvgb1/wp-go v0.0.0-20231210111549-d72bed0c8c4e
|
|
||||||
github.com/redis/go-redis/v9 v9.3.0
|
|
||||||
)
|
|
||||||
|
|
||||||
replace github.com/fthvgb1/wp-go v0.0.0-20231210111549-d72bed0c8c4e => ../wp-go
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/bytedance/sonic v1.10.2 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
|
||||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
|
||||||
github.com/elliotchance/phpserialize v1.3.3 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
|
||||||
github.com/gin-contrib/sessions v0.0.5 // indirect
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.16.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
|
||||||
github.com/gorilla/sessions v1.2.2 // indirect
|
|
||||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
|
||||||
golang.org/x/arch v0.6.0 // indirect
|
|
||||||
golang.org/x/crypto v0.15.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
|
||||||
golang.org/x/net v0.18.0 // indirect
|
|
||||||
golang.org/x/sys v0.14.0 // indirect
|
|
||||||
golang.org/x/text v0.14.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
@ -1,11 +0,0 @@
|
|||||||
package xx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Xo() {
|
|
||||||
fmt.Println("xxoo")
|
|
||||||
fmt.Println(decimal.Max(decimal.NewFromFloat(32.3333333333333), decimal.NewFromFloat(32.33333333333331)).String())
|
|
||||||
}
|
|
@ -1,187 +0,0 @@
|
|||||||
package plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
"github.com/fthvgb1/wp-go/helper"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/maps"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/fthvgb1/wp-go/plugin/digest"
|
|
||||||
"github.com/fthvgb1/wp-go/safety"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
var more = regexp.MustCompile("<!--more(.*?)?-->")
|
|
||||||
|
|
||||||
var removeWpBlock = regexp.MustCompile("<!-- /?wp:.*-->")
|
|
||||||
|
|
||||||
type DigestConfig struct {
|
|
||||||
DigestWordCount int `yaml:"digestWordCount"`
|
|
||||||
DigestAllowTag string `yaml:"digestAllowTag"`
|
|
||||||
DigestRegex string `yaml:"digestRegex"`
|
|
||||||
DigestTagOccupyNum []struct {
|
|
||||||
Tag string `yaml:"tag"`
|
|
||||||
Num int `yaml:"num"`
|
|
||||||
ChuckOvered bool `yaml:"chuckOvered"`
|
|
||||||
EscapeCharacter []struct {
|
|
||||||
Tags string `yaml:"tags"`
|
|
||||||
Character []string `yaml:"character"`
|
|
||||||
Num int `yaml:"num"`
|
|
||||||
ChuckOvered bool `yaml:"chuckOvered"`
|
|
||||||
} `yaml:"escapeCharacter"`
|
|
||||||
} `yaml:"digestTagOccupyNum"`
|
|
||||||
specialSolve map[string]digest.SpecialSolveConf
|
|
||||||
}
|
|
||||||
|
|
||||||
var digestConfig *safety.Var[DigestConfig]
|
|
||||||
|
|
||||||
func InitDigestCache() {
|
|
||||||
cachemanager.NewMemoryMapCache(nil, digestRaw, config.GetConfig().CacheTime.DigestCacheTime, "digestPlugin", func() time.Duration {
|
|
||||||
return config.GetConfig().CacheTime.DigestCacheTime
|
|
||||||
})
|
|
||||||
|
|
||||||
digestConfig = reload.VarsBy(func() DigestConfig {
|
|
||||||
c, err := config.GetCustomizedConfig[DigestConfig]()
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "get digest config fail")
|
|
||||||
c.DigestWordCount = config.GetConfig().DigestWordCount
|
|
||||||
c.DigestAllowTag = config.GetConfig().DigestAllowTag
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
if c.DigestRegex != "" {
|
|
||||||
digest.SetQutos(c.DigestRegex)
|
|
||||||
}
|
|
||||||
if len(c.DigestTagOccupyNum) <= 1 {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
c.specialSolve = ParseDigestConf(c)
|
|
||||||
return c
|
|
||||||
}, "digestConfig")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseDigestConf(c DigestConfig) map[string]digest.SpecialSolveConf {
|
|
||||||
specialSolve := map[string]digest.SpecialSolveConf{}
|
|
||||||
for _, item := range c.DigestTagOccupyNum {
|
|
||||||
tags := strings.Split(strings.ReplaceAll(item.Tag, " ", ""), "<")
|
|
||||||
for _, tag := range tags {
|
|
||||||
if tag == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ec := make(map[rune]digest.SpecialSolve)
|
|
||||||
specialTags := make(map[string]digest.SpecialSolve)
|
|
||||||
tag = str.Join("<", tag)
|
|
||||||
if len(item.EscapeCharacter) > 0 {
|
|
||||||
for _, esc := range item.EscapeCharacter {
|
|
||||||
for _, i := range esc.Character {
|
|
||||||
s := []rune(i)
|
|
||||||
if len(s) == 1 {
|
|
||||||
ec[s[0]] = digest.SpecialSolve{
|
|
||||||
Num: esc.Num,
|
|
||||||
ChuckOvered: esc.ChuckOvered,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if esc.Tags == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tagss := strings.Split(strings.ReplaceAll(esc.Tags, " ", ""), "<")
|
|
||||||
for _, t := range tagss {
|
|
||||||
if t == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t = str.Join("<", t)
|
|
||||||
specialTags[t] = digest.SpecialSolve{
|
|
||||||
Num: esc.Num,
|
|
||||||
ChuckOvered: esc.ChuckOvered,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v, ok := specialSolve[tag]
|
|
||||||
if !ok {
|
|
||||||
specialSolve[tag] = digest.SpecialSolveConf{
|
|
||||||
Num: item.Num,
|
|
||||||
ChuckOvered: item.ChuckOvered,
|
|
||||||
EscapeCharacter: ec,
|
|
||||||
Tags: specialTags,
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
v.Num = item.Num
|
|
||||||
v.ChuckOvered = item.ChuckOvered
|
|
||||||
v.EscapeCharacter = maps.Merge(v.EscapeCharacter, ec)
|
|
||||||
v.Tags = maps.Merge(v.Tags, specialTags)
|
|
||||||
specialSolve[tag] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return specialSolve
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveWpBlock(s string) string {
|
|
||||||
return removeWpBlock.ReplaceAllString(s, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func digestRaw(ctx context.Context, id uint64, arg ...any) (string, error) {
|
|
||||||
s := arg[1].(string)
|
|
||||||
limit := arg[3].(int)
|
|
||||||
if limit < 0 {
|
|
||||||
return s, nil
|
|
||||||
} else if limit == 0 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s = more.ReplaceAllString(s, "")
|
|
||||||
fn := helper.GetContextVal(ctx, "postMoreFn", PostsMore)
|
|
||||||
return Digests(s, id, limit, fn), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Digests(content string, id uint64, limit int, fn func(id uint64, content, closeTag string) string) string {
|
|
||||||
closeTag := ""
|
|
||||||
content = RemoveWpBlock(content)
|
|
||||||
c := digestConfig.Load()
|
|
||||||
tag := c.DigestAllowTag
|
|
||||||
if tag == "" {
|
|
||||||
tag = "<a><b><blockquote><br><cite><code><dd><del><div><dl><dt><em><h1><h2><h3><h4><h5><h6><i><img><li><ol><p><pre><span><strong><ul>"
|
|
||||||
}
|
|
||||||
content = digest.StripTags(content, tag)
|
|
||||||
length := utf8.RuneCountInString(content) + 1
|
|
||||||
if length <= limit {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
if len(c.specialSolve) > 0 {
|
|
||||||
content, closeTag = digest.CustomizeHtml(content, limit, c.specialSolve)
|
|
||||||
} else {
|
|
||||||
content, closeTag = digest.Html(content, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fn == nil {
|
|
||||||
return PostsMore(id, content, closeTag)
|
|
||||||
}
|
|
||||||
return fn(id, content, closeTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostsMore(id uint64, content, closeTag string) string {
|
|
||||||
tmp := `%s......%s<p class="read-more"><a href="/p/%d">继续阅读</a></p>`
|
|
||||||
if strings.Contains(closeTag, "pre") || strings.Contains(closeTag, "code") {
|
|
||||||
tmp = `%s%s......<p class="read-more"><a href="/p/%d">继续阅读</a></p>`
|
|
||||||
}
|
|
||||||
content = fmt.Sprintf(tmp, content, closeTag, id)
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
func Digest(ctx context.Context, post *models.Posts, limit int) {
|
|
||||||
content, _ := cachemanager.GetBy[string]("digestPlugin", ctx, post.Id, time.Second, ctx, post.PostContent, post.Id, limit)
|
|
||||||
post.PostContent = content
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostExcerpt(post *models.Posts) {
|
|
||||||
post.PostContent = strings.Replace(post.PostExcerpt, "\n", "<br/>", -1)
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
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"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Gravatar(email string, isTls bool) (u string) {
|
|
||||||
email = strings.Trim(email, " \t\n\r\000\x0B")
|
|
||||||
num := number.Rand(0, 2)
|
|
||||||
h := ""
|
|
||||||
if email != "" {
|
|
||||||
h = str.Md5(strings.ToLower(email))
|
|
||||||
num = int(h[0] % 3)
|
|
||||||
}
|
|
||||||
if isTls {
|
|
||||||
u = fmt.Sprintf("%s%s", "https://secure.gravatar.com/avatar/", h)
|
|
||||||
} else {
|
|
||||||
u = fmt.Sprintf("http://%d.gravatar.com/avatar/%s", num, h)
|
|
||||||
}
|
|
||||||
q := url.Values{}
|
|
||||||
q.Add("s", "112")
|
|
||||||
q.Add("d", "mm")
|
|
||||||
q.Add("r", strings.ToLower(wpconfig.GetOption("avatar_rating")))
|
|
||||||
u = fmt.Sprintf("%s?%s", u, q.Encode())
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,176 +0,0 @@
|
|||||||
package plugins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/helper"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PageEle struct {
|
|
||||||
PrevEle string
|
|
||||||
NextEle string
|
|
||||||
DotsEle string
|
|
||||||
MiddleEle string
|
|
||||||
CurrentEle string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TwentyFifteenPagination() PageEle {
|
|
||||||
return twentyFifteen
|
|
||||||
}
|
|
||||||
func TwentyFifteenCommentPagination() CommentPageEle {
|
|
||||||
return twentyFifteenComment
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommentPageEle struct {
|
|
||||||
PageEle
|
|
||||||
}
|
|
||||||
|
|
||||||
var twentyFifteen = PageEle{
|
|
||||||
PrevEle: `<a class="prev page-numbers" href="%s">上一页</a>`,
|
|
||||||
NextEle: `<a class="next page-numbers" href="%s">下一页</a>`,
|
|
||||||
DotsEle: `<span class="page-numbers dots">…</span>`,
|
|
||||||
MiddleEle: `<a class="page-numbers" href="%s"><span class="meta-nav screen-reader-text">页 </span>%d</a>
|
|
||||||
`,
|
|
||||||
CurrentEle: `<span aria-current="page" class="page-numbers current">
|
|
||||||
<span class="meta-nav screen-reader-text">页 </span>%d</span>`,
|
|
||||||
}
|
|
||||||
var twentyFifteenComment = CommentPageEle{
|
|
||||||
PageEle{
|
|
||||||
PrevEle: `<div class="nav-previous"><a href="%s">%s</a></div>`,
|
|
||||||
NextEle: `<div class="nav-next"><a href="%s">%s</a></div>`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PageEle) Current(page, totalPage, totalRow int) string {
|
|
||||||
return fmt.Sprintf(p.CurrentEle, page)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PageEle) Prev(url string) string {
|
|
||||||
return fmt.Sprintf(p.PrevEle, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PageEle) Next(url string) string {
|
|
||||||
return fmt.Sprintf(p.NextEle, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PageEle) Dots() string {
|
|
||||||
return p.DotsEle
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PageEle) Middle(page int, url string) string {
|
|
||||||
return fmt.Sprintf(p.MiddleEle, url, page)
|
|
||||||
}
|
|
||||||
|
|
||||||
var reg = regexp.MustCompile(`(/page)/(\d+)`)
|
|
||||||
var commentReg = regexp.MustCompile(`/comment-page-(\d+)`)
|
|
||||||
|
|
||||||
var queryParam = []string{"paged", "cat", "m", "author", "tag"}
|
|
||||||
|
|
||||||
func (p PageEle) Urls(u url.URL, page int, isTLS bool) string {
|
|
||||||
var path, query = u.Path, u.RawQuery
|
|
||||||
if path == "/" {
|
|
||||||
v := u.Query()
|
|
||||||
for _, q := range queryParam {
|
|
||||||
if v.Get(q) != "" {
|
|
||||||
v.Set("paged", strconv.Itoa(page))
|
|
||||||
return str.Join(path, "?", v.Encode())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(path, "/page/") {
|
|
||||||
path = fmt.Sprintf("%s%s", path, "/page/1")
|
|
||||||
}
|
|
||||||
if page == 1 {
|
|
||||||
path = reg.ReplaceAllString(path, "")
|
|
||||||
} else {
|
|
||||||
s := fmt.Sprintf("$1/%d", page)
|
|
||||||
path = reg.ReplaceAllString(path, s)
|
|
||||||
}
|
|
||||||
path = strings.Replace(path, "//", "/", -1)
|
|
||||||
if path == "" {
|
|
||||||
path = "/"
|
|
||||||
}
|
|
||||||
if query != "" {
|
|
||||||
return str.Join(path, "?", query)
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p CommentPageEle) Urls(u url.URL, page int, isTLS bool) string {
|
|
||||||
var path, query = u.Path, u.RawQuery
|
|
||||||
if path == "/" {
|
|
||||||
v := u.Query()
|
|
||||||
if v.Get("p") != "" {
|
|
||||||
v.Set("cpage", strconv.Itoa(page))
|
|
||||||
return str.Join(path, "?", v.Encode(), "#comments")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(path, "/comment-page-") {
|
|
||||||
path = fmt.Sprintf("%s%s", path, "/comment-page-1")
|
|
||||||
}
|
|
||||||
path = commentReg.ReplaceAllString(path, fmt.Sprintf("/comment-page-%d", page))
|
|
||||||
path = strings.Replace(path, "//", "/", -1)
|
|
||||||
ur := path
|
|
||||||
if query != "" {
|
|
||||||
ur = str.Join(path, "?", query)
|
|
||||||
}
|
|
||||||
ur = str.Join(ur, "#comments")
|
|
||||||
return ur
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p CommentPageEle) Middle(page int, url string) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
func (p CommentPageEle) Dots() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
func (p CommentPageEle) Current(page, totalPage, totalRow int) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
func (p CommentPageEle) Prev(url string) string {
|
|
||||||
return fmt.Sprintf(p.PrevEle, url, helper.Or(wpconfig.GetOption("comment_order") == "asc", "较早评论", "较新评论"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p CommentPageEle) Next(url string) string {
|
|
||||||
return fmt.Sprintf(p.NextEle, url, helper.Or(wpconfig.GetOption("comment_order") == "asc", "较新评论", "较早评论"))
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaginationNav struct {
|
|
||||||
Currents func(page, totalPage, totalRows int) string
|
|
||||||
Prevs func(url string) string
|
|
||||||
Nexts func(url string) string
|
|
||||||
Dotss func() string
|
|
||||||
Middles func(page int, url string) string
|
|
||||||
Urlss func(u url.URL, page int, isTLS bool) string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PaginationNav) Current(page, totalPage, totalRows int) string {
|
|
||||||
return p.Currents(page, totalPage, totalRows)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PaginationNav) Prev(url string) string {
|
|
||||||
return p.Prevs(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PaginationNav) Next(url string) string {
|
|
||||||
return p.Nexts(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PaginationNav) Dots() string {
|
|
||||||
return p.Dotss()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PaginationNav) Middle(page int, url string) string {
|
|
||||||
return p.Middles(page, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PaginationNav) Urls(u url.URL, page int, isTLS bool) string {
|
|
||||||
return p.Urlss(u, page, isTLS)
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package apply
|
|
||||||
|
|
||||||
import "github.com/fthvgb1/wp-go/safety"
|
|
||||||
|
|
||||||
var contains = safety.NewMap[string, any]()
|
|
||||||
|
|
||||||
func SetVal(key string, val any) {
|
|
||||||
contains.Store(key, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DelVal(key string) {
|
|
||||||
contains.Delete(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetVal[V any](key string) (V, bool) {
|
|
||||||
v, ok := contains.Load(key)
|
|
||||||
if !ok {
|
|
||||||
var vv V
|
|
||||||
return vv, ok
|
|
||||||
}
|
|
||||||
return v.(V), ok
|
|
||||||
}
|
|
||||||
func GetRawVal(key string) (any, bool) {
|
|
||||||
return contains.Load(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPlugins() any {
|
|
||||||
v, _ := contains.Load("wp-plugins")
|
|
||||||
return v
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package enlightjs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/phphelper"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/maps"
|
|
||||||
"github.com/goccy/go-json"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Selectors Selectors `json:"selectors"`
|
|
||||||
Options Options `json:"options"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
Indent int64 `json:"indent,omitempty"`
|
|
||||||
AmpersandCleanup bool `json:"ampersandCleanup,omitempty"`
|
|
||||||
Linehover bool `json:"linehover,omitempty"`
|
|
||||||
RawcodeDbclick bool `json:"rawcodeDbclick,omitempty"`
|
|
||||||
TextOverflow string `json:"textOverflow,omitempty"`
|
|
||||||
Linenumbers bool `json:"linenumbers,omitempty"`
|
|
||||||
Theme string `json:"theme,omitempty"`
|
|
||||||
Language string `json:"language,omitempty"`
|
|
||||||
RetainCssClasses bool `json:"retainCssClasses,omitempty"`
|
|
||||||
Collapse bool `json:"collapse,omitempty"`
|
|
||||||
ToolbarOuter string `json:"toolbarOuter,omitempty"`
|
|
||||||
ToolbarTop string `json:"toolbarTop,omitempty"`
|
|
||||||
ToolbarBottom string `json:"toolbarBottom,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Selectors struct {
|
|
||||||
Block string `json:"block,omitempty"`
|
|
||||||
Inline string `json:"inline,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnlighterJS(h *wp.Handle) {
|
|
||||||
h.PushGroupHeadScript(constraints.AllScene, "enlighterjs-css", 20, `<link rel='stylesheet' id='enlighterjs-css' href='/wp-content/plugins/enlighter/cache/enlighterjs.min.css' media='all' />`)
|
|
||||||
|
|
||||||
h.PushCacheGroupFooterScript(constraints.AllScene, "enlighterJs", 10, func(h *wp.Handle) string {
|
|
||||||
op := wpconfig.GetOption("enlighter-options")
|
|
||||||
opp, err := phphelper.UnPHPSerializeToStrAnyMap(op)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "获取enlighter-option失败", op)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
v := Config{
|
|
||||||
Selectors: Selectors{
|
|
||||||
Block: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-selector-block", "pre.EnlighterJSRAW"),
|
|
||||||
Inline: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-selector-inline", "code.EnlighterJSRAW"),
|
|
||||||
},
|
|
||||||
Options: Options{
|
|
||||||
Indent: maps.GetStrAnyValWithDefaults[int64](opp, "enlighterjs-indent", 4),
|
|
||||||
AmpersandCleanup: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-ampersandcleanup", true),
|
|
||||||
Linehover: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linehover", true),
|
|
||||||
RawcodeDbclick: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-rawcodedbclick", true),
|
|
||||||
TextOverflow: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-textoverflow", "break"),
|
|
||||||
Linenumbers: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linenumbers", true),
|
|
||||||
Theme: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-theme", "enlighter"),
|
|
||||||
Language: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-language", "generic"),
|
|
||||||
RetainCssClasses: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-retaincss", false),
|
|
||||||
Collapse: false,
|
|
||||||
ToolbarOuter: "",
|
|
||||||
ToolbarTop: "{BTN_RAW}{BTN_COPY}{BTN_WINDOW}{BTN_WEBSITE}",
|
|
||||||
ToolbarBottom: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
conf, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "json化enlighterjs配置失败")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(enlighterjs, conf)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var enlighterjs = `<script src='/wp-content/plugins/enlighter/cache/enlighterjs.min.js?ver=0A0B0C' id='enlighterjs-js'></script>
|
|
||||||
<script id='enlighterjs-js-after'>
|
|
||||||
!function(e,n){if("undefined"!=typeof EnlighterJS){var o=%s;(e.EnlighterJSINIT=function(){EnlighterJS.init(o.selectors.block,o.selectors.inline,o.options)})()}else{(n&&(n.error||n.log)||function(){})("Error: EnlighterJS resources not loaded yet!")}}(window,console);
|
|
||||||
</script>`
|
|
@ -1,72 +0,0 @@
|
|||||||
package wphandle
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle/apply"
|
|
||||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle/enlightjs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle/hiddenlogin"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/fthvgb1/wp-go/safety"
|
|
||||||
"path/filepath"
|
|
||||||
"plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
var plugins = func() *safety.Map[string, func(*wp.Handle)] {
|
|
||||||
m := safety.NewMap[string, func(*wp.Handle)]()
|
|
||||||
m.Store("Enlightjs", enlightjs.EnlighterJS)
|
|
||||||
m.Store("HiddenLogin", hiddenlogin.HiddenLogin)
|
|
||||||
return m
|
|
||||||
}()
|
|
||||||
|
|
||||||
func RegisterPlugin(name string, fn func(*wp.Handle)) {
|
|
||||||
plugins.Store(name, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UsePlugins(h *wp.Handle, calls ...string) {
|
|
||||||
calls = append(calls, config.GetConfig().Plugins...)
|
|
||||||
for _, call := range calls {
|
|
||||||
call = str.FirstUpper(call)
|
|
||||||
if fn, ok := plugins.Load(call); ok {
|
|
||||||
fn(h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadPlugins() {
|
|
||||||
dirPath := config.GetConfig().PluginPath
|
|
||||||
if dirPath == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
glob, err := filepath.Glob(filepath.Join(dirPath, "*.so"))
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "读取插件目录错误", dirPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, entry := range glob {
|
|
||||||
p, err := plugin.Open(entry)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "读取插件错误", entry)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := filepath.Ext(entry)
|
|
||||||
name = filepath.Base(entry[0 : len(entry)-len(name)])
|
|
||||||
name = str.FirstUpper(name)
|
|
||||||
pl, err := p.Lookup(name)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, "插件lookup错误", entry)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
plu, ok := pl.(func(*wp.Handle))
|
|
||||||
if !ok {
|
|
||||||
logs.Error(errors.New("switch func(*wp.Handle) fail"), "插件转换错误", entry)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
RegisterPlugin(name, plu)
|
|
||||||
}
|
|
||||||
apply.SetVal("wp-plugins", func(h *wp.Handle) {
|
|
||||||
UsePlugins(h)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package hiddenlogin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HiddenLogin(h *wp.Handle) {
|
|
||||||
h.AddActionFilter(widgets.Meta, func(h *wp.Handle, s string, args ...any) string {
|
|
||||||
return str.Replace(s, map[string]string{
|
|
||||||
`<li><a href="/wp-login.php">登录</a></li>`: "",
|
|
||||||
`<li><a href="/feed">登录</a></li>`: "",
|
|
||||||
`<li><a href="/comments/feed">登录</a></li>`: "",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package tests
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Tt(h *wp.Handle) {
|
|
||||||
h.HookHandle(constraints.PipeMiddleware, constraints.AllScene, func(call wp.HandleCall) (wp.HandleCall, bool) {
|
|
||||||
return call, false
|
|
||||||
})
|
|
||||||
/*h.PushPipeHook(constraints.Home, func(pipe wp.Pipe) (wp.Pipe, bool) {
|
|
||||||
return wp.Pipe{}, false
|
|
||||||
})*/
|
|
||||||
//h.DeletePipe(constraints.Home, constraints.PipeMiddleware)
|
|
||||||
h.ReplacePipe(constraints.Home, constraints.PipeMiddleware, wp.NewPipe("log", 500, func(next wp.HandleFn[*wp.Handle], h *wp.Handle) {
|
|
||||||
fmt.Println("ffff")
|
|
||||||
next(h)
|
|
||||||
fmt.Println("iiiii")
|
|
||||||
}))
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package wpposts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PasswordProjectTitle(post *models.Posts) {
|
|
||||||
post.PostTitle = fmt.Sprintf("密码保护:%s", post.PostTitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PasswdProjectContent(post *models.Posts) {
|
|
||||||
format := `
|
|
||||||
<form action="/login" class="post-password-form" method="post">
|
|
||||||
<p>此内容受密码保护。如需查阅,请在下列字段中输入您的密码。</p>
|
|
||||||
<p><label for="pwbox-%d">密码: <input name="post_password" id="pwbox-%d" type="password" size="20"></label> <input type="submit" name="Submit" value="提交"></p>
|
|
||||||
</form>`
|
|
||||||
post.PostContent = fmt.Sprintf(format, post.Id, post.Id)
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/actions"
|
|
||||||
"github.com/fthvgb1/wp-go/app/middleware"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/gin-contrib/gzip"
|
|
||||||
"github.com/gin-contrib/pprof"
|
|
||||||
"github.com/gin-contrib/sessions"
|
|
||||||
"github.com/gin-contrib/sessions/cookie"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GinSetter func(*gin.Engine)
|
|
||||||
|
|
||||||
var setters mockmap.Map[string, GinSetter]
|
|
||||||
|
|
||||||
var setterHooks []func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool)
|
|
||||||
|
|
||||||
// SetGinAction 方便插件在init时使用
|
|
||||||
func SetGinAction(name string, hook GinSetter, orders ...float64) {
|
|
||||||
setters.Set(name, hook, orders...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HookGinSetter(fn func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool)) {
|
|
||||||
setterHooks = append(setterHooks, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DelGinSetter 方便插件在init时使用
|
|
||||||
func DelGinSetter(name string) {
|
|
||||||
setterHooks = append(setterHooks, func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool) {
|
|
||||||
return item, item.Name != name
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetupRouter() *gin.Engine {
|
|
||||||
// Disable Console Color
|
|
||||||
// gin.DisableConsoleColor()
|
|
||||||
r := gin.New()
|
|
||||||
c := config.GetConfig()
|
|
||||||
SetGinAction("initTrustIp", func(r *gin.Engine) {
|
|
||||||
if len(c.TrustIps) > 0 {
|
|
||||||
err := r.SetTrustedProxies(c.TrustIps)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 99.5)
|
|
||||||
|
|
||||||
SetGinAction("setTemplate", func(r *gin.Engine) {
|
|
||||||
r.HTMLRender = theme.BuildTemplate()
|
|
||||||
wpconfig.SetTemplateFs(theme.TemplateFs)
|
|
||||||
}, 90.5)
|
|
||||||
|
|
||||||
siteFlowLimitMiddleware, siteFlow := middleware.FlowLimit(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
|
|
||||||
reload.Append(func() {
|
|
||||||
c = config.GetConfig()
|
|
||||||
siteFlow(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
|
|
||||||
}, "site-flowLimit-config")
|
|
||||||
|
|
||||||
SetGinAction("setGlobalMiddleware", func(r *gin.Engine) {
|
|
||||||
r.Use(
|
|
||||||
gin.Logger(),
|
|
||||||
middleware.ValidateServerNames(),
|
|
||||||
middleware.RecoverAndSendMail(gin.DefaultErrorWriter),
|
|
||||||
siteFlowLimitMiddleware,
|
|
||||||
middleware.SetStaticFileCache,
|
|
||||||
)
|
|
||||||
}, 88.5)
|
|
||||||
|
|
||||||
SetGinAction("setGzip", func(r *gin.Engine) {
|
|
||||||
//gzip 因为一般会用nginx做反代时自动使用gzip,所以go这边本身可以不用
|
|
||||||
if c.Gzip {
|
|
||||||
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{
|
|
||||||
"/wp-includes/", "/wp-content/",
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
}, 87.6)
|
|
||||||
|
|
||||||
SetGinAction("setWpDir", func(r *gin.Engine) {
|
|
||||||
if c.WpDir == "" {
|
|
||||||
panic("wordpress path can't be empty")
|
|
||||||
}
|
|
||||||
r.Static("/wp-content/uploads", str.Join(c.WpDir, "/wp-content/uploads"))
|
|
||||||
r.Static("/wp-content/themes", str.Join(c.WpDir, "/wp-content/themes"))
|
|
||||||
r.Static("/wp-content/plugins", str.Join(c.WpDir, "/wp-content/plugins"))
|
|
||||||
r.Static("/wp-includes/css", str.Join(c.WpDir, "/wp-includes/css"))
|
|
||||||
r.Static("/wp-includes/fonts", str.Join(c.WpDir, "/wp-includes/fonts"))
|
|
||||||
r.Static("/wp-includes/js", str.Join(c.WpDir, "/wp-includes/js"))
|
|
||||||
}, 86.1)
|
|
||||||
|
|
||||||
SetGinAction("setSession", func(r *gin.Engine) {
|
|
||||||
store := cookie.NewStore([]byte("secret"))
|
|
||||||
r.Use(sessions.Sessions("go-wp", store))
|
|
||||||
}, 85.1)
|
|
||||||
|
|
||||||
SetGinAction("setRoute", func(r *gin.Engine) {
|
|
||||||
r.GET("/", actions.Feed, middleware.SearchLimit(c.SingleIpSearchNum),
|
|
||||||
actions.ThemeHook(constraints.Home))
|
|
||||||
r.GET("/page/:page", actions.ThemeHook(constraints.Home))
|
|
||||||
r.GET("/p/category/:category", actions.ThemeHook(constraints.Category))
|
|
||||||
r.GET("/p/category/:category/page/:page", actions.ThemeHook(constraints.Category))
|
|
||||||
r.GET("/p/tag/:tag", actions.ThemeHook(constraints.Tag))
|
|
||||||
r.GET("/p/tag/:tag/page/:page", actions.ThemeHook(constraints.Tag))
|
|
||||||
r.GET("/p/date/:year/:month", actions.ThemeHook(constraints.Archive))
|
|
||||||
r.GET("/p/date/:year/:month/page/:page", actions.ThemeHook(constraints.Archive))
|
|
||||||
r.GET("/p/author/:author", actions.ThemeHook(constraints.Author))
|
|
||||||
r.GET("/p/author/:author/page/:page", actions.ThemeHook(constraints.Author))
|
|
||||||
r.POST("/login", actions.Login)
|
|
||||||
r.GET("/p/:id", actions.ThemeHook(constraints.Detail))
|
|
||||||
r.GET("/p/:id/comment-page-:page", actions.ThemeHook(constraints.Detail))
|
|
||||||
r.GET("/p/:id/feed", actions.PostFeed)
|
|
||||||
r.GET("/feed", actions.SiteFeed)
|
|
||||||
r.GET("/comments/feed", actions.CommentsFeed)
|
|
||||||
commentMiddleWare, _ := middleware.FlowLimit(c.MaxRequestSleepNum, 5, c.CacheTime.SleepTime)
|
|
||||||
r.POST("/comment", commentMiddleWare, actions.PostComment)
|
|
||||||
|
|
||||||
r.NoRoute(actions.ThemeHook(constraints.NoRoute))
|
|
||||||
}, 84.6)
|
|
||||||
|
|
||||||
SetGinAction("setpprof", func(r *gin.Engine) {
|
|
||||||
if c.Pprof != "" {
|
|
||||||
pprof.Register(r, c.Pprof)
|
|
||||||
}
|
|
||||||
}, 80.8)
|
|
||||||
|
|
||||||
for _, hook := range setterHooks {
|
|
||||||
setters = slice.FilterAndMap(setters, hook)
|
|
||||||
}
|
|
||||||
|
|
||||||
slice.SimpleSort(setters, slice.DESC, func(t mockmap.Item[string, GinSetter]) float64 {
|
|
||||||
return t.Order
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, fn := range setters {
|
|
||||||
fn.Value(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
package theme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"github.com/fthvgb1/wp-go/multipTemplate"
|
|
||||||
"github.com/fthvgb1/wp-go/safety"
|
|
||||||
"html/template"
|
|
||||||
"io/fs"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed *[^.go]
|
|
||||||
var TemplateFs embed.FS
|
|
||||||
|
|
||||||
var templates = safety.NewMap[string, *template.Template]() //方便外部获取模板render后的字符串,不然在gin中获取不了
|
|
||||||
|
|
||||||
var multiple *multipTemplate.MultipleFsTemplate
|
|
||||||
|
|
||||||
func BuildTemplate() *multipTemplate.MultipleFsTemplate {
|
|
||||||
if multiple != nil {
|
|
||||||
tt := multipTemplate.NewFsTemplate(TemplateFs)
|
|
||||||
commonTemplate(tt)
|
|
||||||
for k, v := range map[string]*template.Template(any(tt.Template).(multipTemplate.TemplateMaps)) {
|
|
||||||
multiple.Template.Store(k, v)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multiple = multipTemplate.NewFsTemplates(TemplateFs, templates)
|
|
||||||
commonTemplate(multiple)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*t.AddTemplate("twentyfifteen/*[^layout]/*.gohtml", FuncMap(), "twentyfifteen/layout/*.gohtml","wp/template.gohtml"). //单个主题设置
|
|
||||||
AddTemplate("twentyseventeen/*[^layout]/*.gohtml", FuncMap(), "twentyseventeen/layout/*.gohtml","wp/template.gohtml")*/
|
|
||||||
return multiple
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMultipleTemplate() *multipTemplate.MultipleFsTemplate {
|
|
||||||
if multiple == nil {
|
|
||||||
BuildTemplate()
|
|
||||||
}
|
|
||||||
return multiple
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTemplate(name string) (*template.Template, bool) {
|
|
||||||
t, ok := templates.Load(name)
|
|
||||||
return t, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// 所有主题模板通用设置
|
|
||||||
func commonTemplate(t *multipTemplate.MultipleFsTemplate) {
|
|
||||||
m, err := fs.Glob(t.Fs, "*/posts/*.gohtml")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
funMap := FuncMap()
|
|
||||||
for _, main := range m {
|
|
||||||
file := filepath.Base(main)
|
|
||||||
dir := strings.Split(main, "/")[0]
|
|
||||||
templ := template.Must(template.New(file).Funcs(funMap).ParseFS(t.Fs, main, filepath.Join(dir, "layout/*.gohtml"), "wp/template.gohtml"))
|
|
||||||
t.SetTemplate(main, templ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsTemplateDirExists(tml string) bool {
|
|
||||||
arr, err := TemplateFs.ReadDir(tml)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(arr) > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsTemplateExists(tml string) bool {
|
|
||||||
t, ok := templates.Load(tml)
|
|
||||||
return ok && t != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetTemplate(name string, val *template.Template) {
|
|
||||||
templates.Store(name, val)
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package theme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/fthvgb1/wp-go/safety"
|
|
||||||
)
|
|
||||||
|
|
||||||
var themeMap = safety.NewMap[string, func(*wp.Handle)]()
|
|
||||||
|
|
||||||
func AddTheme(name string, fn func(handle *wp.Handle)) {
|
|
||||||
themeMap.Store(name, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DelTheme(name string) {
|
|
||||||
themeMap.Delete(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTheme(name string) (func(*wp.Handle), bool) {
|
|
||||||
return themeMap.Load(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsThemeHookFuncExist(name string) bool {
|
|
||||||
_, ok := themeMap.Load(name)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func Hook(themeName string, h *wp.Handle) {
|
|
||||||
fn, ok := themeMap.Load(themeName)
|
|
||||||
if ok && fn != nil {
|
|
||||||
fn(h)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
panic(str.Join("theme ", themeName, " don't exist"))
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package theme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"html/template"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func postsFn(fn func(models.Posts) string, a models.Posts) string {
|
|
||||||
return fn(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FuncMap() template.FuncMap {
|
|
||||||
return template.FuncMap{
|
|
||||||
"unescaped": func(s string) any {
|
|
||||||
return template.HTML(s)
|
|
||||||
},
|
|
||||||
"dateCh": func(t time.Time) any {
|
|
||||||
return t.Format("2006年 01月 02日")
|
|
||||||
},
|
|
||||||
"timeFormat": func(t time.Time, format string) any {
|
|
||||||
return t.Format(format)
|
|
||||||
},
|
|
||||||
"getOption": func(k string) string {
|
|
||||||
return wpconfig.GetOption(k)
|
|
||||||
},
|
|
||||||
"getLang": wpconfig.GetLang,
|
|
||||||
"postsFn": postsFn,
|
|
||||||
"exec": func(fn func() string) template.HTML {
|
|
||||||
return template.HTML(fn())
|
|
||||||
},
|
|
||||||
"callFuncString": func(fn func(string) string, s string) template.HTML {
|
|
||||||
return template.HTML(fn(s))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package theme
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/twentyfifteen"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/twentyseventeen"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitTheme() {
|
|
||||||
AddTheme(twentyfifteen.ThemeName, twentyfifteen.Hook)
|
|
||||||
AddTheme(twentyseventeen.ThemeName, twentyseventeen.Hook)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCurrentTheme() string {
|
|
||||||
themeName := config.GetConfig().Theme
|
|
||||||
if themeName == "" {
|
|
||||||
themeName = wpconfig.GetOption("template")
|
|
||||||
}
|
|
||||||
if !IsTemplateDirExists(themeName) {
|
|
||||||
themeName = "twentyfifteen"
|
|
||||||
}
|
|
||||||
return themeName
|
|
||||||
}
|
|
@ -1,614 +0,0 @@
|
|||||||
package twentyfifteen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func colorSchemeCss(h *wp.Handle) string {
|
|
||||||
s := slice.Filter([]string{calColorSchemeCss(h), calSidebarTextColorCss(h), calHeaderBackgroundColorCss(h)}, func(s string, i int) bool {
|
|
||||||
return s != ""
|
|
||||||
})
|
|
||||||
if len(s) < 1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(`<style id='%s-inline-css'%s>\n%s\n</style>`, "twentyfifteen-style", "", strings.Join(s, "\n"))
|
|
||||||
}
|
|
||||||
func calColorSchemeCss(h *wp.Handle) (r string) {
|
|
||||||
color := getColorScheme(h)
|
|
||||||
if "default" == h.CommonThemeMods().ColorScheme || len(color) < 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
textColorRgb := slice.ToAnySlice(Hex2RgbUint8(color[3]))
|
|
||||||
sidebarTextColorRgb := Hex2RgbUint8(color[4])
|
|
||||||
sidebarTextColorRgbs := slice.ToAnySlice(sidebarTextColorRgb)
|
|
||||||
colors := map[string]string{
|
|
||||||
"background_color": color[0],
|
|
||||||
"header_background_color": color[1],
|
|
||||||
"box_background_color": color[2],
|
|
||||||
"textcolor": color[3],
|
|
||||||
"secondary_textcolor": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", textColorRgb...),
|
|
||||||
"border_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", textColorRgb...),
|
|
||||||
"border_focus_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", textColorRgb...),
|
|
||||||
"sidebar_textcolor": color[4],
|
|
||||||
"sidebar_border_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", sidebarTextColorRgbs...),
|
|
||||||
"sidebar_border_focus_color": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", sidebarTextColorRgbs...),
|
|
||||||
"secondary_sidebar_textcolor": fmt.Sprintf("rgba(%d, %d, %d, 0.7)", sidebarTextColorRgbs...),
|
|
||||||
"meta_box_background_color": color[5],
|
|
||||||
}
|
|
||||||
r = cssTemplate
|
|
||||||
for k, v := range colors {
|
|
||||||
r = strings.ReplaceAll(r, fmt.Sprintf(`{$colors['%s']}`, k), v)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func calSidebarTextColorCss(h *wp.Handle) (r string) {
|
|
||||||
colors := getColorScheme(h)
|
|
||||||
themeMods := h.CommonThemeMods()
|
|
||||||
if themeMods.SidebarTextcolor == "" || themeMods.SidebarTextcolor == colors[4] {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
linkColorRgb := Hex2RgbUint8(themeMods.SidebarTextcolor)
|
|
||||||
color := slice.ToAnySlice(linkColorRgb)
|
|
||||||
textColor := fmt.Sprintf(`rgba( %[1]v, %[2]v, %[3]v, 0.7)`, color...)
|
|
||||||
borderColor := fmt.Sprintf(`rgba( %[1]v, %[2]v, %[3]v, 0.1)`, color...)
|
|
||||||
borderFocusColor := fmt.Sprintf(`rgba( %[1]v, %[2]v, %[3]v, 0.3)`, color...)
|
|
||||||
r = fmt.Sprintf(sidebarTextColorTemplate, themeMods.SidebarTextcolor, textColor, borderColor, borderFocusColor)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func calHeaderBackgroundColorCss(h *wp.Handle) (r string) {
|
|
||||||
colors := getColorScheme(h)
|
|
||||||
themeMods := h.CommonThemeMods()
|
|
||||||
if themeMods.HeaderBackgroundColor == "" || themeMods.HeaderBackgroundColor == colors[1] {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r = fmt.Sprintf(headerBackgroundColorCssTemplate, themeMods.HeaderBackgroundColor, themeMods.HeaderBackgroundColor)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getColorScheme(h *wp.Handle) (r []string) {
|
|
||||||
x, ok := colorscheme[h.CommonThemeMods().ColorScheme]
|
|
||||||
if ok {
|
|
||||||
r = x.Colors
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type ColorScheme struct {
|
|
||||||
Label string `json:"label,omitempty"`
|
|
||||||
Colors []string `json:"colors,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Hex2RgbUint8(color string) []uint8 {
|
|
||||||
var r []uint8
|
|
||||||
color = strings.TrimLeft(color, "#")
|
|
||||||
fn := func(s string) uint8 {
|
|
||||||
n, _ := strconv.ParseInt(s, 16, 0)
|
|
||||||
return uint8(n)
|
|
||||||
}
|
|
||||||
switch len(color) {
|
|
||||||
case 3:
|
|
||||||
r = []uint8{color[0], color[1], color[2]}
|
|
||||||
case 6:
|
|
||||||
r = []uint8{fn(color[:2]), fn(color[2:4]), fn(color[4:])}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
var cssTemplate = `
|
|
||||||
/* Color Scheme */
|
|
||||||
|
|
||||||
/* Background Color */
|
|
||||||
body {
|
|
||||||
background-color: {$colors['background_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Background Color */
|
|
||||||
body:before,
|
|
||||||
.site-header {
|
|
||||||
background-color: {$colors['header_background_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Box Background Color */
|
|
||||||
.post-navigation,
|
|
||||||
.pagination,
|
|
||||||
.secondary,
|
|
||||||
.site-footer,
|
|
||||||
.hentry,
|
|
||||||
.page-header,
|
|
||||||
.page-content,
|
|
||||||
.comments-area,
|
|
||||||
.widecolumn {
|
|
||||||
background-color: {$colors['box_background_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Box Background Color */
|
|
||||||
button,
|
|
||||||
input[type="button"],
|
|
||||||
input[type="reset"],
|
|
||||||
input[type="submit"],
|
|
||||||
.pagination .prev,
|
|
||||||
.pagination .next,
|
|
||||||
.widget_calendar tbody a,
|
|
||||||
.widget_calendar tbody a:hover,
|
|
||||||
.widget_calendar tbody a:focus,
|
|
||||||
.page-links a,
|
|
||||||
.page-links a:hover,
|
|
||||||
.page-links a:focus,
|
|
||||||
.sticky-post {
|
|
||||||
color: {$colors['box_background_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Text Color */
|
|
||||||
button,
|
|
||||||
input[type="button"],
|
|
||||||
input[type="reset"],
|
|
||||||
input[type="submit"],
|
|
||||||
.pagination .prev,
|
|
||||||
.pagination .next,
|
|
||||||
.widget_calendar tbody a,
|
|
||||||
.page-links a,
|
|
||||||
.sticky-post {
|
|
||||||
background-color: {$colors['textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Text Color */
|
|
||||||
body,
|
|
||||||
blockquote cite,
|
|
||||||
blockquote small,
|
|
||||||
a,
|
|
||||||
.dropdown-toggle:after,
|
|
||||||
.image-navigation a:hover,
|
|
||||||
.image-navigation a:focus,
|
|
||||||
.comment-navigation a:hover,
|
|
||||||
.comment-navigation a:focus,
|
|
||||||
.widget-title,
|
|
||||||
.entry-footer a:hover,
|
|
||||||
.entry-footer a:focus,
|
|
||||||
.comment-metadata a:hover,
|
|
||||||
.comment-metadata a:focus,
|
|
||||||
.pingback .edit-link a:hover,
|
|
||||||
.pingback .edit-link a:focus,
|
|
||||||
.comment-list .reply a:hover,
|
|
||||||
.comment-list .reply a:focus,
|
|
||||||
.site-info a:hover,
|
|
||||||
.site-info a:focus {
|
|
||||||
color: {$colors['textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Text Color */
|
|
||||||
.entry-content a,
|
|
||||||
.entry-summary a,
|
|
||||||
.page-content a,
|
|
||||||
.comment-content a,
|
|
||||||
.pingback .comment-body > a,
|
|
||||||
.author-description a,
|
|
||||||
.taxonomy-description a,
|
|
||||||
.textwidget a,
|
|
||||||
.entry-footer a:hover,
|
|
||||||
.comment-metadata a:hover,
|
|
||||||
.pingback .edit-link a:hover,
|
|
||||||
.comment-list .reply a:hover,
|
|
||||||
.site-info a:hover {
|
|
||||||
border-color: {$colors['textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Secondary Text Color */
|
|
||||||
button:hover,
|
|
||||||
button:focus,
|
|
||||||
input[type="button"]:hover,
|
|
||||||
input[type="button"]:focus,
|
|
||||||
input[type="reset"]:hover,
|
|
||||||
input[type="reset"]:focus,
|
|
||||||
input[type="submit"]:hover,
|
|
||||||
input[type="submit"]:focus,
|
|
||||||
.pagination .prev:hover,
|
|
||||||
.pagination .prev:focus,
|
|
||||||
.pagination .next:hover,
|
|
||||||
.pagination .next:focus,
|
|
||||||
.widget_calendar tbody a:hover,
|
|
||||||
.widget_calendar tbody a:focus,
|
|
||||||
.page-links a:hover,
|
|
||||||
.page-links a:focus {
|
|
||||||
background-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
|
|
||||||
background-color: {$colors['secondary_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Secondary Text Color */
|
|
||||||
blockquote,
|
|
||||||
a:hover,
|
|
||||||
a:focus,
|
|
||||||
.main-navigation .menu-item-description,
|
|
||||||
.post-navigation .meta-nav,
|
|
||||||
.post-navigation a:hover .post-title,
|
|
||||||
.post-navigation a:focus .post-title,
|
|
||||||
.image-navigation,
|
|
||||||
.image-navigation a,
|
|
||||||
.comment-navigation,
|
|
||||||
.comment-navigation a,
|
|
||||||
.widget,
|
|
||||||
.author-heading,
|
|
||||||
.entry-footer,
|
|
||||||
.entry-footer a,
|
|
||||||
.taxonomy-description,
|
|
||||||
.page-links > .page-links-title,
|
|
||||||
.entry-caption,
|
|
||||||
.comment-author,
|
|
||||||
.comment-metadata,
|
|
||||||
.comment-metadata a,
|
|
||||||
.pingback .edit-link,
|
|
||||||
.pingback .edit-link a,
|
|
||||||
.post-password-form label,
|
|
||||||
.comment-form label,
|
|
||||||
.comment-notes,
|
|
||||||
.comment-awaiting-moderation,
|
|
||||||
.logged-in-as,
|
|
||||||
.form-allowed-tags,
|
|
||||||
.no-comments,
|
|
||||||
.site-info,
|
|
||||||
.site-info a,
|
|
||||||
.wp-caption-text,
|
|
||||||
.gallery-caption,
|
|
||||||
.comment-list .reply a,
|
|
||||||
.widecolumn label,
|
|
||||||
.widecolumn .mu_register label {
|
|
||||||
color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
|
|
||||||
color: {$colors['secondary_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Secondary Text Color */
|
|
||||||
blockquote,
|
|
||||||
.logged-in-as a:hover,
|
|
||||||
.comment-author a:hover {
|
|
||||||
border-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
|
|
||||||
border-color: {$colors['secondary_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Border Color */
|
|
||||||
hr,
|
|
||||||
.dropdown-toggle:hover,
|
|
||||||
.dropdown-toggle:focus {
|
|
||||||
background-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
|
|
||||||
background-color: {$colors['border_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Border Color */
|
|
||||||
pre,
|
|
||||||
abbr[title],
|
|
||||||
table,
|
|
||||||
th,
|
|
||||||
td,
|
|
||||||
input,
|
|
||||||
textarea,
|
|
||||||
.main-navigation ul,
|
|
||||||
.main-navigation li,
|
|
||||||
.post-navigation,
|
|
||||||
.post-navigation div + div,
|
|
||||||
.pagination,
|
|
||||||
.comment-navigation,
|
|
||||||
.widget li,
|
|
||||||
.widget_categories .children,
|
|
||||||
.widget_nav_menu .sub-menu,
|
|
||||||
.widget_pages .children,
|
|
||||||
.site-header,
|
|
||||||
.site-footer,
|
|
||||||
.hentry + .hentry,
|
|
||||||
.author-info,
|
|
||||||
.entry-content .page-links a,
|
|
||||||
.page-links > span,
|
|
||||||
.page-header,
|
|
||||||
.comments-area,
|
|
||||||
.comment-list + .comment-respond,
|
|
||||||
.comment-list article,
|
|
||||||
.comment-list .pingback,
|
|
||||||
.comment-list .trackback,
|
|
||||||
.comment-list .reply a,
|
|
||||||
.no-comments {
|
|
||||||
border-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
|
|
||||||
border-color: {$colors['border_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Border Focus Color */
|
|
||||||
a:focus,
|
|
||||||
button:focus,
|
|
||||||
input:focus {
|
|
||||||
outline-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
|
|
||||||
outline-color: {$colors['border_focus_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus,
|
|
||||||
textarea:focus {
|
|
||||||
border-color: {$colors['textcolor']}; /* Fallback for IE7 and IE8 */
|
|
||||||
border-color: {$colors['border_focus_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Link Color */
|
|
||||||
.secondary-toggle:before {
|
|
||||||
color: {$colors['sidebar_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-title a,
|
|
||||||
.site-description {
|
|
||||||
color: {$colors['sidebar_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Text Color */
|
|
||||||
.site-title a:hover,
|
|
||||||
.site-title a:focus {
|
|
||||||
color: {$colors['secondary_sidebar_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Border Color */
|
|
||||||
.secondary-toggle {
|
|
||||||
border-color: {$colors['sidebar_textcolor']}; /* Fallback for IE7 and IE8 */
|
|
||||||
border-color: {$colors['sidebar_border_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Border Focus Color */
|
|
||||||
.secondary-toggle:hover,
|
|
||||||
.secondary-toggle:focus {
|
|
||||||
border-color: {$colors['sidebar_textcolor']}; /* Fallback for IE7 and IE8 */
|
|
||||||
border-color: {$colors['sidebar_border_focus_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-title a {
|
|
||||||
outline-color: {$colors['sidebar_textcolor']}; /* Fallback for IE7 and IE8 */
|
|
||||||
outline-color: {$colors['sidebar_border_focus_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Meta Background Color */
|
|
||||||
.entry-footer {
|
|
||||||
background-color: {$colors['meta_box_background_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 38.75em) {
|
|
||||||
/* Main Text Color */
|
|
||||||
.page-header {
|
|
||||||
border-color: {$colors['textcolor']};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 59.6875em) {
|
|
||||||
/* Make sure its transparent on desktop */
|
|
||||||
.site-header,
|
|
||||||
.secondary {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Background Color */
|
|
||||||
.widget button,
|
|
||||||
.widget input[type="button"],
|
|
||||||
.widget input[type="reset"],
|
|
||||||
.widget input[type="submit"],
|
|
||||||
.widget_calendar tbody a,
|
|
||||||
.widget_calendar tbody a:hover,
|
|
||||||
.widget_calendar tbody a:focus {
|
|
||||||
color: {$colors['header_background_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Link Color */
|
|
||||||
.secondary a,
|
|
||||||
.dropdown-toggle:after,
|
|
||||||
.widget-title,
|
|
||||||
.widget blockquote cite,
|
|
||||||
.widget blockquote small {
|
|
||||||
color: {$colors['sidebar_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget button,
|
|
||||||
.widget input[type="button"],
|
|
||||||
.widget input[type="reset"],
|
|
||||||
.widget input[type="submit"],
|
|
||||||
.widget_calendar tbody a {
|
|
||||||
background-color: {$colors['sidebar_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
.textwidget a {
|
|
||||||
border-color: {$colors['sidebar_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Text Color */
|
|
||||||
.secondary a:hover,
|
|
||||||
.secondary a:focus,
|
|
||||||
.main-navigation .menu-item-description,
|
|
||||||
.widget,
|
|
||||||
.widget blockquote,
|
|
||||||
.widget .wp-caption-text,
|
|
||||||
.widget .gallery-caption {
|
|
||||||
color: {$colors['secondary_sidebar_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget button:hover,
|
|
||||||
.widget button:focus,
|
|
||||||
.widget input[type="button"]:hover,
|
|
||||||
.widget input[type="button"]:focus,
|
|
||||||
.widget input[type="reset"]:hover,
|
|
||||||
.widget input[type="reset"]:focus,
|
|
||||||
.widget input[type="submit"]:hover,
|
|
||||||
.widget input[type="submit"]:focus,
|
|
||||||
.widget_calendar tbody a:hover,
|
|
||||||
.widget_calendar tbody a:focus {
|
|
||||||
background-color: {$colors['secondary_sidebar_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget blockquote {
|
|
||||||
border-color: {$colors['secondary_sidebar_textcolor']};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebar Border Color */
|
|
||||||
.main-navigation ul,
|
|
||||||
.main-navigation li,
|
|
||||||
.widget input,
|
|
||||||
.widget textarea,
|
|
||||||
.widget table,
|
|
||||||
.widget th,
|
|
||||||
.widget td,
|
|
||||||
.widget pre,
|
|
||||||
.widget li,
|
|
||||||
.widget_categories .children,
|
|
||||||
.widget_nav_menu .sub-menu,
|
|
||||||
.widget_pages .children,
|
|
||||||
.widget abbr[title] {
|
|
||||||
border-color: {$colors['sidebar_border_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-toggle:hover,
|
|
||||||
.dropdown-toggle:focus,
|
|
||||||
.widget hr {
|
|
||||||
background-color: {$colors['sidebar_border_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget input:focus,
|
|
||||||
.widget textarea:focus {
|
|
||||||
border-color: {$colors['sidebar_border_focus_color']};
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar a:focus,
|
|
||||||
.dropdown-toggle:focus {
|
|
||||||
outline-color: {$colors['sidebar_border_focus_color']};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
var headerBackgroundColorCssTemplate = `
|
|
||||||
/* Custom Header Background Color */
|
|
||||||
body:before,
|
|
||||||
.site-header {
|
|
||||||
background-color: %s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 59.6875em) {
|
|
||||||
.site-header,
|
|
||||||
.secondary {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget button,
|
|
||||||
.widget input[type="button"],
|
|
||||||
.widget input[type="reset"],
|
|
||||||
.widget input[type="submit"],
|
|
||||||
.widget_calendar tbody a,
|
|
||||||
.widget_calendar tbody a:hover,
|
|
||||||
.widget_calendar tbody a:focus {
|
|
||||||
color: %s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
var sidebarTextColorTemplate = `
|
|
||||||
/* Custom Sidebar Text Color */
|
|
||||||
.site-title a,
|
|
||||||
.site-description,
|
|
||||||
.secondary-toggle:before {
|
|
||||||
color: %[1]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-title a:hover,
|
|
||||||
.site-title a:focus {
|
|
||||||
color: %[1]v; /* Fallback for IE7 and IE8 */
|
|
||||||
color: %[2]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary-toggle {
|
|
||||||
border-color: %[1]v; /* Fallback for IE7 and IE8 */
|
|
||||||
border-color: %[3]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary-toggle:hover,
|
|
||||||
.secondary-toggle:focus {
|
|
||||||
border-color: %[1]v; /* Fallback for IE7 and IE8 */
|
|
||||||
border-color: %[4]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-title a {
|
|
||||||
outline-color: %[1]v; /* Fallback for IE7 and IE8 */
|
|
||||||
outline-color: %[4]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 59.6875em) {
|
|
||||||
.secondary a,
|
|
||||||
.dropdown-toggle:after,
|
|
||||||
.widget-title,
|
|
||||||
.widget blockquote cite,
|
|
||||||
.widget blockquote small {
|
|
||||||
color: %[1]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget button,
|
|
||||||
.widget input[type="button"],
|
|
||||||
.widget input[type="reset"],
|
|
||||||
.widget input[type="submit"],
|
|
||||||
.widget_calendar tbody a {
|
|
||||||
background-color: %[1]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textwidget a {
|
|
||||||
border-color: %[1]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary a:hover,
|
|
||||||
.secondary a:focus,
|
|
||||||
.main-navigation .menu-item-description,
|
|
||||||
.widget,
|
|
||||||
.widget blockquote,
|
|
||||||
.widget .wp-caption-text,
|
|
||||||
.widget .gallery-caption {
|
|
||||||
color: %[2]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget button:hover,
|
|
||||||
.widget button:focus,
|
|
||||||
.widget input[type="button"]:hover,
|
|
||||||
.widget input[type="button"]:focus,
|
|
||||||
.widget input[type="reset"]:hover,
|
|
||||||
.widget input[type="reset"]:focus,
|
|
||||||
.widget input[type="submit"]:hover,
|
|
||||||
.widget input[type="submit"]:focus,
|
|
||||||
.widget_calendar tbody a:hover,
|
|
||||||
.widget_calendar tbody a:focus {
|
|
||||||
background-color: %[2]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget blockquote {
|
|
||||||
border-color: %[2]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-navigation ul,
|
|
||||||
.main-navigation li,
|
|
||||||
.secondary-toggle,
|
|
||||||
.widget input,
|
|
||||||
.widget textarea,
|
|
||||||
.widget table,
|
|
||||||
.widget th,
|
|
||||||
.widget td,
|
|
||||||
.widget pre,
|
|
||||||
.widget li,
|
|
||||||
.widget_categories .children,
|
|
||||||
.widget_nav_menu .sub-menu,
|
|
||||||
.widget_pages .children,
|
|
||||||
.widget abbr[title] {
|
|
||||||
border-color: %[3]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-toggle:hover,
|
|
||||||
.dropdown-toggle:focus,
|
|
||||||
.widget hr {
|
|
||||||
background-color: %[3]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget input:focus,
|
|
||||||
.widget textarea:focus {
|
|
||||||
border-color: %[4]v;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar a:focus,
|
|
||||||
.dropdown-toggle:focus {
|
|
||||||
outline-color: %[4]v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
@ -1,65 +0,0 @@
|
|||||||
package twentyfifteen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/helper"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/maps"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var postx = map[string]string{
|
|
||||||
"left": "left",
|
|
||||||
"right": "right",
|
|
||||||
"center": "center",
|
|
||||||
}
|
|
||||||
var posty = map[string]string{
|
|
||||||
"top": "top",
|
|
||||||
"bottom": "bottom",
|
|
||||||
"center": "center",
|
|
||||||
}
|
|
||||||
var size = map[string]string{
|
|
||||||
"auto": "auto",
|
|
||||||
"contain": "contain",
|
|
||||||
"cover": "cover",
|
|
||||||
}
|
|
||||||
var repeat = map[string]string{
|
|
||||||
"repeat-x": "repeat-x",
|
|
||||||
"repeat-y": "repeat-y",
|
|
||||||
"repeat": "repeat",
|
|
||||||
"no-repeat": "no-repeat",
|
|
||||||
}
|
|
||||||
|
|
||||||
func CalCustomBackGround(h *wp.Handle) (r string) {
|
|
||||||
themeMods := h.CommonThemeMods()
|
|
||||||
if themeMods.BackgroundImage == "" && (themeMods.BackgroundColor == "" || themeMods.BackgroundColor == themesupport.CustomBackground.DefaultColor) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s := str.NewBuilder()
|
|
||||||
if themeMods.BackgroundImage != "" {
|
|
||||||
s.Sprintf(` background-image: url("%s");`, helper.CutUrlHost(themeMods.BackgroundImage))
|
|
||||||
}
|
|
||||||
backgroundPositionX := helper.Defaults(themeMods.BackgroundPositionX, themesupport.CustomBackground.DefaultPositionX)
|
|
||||||
backgroundPositionX = maps.WithDefaultVal(postx, backgroundPositionX, "left")
|
|
||||||
|
|
||||||
backgroundPositionY := helper.Defaults(themeMods.BackgroundPositionY, themesupport.CustomBackground.DefaultPositionY)
|
|
||||||
backgroundPositionY = maps.WithDefaultVal(posty, backgroundPositionY, "top")
|
|
||||||
positon := fmt.Sprintf(" background-position: %s %s;", backgroundPositionX, backgroundPositionY)
|
|
||||||
|
|
||||||
siz := helper.DefaultVal(themeMods.BackgroundSize, themesupport.CustomBackground.DefaultSize)
|
|
||||||
siz = maps.WithDefaultVal(size, siz, "auto")
|
|
||||||
siz = fmt.Sprintf(" background-size: %s;", siz)
|
|
||||||
|
|
||||||
repeats := helper.Defaults(themeMods.BackgroundRepeat, themesupport.CustomBackground.DefaultRepeat)
|
|
||||||
repeats = maps.WithDefaultVal(repeat, repeats, "repeat")
|
|
||||||
repeats = fmt.Sprintf(" background-repeat: %s;", repeats)
|
|
||||||
|
|
||||||
attachment := helper.Defaults(themeMods.BackgroundAttachment, themesupport.CustomBackground.DefaultAttachment)
|
|
||||||
attachment = helper.Or(attachment == "fixed", "fixed", "scroll")
|
|
||||||
attachment = fmt.Sprintf(" background-attachment: %s;", attachment)
|
|
||||||
s.WriteString(positon, siz, repeats, attachment)
|
|
||||||
r = fmt.Sprintf(`<style id="custom-background-css">
|
|
||||||
body.custom-background {%s}
|
|
||||||
</style>`, s.String())
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
package twentyfifteen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var style = `<style type="text/css" id="twentyfifteen-header-css">`
|
|
||||||
var defaultTextStyle = `.site-header {
|
|
||||||
padding-top: 14px;
|
|
||||||
padding-bottom: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-branding {
|
|
||||||
min-height: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 46.25em) {
|
|
||||||
.site-header {
|
|
||||||
padding-top: 21px;
|
|
||||||
padding-bottom: 21px;
|
|
||||||
}
|
|
||||||
.site-branding {
|
|
||||||
min-height: 56px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 55em) {
|
|
||||||
.site-header {
|
|
||||||
padding-top: 25px;
|
|
||||||
padding-bottom: 25px;
|
|
||||||
}
|
|
||||||
.site-branding {
|
|
||||||
min-height: 62px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 59.6875em) {
|
|
||||||
.site-header {
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
.site-branding {
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
var imgStyle = `.site-header {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* No shorthand so the Customizer can override individual properties.
|
|
||||||
* @see https://core.trac.wordpress.org/ticket/31460
|
|
||||||
*/
|
|
||||||
background-image: url("%s");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 50% 50%;
|
|
||||||
-webkit-background-size: cover;
|
|
||||||
-moz-background-size: cover;
|
|
||||||
-o-background-size: cover;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 59.6875em) {
|
|
||||||
body:before {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* No shorthand so the Customizer can override individual properties.
|
|
||||||
* @see https://core.trac.wordpress.org/ticket/31460
|
|
||||||
*/
|
|
||||||
background-image: url("%s");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 100% 50%;
|
|
||||||
-webkit-background-size: cover;
|
|
||||||
-moz-background-size: cover;
|
|
||||||
-o-background-size: cover;
|
|
||||||
background-size: cover;
|
|
||||||
border-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-header {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
var header = reload.Vars(constraints.Defaults, "twentyfifteen-customheader")
|
|
||||||
|
|
||||||
func calCustomHeaderImg(h *wp.Handle) (r string, rand bool) {
|
|
||||||
img, rand := h.GetCustomHeaderImg()
|
|
||||||
if img.Path == "" && h.DisplayHeaderText() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ss := str.NewBuilder()
|
|
||||||
ss.WriteString(style)
|
|
||||||
if img.Path == "" && !h.DisplayHeaderText() {
|
|
||||||
ss.WriteString(defaultTextStyle)
|
|
||||||
}
|
|
||||||
if img.Path != "" {
|
|
||||||
ss.Sprintf(imgStyle, img.Path, img.Path)
|
|
||||||
}
|
|
||||||
if !h.DisplayHeaderText() {
|
|
||||||
ss.WriteString(`.site-title,
|
|
||||||
.site-description {
|
|
||||||
clip: rect(1px, 1px, 1px, 1px);
|
|
||||||
position: absolute;
|
|
||||||
}`)
|
|
||||||
}
|
|
||||||
ss.WriteString("</style>")
|
|
||||||
r = ss.String()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func customHeader(h *wp.Handle) func() string {
|
|
||||||
return func() string {
|
|
||||||
headers := header.Load()
|
|
||||||
if headers == constraints.Defaults {
|
|
||||||
headerss, rand := calCustomHeaderImg(h)
|
|
||||||
headers = headerss
|
|
||||||
if !rand {
|
|
||||||
header.Store(headers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return headers
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
{{define "layout/footer"}}
|
|
||||||
<style>.wp-container-1 > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.wp-container-1 > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.wp-container-1 > .aligncenter { margin-left: auto !important; margin-right: auto !important; }</style>
|
|
||||||
<style>.wp-container-2 > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.wp-container-2 > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.wp-container-2 > .aligncenter { margin-left: auto !important; margin-right: auto !important; }</style>
|
|
||||||
<style>.wp-container-3 > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.wp-container-3 > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.wp-container-3 > .aligncenter { margin-left: auto !important; margin-right: auto !important; }</style>
|
|
||||||
<style>.wp-container-4 > .alignleft { float: left; margin-inline-start: 0; margin-inline-end: 2em; }.wp-container-4 > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }.wp-container-4 > .aligncenter { margin-left: auto !important; margin-right: auto !important; }</style>
|
|
||||||
<script src='/wp-content/themes/twentyfifteen/js/skip-link-focus-fix.js?ver=20141028' id='twentyfifteen-skip-link-focus-fix-js'></script>
|
|
||||||
<script id='twentyfifteen-script-js-extra'>
|
|
||||||
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>
|
|
||||||
|
|
||||||
{{template "common/footer" .}}
|
|
||||||
{{ block "footer" .}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
@ -1,5 +0,0 @@
|
|||||||
{{define "layout/sidebar" }}
|
|
||||||
<div id="widget-area" class="widget-area" role="complementary">
|
|
||||||
{{template "common/sidebarWidget" .}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
@ -1,77 +0,0 @@
|
|||||||
{{template "layout/base" .}}
|
|
||||||
|
|
||||||
{{define "content" }}
|
|
||||||
{{if .posts}}
|
|
||||||
<div id="primary" class="content-area">
|
|
||||||
<main id="main" class="site-main">
|
|
||||||
{{if .header}}
|
|
||||||
<header class="page-header">
|
|
||||||
<h1 class="page-title">
|
|
||||||
{{.header |unescaped}}
|
|
||||||
</h1>
|
|
||||||
</header>
|
|
||||||
{{end}}
|
|
||||||
{{ range $k,$v:=.posts}}
|
|
||||||
<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">
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<header class="entry-header">
|
|
||||||
<h2 class="entry-title">
|
|
||||||
<a href="/p/{{$v.Id}}" rel="bookmark">{{$v.PostTitle}}</a>
|
|
||||||
</h2>
|
|
||||||
</header>
|
|
||||||
<!-- .entry-header -->
|
|
||||||
|
|
||||||
<div class="entry-content">
|
|
||||||
{{$v.PostContent|unescaped}}
|
|
||||||
</div><!-- .entry-content -->
|
|
||||||
|
|
||||||
<footer class="entry-footer">
|
|
||||||
{{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>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if $v.TagsHtml}}
|
|
||||||
<span class="tags-links">
|
|
||||||
<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>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</footer><!-- .entry-footer -->
|
|
||||||
|
|
||||||
</article><!-- #post-{{$v.Id}} -->
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{template "layout/page" .}}
|
|
||||||
</main><!-- .site-main -->
|
|
||||||
</div>
|
|
||||||
{{else }}
|
|
||||||
{{template "layout/empty" .}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{end}}
|
|
@ -1,295 +0,0 @@
|
|||||||
package twentyfifteen
|
|
||||||
|
|
||||||
import "github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
|
|
||||||
type themeSupport struct {
|
|
||||||
CustomBackground customBackground `json:"custom-background"`
|
|
||||||
EditorColorPalette []EditorColorPalette `json:"editor-color-palette"`
|
|
||||||
EditorGradientPresets []EditorGradientPresets `json:"editor-gradient-presets"`
|
|
||||||
}
|
|
||||||
type customBackground struct {
|
|
||||||
DefaultImage string `json:"default-image"`
|
|
||||||
DefaultPreset string `json:"default-preset"`
|
|
||||||
DefaultPositionX string `json:"default-position-x"`
|
|
||||||
DefaultPositionY string `json:"default-position-y"`
|
|
||||||
DefaultSize string `json:"default-size"`
|
|
||||||
DefaultRepeat string `json:"default-repeat"`
|
|
||||||
DefaultAttachment string `json:"default-attachment"`
|
|
||||||
DefaultColor string `json:"default-color"`
|
|
||||||
WpHeadCallback string `json:"wp-head-callback"`
|
|
||||||
AdminHeadCallback string `json:"admin-head-callback"`
|
|
||||||
AdminPreviewCallback string `json:"admin-preview-callback"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type EditorColorPalette struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Slug string `json:"slug"`
|
|
||||||
Color string `json:"color"`
|
|
||||||
}
|
|
||||||
type EditorGradientPresets struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Slug string `json:"slug"`
|
|
||||||
Gradient string `json:"gradient"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var themesupport = themeSupport{
|
|
||||||
CustomBackground: customBackground{
|
|
||||||
DefaultImage: "",
|
|
||||||
DefaultPreset: "default",
|
|
||||||
DefaultPositionX: "left",
|
|
||||||
DefaultPositionY: "top",
|
|
||||||
DefaultSize: "auto",
|
|
||||||
DefaultRepeat: "repeat",
|
|
||||||
DefaultAttachment: "fixed",
|
|
||||||
DefaultColor: "f1f1f1",
|
|
||||||
WpHeadCallback: "_custom_background_cb",
|
|
||||||
AdminHeadCallback: "",
|
|
||||||
AdminPreviewCallback: "",
|
|
||||||
},
|
|
||||||
EditorColorPalette: []EditorColorPalette{
|
|
||||||
{
|
|
||||||
Name: "暗灰色",
|
|
||||||
Slug: "dark-gray",
|
|
||||||
Color: "#111",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "亮灰色",
|
|
||||||
Slug: "light-gray",
|
|
||||||
Color: "#f1f1f1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "白色",
|
|
||||||
Slug: "white",
|
|
||||||
Color: "#fff",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "黄色",
|
|
||||||
Slug: "yellow",
|
|
||||||
Color: "#f4ca16",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "暗棕色",
|
|
||||||
Slug: "dark-brown",
|
|
||||||
Color: "#352712",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "粉色",
|
|
||||||
Slug: "medium-pink",
|
|
||||||
Color: "#e53b51",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "浅粉色",
|
|
||||||
Slug: "light-pink",
|
|
||||||
Color: "#ffe5d1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "暗紫色",
|
|
||||||
Slug: "dark-purple",
|
|
||||||
Color: "#2e2256",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "紫色",
|
|
||||||
Slug: "purple",
|
|
||||||
Color: "#674970",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "蓝灰色",
|
|
||||||
Slug: "blue-gray",
|
|
||||||
Color: "#22313f",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "亮蓝色",
|
|
||||||
Slug: "bright-blue",
|
|
||||||
Color: "#55c3dc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "浅蓝色",
|
|
||||||
Slug: "light-blue",
|
|
||||||
Color: "#e9f2f9",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
EditorGradientPresets: []EditorGradientPresets{
|
|
||||||
{
|
|
||||||
Name: "Dark Gray Gradient",
|
|
||||||
Slug: "dark-gray-gradient-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(17,17,17,1) 0%, rgba(42,42,42,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Light Gray Gradient",
|
|
||||||
Slug: "light-gray-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(241,241,241,1) 0%, rgba(215,215,215,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "White Gradient",
|
|
||||||
Slug: "white-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(255,255,255,1) 0%, rgba(230,230,230,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Yellow Gradient",
|
|
||||||
Slug: "yellow-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(244,202,22,1) 0%, rgba(205,168,10,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Dark Brown Gradient",
|
|
||||||
Slug: "dark-brown-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(53,39,18,1) 0%, rgba(91,67,31,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Medium Pink Gradient",
|
|
||||||
Slug: "medium-pink-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(229,59,81,1) 0%, rgba(209,28,51,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Light Pink Gradient",
|
|
||||||
Slug: "light-pink-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(255,229,209,1) 0%, rgba(255,200,158,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Dark Purple Gradient",
|
|
||||||
Slug: "dark-purple-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(46,34,86,1) 0%, rgba(66,48,123,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Purple Gradient",
|
|
||||||
Slug: "purple-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(103,73,112,1) 0%, rgba(131,93,143,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Blue Gray Gradient",
|
|
||||||
Slug: "blue-gray-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(34,49,63,1) 0%, rgba(52,75,96,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Bright Blue Gradient",
|
|
||||||
Slug: "bright-blue-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(85,195,220,1) 0%, rgba(43,180,211,1) 100%)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Light Blue Gradient",
|
|
||||||
Slug: "light-blue-gradient",
|
|
||||||
Gradient: "linear-gradient(90deg, rgba(233,242,249,1) 0%, rgba(193,218,238,1) 100%)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var colorscheme = map[string]ColorScheme{
|
|
||||||
"default": {
|
|
||||||
Label: "Default",
|
|
||||||
Colors: []string{
|
|
||||||
"#f1f1f1",
|
|
||||||
"#ffffff",
|
|
||||||
"#ffffff",
|
|
||||||
"#333333",
|
|
||||||
"#333333",
|
|
||||||
"#f7f7f7",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"dark": {
|
|
||||||
Label: "Dark",
|
|
||||||
Colors: []string{
|
|
||||||
"#111111",
|
|
||||||
"#202020",
|
|
||||||
"#202020",
|
|
||||||
"#bebebe",
|
|
||||||
"#bebebe",
|
|
||||||
"#1b1b1b",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"pink": {
|
|
||||||
Label: "Pink",
|
|
||||||
Colors: []string{
|
|
||||||
"#ffe5d1",
|
|
||||||
"#e53b51",
|
|
||||||
"#ffffff",
|
|
||||||
"#352712",
|
|
||||||
"#ffffff",
|
|
||||||
"#f1f1f1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"purple": {
|
|
||||||
Label: "Purple",
|
|
||||||
Colors: []string{
|
|
||||||
"#674970",
|
|
||||||
"#2e2256",
|
|
||||||
"#ffffff",
|
|
||||||
"#2e2256",
|
|
||||||
"#ffffff",
|
|
||||||
"#f1f1f1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"blue": {
|
|
||||||
Label: "Blue",
|
|
||||||
Colors: []string{
|
|
||||||
"#e9f2f9",
|
|
||||||
"#55c3dc",
|
|
||||||
"#ffffff",
|
|
||||||
"#22313f",
|
|
||||||
"#ffffff",
|
|
||||||
"#f1f1f1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = func() struct{} {
|
|
||||||
v := wpconfig.ThemeSupport{
|
|
||||||
CoreBlockPatterns: true,
|
|
||||||
WidgetsBlockEditor: true,
|
|
||||||
AutomaticFeedLinks: true,
|
|
||||||
TitleTag: true,
|
|
||||||
PostThumbnails: true,
|
|
||||||
Menus: true,
|
|
||||||
HTML5: []string{
|
|
||||||
"search-form",
|
|
||||||
"comment-form",
|
|
||||||
"comment-list",
|
|
||||||
"gallery",
|
|
||||||
"caption",
|
|
||||||
"script",
|
|
||||||
"style",
|
|
||||||
"navigation-widgets",
|
|
||||||
},
|
|
||||||
PostFormats: []string{
|
|
||||||
"aside",
|
|
||||||
"image",
|
|
||||||
"video",
|
|
||||||
"quote",
|
|
||||||
"link",
|
|
||||||
"gallery",
|
|
||||||
"status",
|
|
||||||
"audio",
|
|
||||||
"chat",
|
|
||||||
},
|
|
||||||
CustomLogo: wpconfig.CustomLogo{
|
|
||||||
Width: 248,
|
|
||||||
Height: 248,
|
|
||||||
FlexWidth: false,
|
|
||||||
FlexHeight: true,
|
|
||||||
HeaderText: "",
|
|
||||||
UnlinkHomepageLogo: false,
|
|
||||||
},
|
|
||||||
CustomizeSelectiveRefreshWidgets: true,
|
|
||||||
EditorStyle: true,
|
|
||||||
EditorStyles: true,
|
|
||||||
WpBlockStyles: true,
|
|
||||||
ResponsiveEmbeds: true,
|
|
||||||
CustomHeader: wpconfig.CustomHeader{
|
|
||||||
DefaultImage: "",
|
|
||||||
RandomDefault: false,
|
|
||||||
Width: 954,
|
|
||||||
Height: 1300,
|
|
||||||
FlexHeight: false,
|
|
||||||
FlexWidth: false,
|
|
||||||
DefaultTextColor: "333333",
|
|
||||||
HeaderText: true,
|
|
||||||
Uploads: true,
|
|
||||||
WpHeadCallback: "twentyfifteen_header_style",
|
|
||||||
AdminHeadCallback: "",
|
|
||||||
AdminPreviewCallback: "",
|
|
||||||
Video: false,
|
|
||||||
VideoActiveCallback: "is_front_page",
|
|
||||||
},
|
|
||||||
Widgets: true,
|
|
||||||
}
|
|
||||||
wpconfig.SetThemeSupport(ThemeName, v)
|
|
||||||
return struct{}{}
|
|
||||||
}()
|
|
@ -1,63 +0,0 @@
|
|||||||
package twentyfifteen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
|
||||||
"github.com/fthvgb1/wp-go/app/plugins"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp/components"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp/middleware"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ThemeName = "twentyfifteen"
|
|
||||||
|
|
||||||
func Hook(h *wp.Handle) {
|
|
||||||
wp.Run(h, configs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func configs(h *wp.Handle) {
|
|
||||||
h.AddActionFilter(widgets.Search, func(h *wp.Handle, s string, args ...any) string {
|
|
||||||
return strings.ReplaceAll(s, `class="search-submit"`, `class="search-submit screen-reader-text"`)
|
|
||||||
})
|
|
||||||
wp.InitPipe(h)
|
|
||||||
middleware.CommonMiddleware(h)
|
|
||||||
setPaginationAndRender(h)
|
|
||||||
h.PushCacheGroupHeadScript(constraints.AllScene, "CalCustomBackGround", 10.005, CalCustomBackGround)
|
|
||||||
h.PushCacheGroupHeadScript(constraints.AllScene, "colorSchemeCss", 10.0056, colorSchemeCss)
|
|
||||||
h.CommonComponents()
|
|
||||||
components.WidgetArea(h)
|
|
||||||
h.PushRender(constraints.AllScene, wp.NewHandleFn(renderCustomHeader, 20.5, "renderCustomHeader"))
|
|
||||||
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 50.005, "wp.IndexRender"))
|
|
||||||
h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 50.005, "wp.DetailRender"))
|
|
||||||
h.PushRender(constraints.Detail, wp.NewHandleFn(postThumb, 60.005, "postThumb"))
|
|
||||||
h.PushDataHandler(constraints.Detail, wp.NewHandleFn(wp.Detail, 100.005, "wp.Detail"))
|
|
||||||
wp.PushIndexHandler(constraints.PipeData, h, wp.NewHandleFn(wp.Index, 100.005, "wp.Index"))
|
|
||||||
h.PushDataHandler(constraints.AllScene, wp.NewHandleFn(wp.PreCodeAndStats, 80.005, "wp.PreCodeAndStats"))
|
|
||||||
h.PushRender(constraints.AllScene, wp.NewHandleFn(wp.PreTemplate, 70.005, "wp.PreTemplate"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func setPaginationAndRender(h *wp.Handle) {
|
|
||||||
h.PushHandler(constraints.PipeRender, constraints.Detail, wp.NewHandleFn(func(hh *wp.Handle) {
|
|
||||||
d := hh.GetDetailHandle()
|
|
||||||
d.CommentRender = plugins.CommentRender()
|
|
||||||
d.CommentPageEle = plugins.TwentyFifteenCommentPagination()
|
|
||||||
}, 150, "setPaginationAndRender"))
|
|
||||||
|
|
||||||
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(func(hh *wp.Handle) {
|
|
||||||
i := hh.GetIndexHandle()
|
|
||||||
i.SetPageEle(plugins.TwentyFifteenPagination())
|
|
||||||
}, 150, "setPaginationAndRender"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func postThumb(h *wp.Handle) {
|
|
||||||
d := h.GetDetailHandle()
|
|
||||||
if d.Post.Thumbnail.Path != "" {
|
|
||||||
d.Post.Thumbnail = wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "post-thumbnail", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderCustomHeader(h *wp.Handle) {
|
|
||||||
h.SetData("customHeader", customHeader(h))
|
|
||||||
}
|
|
@ -1,574 +0,0 @@
|
|||||||
package twentyseventeen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/number"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func colorScheme(h *wp.Handle) (r string) {
|
|
||||||
if "custom" != wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s := str.NewBuilder()
|
|
||||||
hue := number.ToString(wpconfig.GetThemeModsVal[int64](ThemeName, "colorscheme_hue", 250))
|
|
||||||
reducedSaturation := fmt.Sprintf("%d%%", int(.8*50))
|
|
||||||
saturation := fmt.Sprintf("%d%%", 50)
|
|
||||||
css := str.Replace(customCss, map[string]string{
|
|
||||||
"' . $hue . '": hue,
|
|
||||||
"' . esc_attr( $hue ) . '": hue,
|
|
||||||
"' . $saturation . '": saturation,
|
|
||||||
"' . esc_attr( $saturation ) .' ": saturation,
|
|
||||||
"' . $reduced_saturation . '": reducedSaturation,
|
|
||||||
})
|
|
||||||
s.Sprintf(`<style type="text/css" id="custom-theme-colors">%s</style>`, css)
|
|
||||||
r = s.String()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var customCss = `
|
|
||||||
/**
|
|
||||||
* Twenty Seventeen: Color Patterns
|
|
||||||
*
|
|
||||||
* Colors are ordered from dark to light.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.colors-custom a:hover,
|
|
||||||
.colors-custom a:active,
|
|
||||||
.colors-custom .entry-content a:focus,
|
|
||||||
.colors-custom .entry-content a:hover,
|
|
||||||
.colors-custom .entry-summary a:focus,
|
|
||||||
.colors-custom .entry-summary a:hover,
|
|
||||||
.colors-custom .comment-content a:focus,
|
|
||||||
.colors-custom .comment-content a:hover,
|
|
||||||
.colors-custom .widget a:focus,
|
|
||||||
.colors-custom .widget a:hover,
|
|
||||||
.colors-custom .site-footer .widget-area a:focus,
|
|
||||||
.colors-custom .site-footer .widget-area a:hover,
|
|
||||||
.colors-custom .posts-navigation a:focus,
|
|
||||||
.colors-custom .posts-navigation a:hover,
|
|
||||||
.colors-custom .comment-metadata a:focus,
|
|
||||||
.colors-custom .comment-metadata a:hover,
|
|
||||||
.colors-custom .comment-metadata a.comment-edit-link:focus,
|
|
||||||
.colors-custom .comment-metadata a.comment-edit-link:hover,
|
|
||||||
.colors-custom .comment-reply-link:focus,
|
|
||||||
.colors-custom .comment-reply-link:hover,
|
|
||||||
.colors-custom .widget_authors a:focus strong,
|
|
||||||
.colors-custom .widget_authors a:hover strong,
|
|
||||||
.colors-custom .entry-title a:focus,
|
|
||||||
.colors-custom .entry-title a:hover,
|
|
||||||
.colors-custom .entry-meta a:focus,
|
|
||||||
.colors-custom .entry-meta a:hover,
|
|
||||||
.colors-custom.blog .entry-meta a.post-edit-link:focus,
|
|
||||||
.colors-custom.blog .entry-meta a.post-edit-link:hover,
|
|
||||||
.colors-custom.archive .entry-meta a.post-edit-link:focus,
|
|
||||||
.colors-custom.archive .entry-meta a.post-edit-link:hover,
|
|
||||||
.colors-custom.search .entry-meta a.post-edit-link:focus,
|
|
||||||
.colors-custom.search .entry-meta a.post-edit-link:hover,
|
|
||||||
.colors-custom .page-links a:focus .page-number,
|
|
||||||
.colors-custom .page-links a:hover .page-number,
|
|
||||||
.colors-custom .entry-footer a:focus,
|
|
||||||
.colors-custom .entry-footer a:hover,
|
|
||||||
.colors-custom .entry-footer .cat-links a:focus,
|
|
||||||
.colors-custom .entry-footer .cat-links a:hover,
|
|
||||||
.colors-custom .entry-footer .tags-links a:focus,
|
|
||||||
.colors-custom .entry-footer .tags-links a:hover,
|
|
||||||
.colors-custom .post-navigation a:focus,
|
|
||||||
.colors-custom .post-navigation a:hover,
|
|
||||||
.colors-custom .pagination a:not(.prev):not(.next):focus,
|
|
||||||
.colors-custom .pagination a:not(.prev):not(.next):hover,
|
|
||||||
.colors-custom .comments-pagination a:not(.prev):not(.next):focus,
|
|
||||||
.colors-custom .comments-pagination a:not(.prev):not(.next):hover,
|
|
||||||
.colors-custom .logged-in-as a:focus,
|
|
||||||
.colors-custom .logged-in-as a:hover,
|
|
||||||
.colors-custom a:focus .nav-title,
|
|
||||||
.colors-custom a:hover .nav-title,
|
|
||||||
.colors-custom .edit-link a:focus,
|
|
||||||
.colors-custom .edit-link a:hover,
|
|
||||||
.colors-custom .site-info a:focus,
|
|
||||||
.colors-custom .site-info a:hover,
|
|
||||||
.colors-custom .widget .widget-title a:focus,
|
|
||||||
.colors-custom .widget .widget-title a:hover,
|
|
||||||
.colors-custom .widget ul li a:focus,
|
|
||||||
.colors-custom .widget ul li a:hover {
|
|
||||||
color: hsl( ' . $hue . ', ' . $saturation . ', 0% ); /* base: #000; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .entry-content a,
|
|
||||||
.colors-custom .entry-summary a,
|
|
||||||
.colors-custom .comment-content a,
|
|
||||||
.colors-custom .widget a,
|
|
||||||
.colors-custom .site-footer .widget-area a,
|
|
||||||
.colors-custom .posts-navigation a,
|
|
||||||
.colors-custom .widget_authors a strong {
|
|
||||||
-webkit-box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 6% ); /* base: rgba(15, 15, 15, 1); */
|
|
||||||
box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 6% ); /* base: rgba(15, 15, 15, 1); */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom button,
|
|
||||||
.colors-custom input[type="button"],
|
|
||||||
.colors-custom input[type="submit"],
|
|
||||||
.colors-custom .entry-footer .edit-link a.post-edit-link {
|
|
||||||
background-color: hsl( ' . $hue . ', ' . $saturation . ', 13% ); /* base: #222; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom input[type="text"]:focus,
|
|
||||||
.colors-custom input[type="email"]:focus,
|
|
||||||
.colors-custom input[type="url"]:focus,
|
|
||||||
.colors-custom input[type="password"]:focus,
|
|
||||||
.colors-custom input[type="search"]:focus,
|
|
||||||
.colors-custom input[type="number"]:focus,
|
|
||||||
.colors-custom input[type="tel"]:focus,
|
|
||||||
.colors-custom input[type="range"]:focus,
|
|
||||||
.colors-custom input[type="date"]:focus,
|
|
||||||
.colors-custom input[type="month"]:focus,
|
|
||||||
.colors-custom input[type="week"]:focus,
|
|
||||||
.colors-custom input[type="time"]:focus,
|
|
||||||
.colors-custom input[type="datetime"]:focus,
|
|
||||||
.colors-custom .colors-custom input[type="datetime-local"]:focus,
|
|
||||||
.colors-custom input[type="color"]:focus,
|
|
||||||
.colors-custom textarea:focus,
|
|
||||||
.colors-custom button.secondary,
|
|
||||||
.colors-custom input[type="reset"],
|
|
||||||
.colors-custom input[type="button"].secondary,
|
|
||||||
.colors-custom input[type="reset"].secondary,
|
|
||||||
.colors-custom input[type="submit"].secondary,
|
|
||||||
.colors-custom a,
|
|
||||||
.colors-custom .site-title,
|
|
||||||
.colors-custom .site-title a,
|
|
||||||
.colors-custom .navigation-top a,
|
|
||||||
.colors-custom .dropdown-toggle,
|
|
||||||
.colors-custom .menu-toggle,
|
|
||||||
.colors-custom .page .panel-content .entry-title,
|
|
||||||
.colors-custom .page-title,
|
|
||||||
.colors-custom.page:not(.twentyseventeen-front-page) .entry-title,
|
|
||||||
.colors-custom .page-links a .page-number,
|
|
||||||
.colors-custom .comment-metadata a.comment-edit-link,
|
|
||||||
.colors-custom .comment-reply-link .icon,
|
|
||||||
.colors-custom h2.widget-title,
|
|
||||||
.colors-custom mark,
|
|
||||||
.colors-custom .post-navigation a:focus .icon,
|
|
||||||
.colors-custom .post-navigation a:hover .icon,
|
|
||||||
.colors-custom .site-content .site-content-light,
|
|
||||||
.colors-custom .twentyseventeen-panel .recent-posts .entry-header .edit-link {
|
|
||||||
color: hsl( ' . $hue . ', ' . $saturation . ', 13% ); /* base: #222; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .entry-content a:focus,
|
|
||||||
.colors-custom .entry-content a:hover,
|
|
||||||
.colors-custom .entry-summary a:focus,
|
|
||||||
.colors-custom .entry-summary a:hover,
|
|
||||||
.colors-custom .comment-content a:focus,
|
|
||||||
.colors-custom .comment-content a:hover,
|
|
||||||
.colors-custom .widget a:focus,
|
|
||||||
.colors-custom .widget a:hover,
|
|
||||||
.colors-custom .site-footer .widget-area a:focus,
|
|
||||||
.colors-custom .site-footer .widget-area a:hover,
|
|
||||||
.colors-custom .posts-navigation a:focus,
|
|
||||||
.colors-custom .posts-navigation a:hover,
|
|
||||||
.colors-custom .comment-metadata a:focus,
|
|
||||||
.colors-custom .comment-metadata a:hover,
|
|
||||||
.colors-custom .comment-metadata a.comment-edit-link:focus,
|
|
||||||
.colors-custom .comment-metadata a.comment-edit-link:hover,
|
|
||||||
.colors-custom .comment-reply-link:focus,
|
|
||||||
.colors-custom .comment-reply-link:hover,
|
|
||||||
.colors-custom .widget_authors a:focus strong,
|
|
||||||
.colors-custom .widget_authors a:hover strong,
|
|
||||||
.colors-custom .entry-title a:focus,
|
|
||||||
.colors-custom .entry-title a:hover,
|
|
||||||
.colors-custom .entry-meta a:focus,
|
|
||||||
.colors-custom .entry-meta a:hover,
|
|
||||||
.colors-custom.blog .entry-meta a.post-edit-link:focus,
|
|
||||||
.colors-custom.blog .entry-meta a.post-edit-link:hover,
|
|
||||||
.colors-custom.archive .entry-meta a.post-edit-link:focus,
|
|
||||||
.colors-custom.archive .entry-meta a.post-edit-link:hover,
|
|
||||||
.colors-custom.search .entry-meta a.post-edit-link:focus,
|
|
||||||
.colors-custom.search .entry-meta a.post-edit-link:hover,
|
|
||||||
.colors-custom .page-links a:focus .page-number,
|
|
||||||
.colors-custom .page-links a:hover .page-number,
|
|
||||||
.colors-custom .entry-footer .cat-links a:focus,
|
|
||||||
.colors-custom .entry-footer .cat-links a:hover,
|
|
||||||
.colors-custom .entry-footer .tags-links a:focus,
|
|
||||||
.colors-custom .entry-footer .tags-links a:hover,
|
|
||||||
.colors-custom .post-navigation a:focus,
|
|
||||||
.colors-custom .post-navigation a:hover,
|
|
||||||
.colors-custom .pagination a:not(.prev):not(.next):focus,
|
|
||||||
.colors-custom .pagination a:not(.prev):not(.next):hover,
|
|
||||||
.colors-custom .comments-pagination a:not(.prev):not(.next):focus,
|
|
||||||
.colors-custom .comments-pagination a:not(.prev):not(.next):hover,
|
|
||||||
.colors-custom .logged-in-as a:focus,
|
|
||||||
.colors-custom .logged-in-as a:hover,
|
|
||||||
.colors-custom a:focus .nav-title,
|
|
||||||
.colors-custom a:hover .nav-title,
|
|
||||||
.colors-custom .edit-link a:focus,
|
|
||||||
.colors-custom .edit-link a:hover,
|
|
||||||
.colors-custom .site-info a:focus,
|
|
||||||
.colors-custom .site-info a:hover,
|
|
||||||
.colors-custom .widget .widget-title a:focus,
|
|
||||||
.colors-custom .widget .widget-title a:hover,
|
|
||||||
.colors-custom .widget ul li a:focus,
|
|
||||||
.colors-custom .widget ul li a:hover {
|
|
||||||
-webkit-box-shadow: inset 0 0 0 hsl( ' . $hue . ', ' . $saturation . ', 13% ), 0 3px 0 hsl( ' . $hue . ', ' . $saturation . ', 13% );
|
|
||||||
box-shadow: inset 0 0 0 hsl( ' . $hue . ', ' . $saturation . ' , 13% ), 0 3px 0 hsl( ' . $hue . ', ' . $saturation . ', 13% );
|
|
||||||
}
|
|
||||||
|
|
||||||
body.colors-custom,
|
|
||||||
.colors-custom button,
|
|
||||||
.colors-custom input,
|
|
||||||
.colors-custom select,
|
|
||||||
.colors-custom textarea,
|
|
||||||
.colors-custom h3,
|
|
||||||
.colors-custom h4,
|
|
||||||
.colors-custom h6,
|
|
||||||
.colors-custom label,
|
|
||||||
.colors-custom .entry-title a,
|
|
||||||
.colors-custom.twentyseventeen-front-page .panel-content .recent-posts article,
|
|
||||||
.colors-custom .entry-footer .cat-links a,
|
|
||||||
.colors-custom .entry-footer .tags-links a,
|
|
||||||
.colors-custom .format-quote blockquote,
|
|
||||||
.colors-custom .nav-title,
|
|
||||||
.colors-custom .comment-body,
|
|
||||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-current-item .wp-playlist-item-album {
|
|
||||||
color: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .social-navigation a:hover,
|
|
||||||
.colors-custom .social-navigation a:focus {
|
|
||||||
background: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom input[type="text"]:focus,
|
|
||||||
.colors-custom input[type="email"]:focus,
|
|
||||||
.colors-custom input[type="url"]:focus,
|
|
||||||
.colors-custom input[type="password"]:focus,
|
|
||||||
.colors-custom input[type="search"]:focus,
|
|
||||||
.colors-custom input[type="number"]:focus,
|
|
||||||
.colors-custom input[type="tel"]:focus,
|
|
||||||
.colors-custom input[type="range"]:focus,
|
|
||||||
.colors-custom input[type="date"]:focus,
|
|
||||||
.colors-custom input[type="month"]:focus,
|
|
||||||
.colors-custom input[type="week"]:focus,
|
|
||||||
.colors-custom input[type="time"]:focus,
|
|
||||||
.colors-custom input[type="datetime"]:focus,
|
|
||||||
.colors-custom input[type="datetime-local"]:focus,
|
|
||||||
.colors-custom input[type="color"]:focus,
|
|
||||||
.colors-custom textarea:focus,
|
|
||||||
.bypostauthor > .comment-body > .comment-meta > .comment-author .avatar {
|
|
||||||
border-color: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom h2,
|
|
||||||
.colors-custom blockquote,
|
|
||||||
.colors-custom input[type="text"],
|
|
||||||
.colors-custom input[type="email"],
|
|
||||||
.colors-custom input[type="url"],
|
|
||||||
.colors-custom input[type="password"],
|
|
||||||
.colors-custom input[type="search"],
|
|
||||||
.colors-custom input[type="number"],
|
|
||||||
.colors-custom input[type="tel"],
|
|
||||||
.colors-custom input[type="range"],
|
|
||||||
.colors-custom input[type="date"],
|
|
||||||
.colors-custom input[type="month"],
|
|
||||||
.colors-custom input[type="week"],
|
|
||||||
.colors-custom input[type="time"],
|
|
||||||
.colors-custom input[type="datetime"],
|
|
||||||
.colors-custom input[type="datetime-local"],
|
|
||||||
.colors-custom input[type="color"],
|
|
||||||
.colors-custom textarea,
|
|
||||||
.colors-custom .site-description,
|
|
||||||
.colors-custom .entry-content blockquote.alignleft,
|
|
||||||
.colors-custom .entry-content blockquote.alignright,
|
|
||||||
.colors-custom .colors-custom .taxonomy-description,
|
|
||||||
.colors-custom .site-info a,
|
|
||||||
.colors-custom .wp-caption,
|
|
||||||
.colors-custom .gallery-caption {
|
|
||||||
color: hsl( ' . $hue . ', ' . $saturation . ', 40% ); /* base: #666; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom abbr,
|
|
||||||
.colors-custom acronym {
|
|
||||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 40% ); /* base: #666; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom h5,
|
|
||||||
.colors-custom .entry-meta,
|
|
||||||
.colors-custom .entry-meta a,
|
|
||||||
.colors-custom.blog .entry-meta a.post-edit-link,
|
|
||||||
.colors-custom.archive .entry-meta a.post-edit-link,
|
|
||||||
.colors-custom.search .entry-meta a.post-edit-link,
|
|
||||||
.colors-custom .nav-subtitle,
|
|
||||||
.colors-custom .comment-metadata,
|
|
||||||
.colors-custom .comment-metadata a,
|
|
||||||
.colors-custom .no-comments,
|
|
||||||
.colors-custom .comment-awaiting-moderation,
|
|
||||||
.colors-custom .page-numbers.current,
|
|
||||||
.colors-custom .page-links .page-number,
|
|
||||||
.colors-custom .navigation-top .current-menu-item > a,
|
|
||||||
.colors-custom .navigation-top .current_page_item > a,
|
|
||||||
.colors-custom .main-navigation a:hover,
|
|
||||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-current-item .wp-playlist-item-artist {
|
|
||||||
color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom :not( .mejs-button ) > button:hover,
|
|
||||||
.colors-custom :not( .mejs-button ) > button:focus,
|
|
||||||
.colors-custom input[type="button"]:hover,
|
|
||||||
.colors-custom input[type="button"]:focus,
|
|
||||||
.colors-custom input[type="submit"]:hover,
|
|
||||||
.colors-custom input[type="submit"]:focus,
|
|
||||||
.colors-custom .entry-footer .edit-link a.post-edit-link:hover,
|
|
||||||
.colors-custom .entry-footer .edit-link a.post-edit-link:focus,
|
|
||||||
.colors-custom .social-navigation a,
|
|
||||||
.colors-custom .prev.page-numbers:focus,
|
|
||||||
.colors-custom .prev.page-numbers:hover,
|
|
||||||
.colors-custom .next.page-numbers:focus,
|
|
||||||
.colors-custom .next.page-numbers:hover,
|
|
||||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover,
|
|
||||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus {
|
|
||||||
background: hsl( ' . esc_attr( $hue ) . ', ' . esc_attr( $saturation ) . ', 46% ); /* base: #767676; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom button.secondary:hover,
|
|
||||||
.colors-custom button.secondary:focus,
|
|
||||||
.colors-custom input[type="reset"]:hover,
|
|
||||||
.colors-custom input[type="reset"]:focus,
|
|
||||||
.colors-custom input[type="button"].secondary:hover,
|
|
||||||
.colors-custom input[type="button"].secondary:focus,
|
|
||||||
.colors-custom input[type="reset"].secondary:hover,
|
|
||||||
.colors-custom input[type="reset"].secondary:focus,
|
|
||||||
.colors-custom input[type="submit"].secondary:hover,
|
|
||||||
.colors-custom input[type="submit"].secondary:focus,
|
|
||||||
.colors-custom hr {
|
|
||||||
background: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom input[type="text"],
|
|
||||||
.colors-custom input[type="email"],
|
|
||||||
.colors-custom input[type="url"],
|
|
||||||
.colors-custom input[type="password"],
|
|
||||||
.colors-custom input[type="search"],
|
|
||||||
.colors-custom input[type="number"],
|
|
||||||
.colors-custom input[type="tel"],
|
|
||||||
.colors-custom input[type="range"],
|
|
||||||
.colors-custom input[type="date"],
|
|
||||||
.colors-custom input[type="month"],
|
|
||||||
.colors-custom input[type="week"],
|
|
||||||
.colors-custom input[type="time"],
|
|
||||||
.colors-custom input[type="datetime"],
|
|
||||||
.colors-custom input[type="datetime-local"],
|
|
||||||
.colors-custom input[type="color"],
|
|
||||||
.colors-custom textarea,
|
|
||||||
.colors-custom select,
|
|
||||||
.colors-custom fieldset,
|
|
||||||
.colors-custom .widget .tagcloud a:hover,
|
|
||||||
.colors-custom .widget .tagcloud a:focus,
|
|
||||||
.colors-custom .widget.widget_tag_cloud a:hover,
|
|
||||||
.colors-custom .widget.widget_tag_cloud a:focus,
|
|
||||||
.colors-custom .wp_widget_tag_cloud a:hover,
|
|
||||||
.colors-custom .wp_widget_tag_cloud a:focus {
|
|
||||||
border-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom thead th {
|
|
||||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .entry-footer .cat-links .icon,
|
|
||||||
.colors-custom .entry-footer .tags-links .icon {
|
|
||||||
color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom button.secondary,
|
|
||||||
.colors-custom input[type="reset"],
|
|
||||||
.colors-custom input[type="button"].secondary,
|
|
||||||
.colors-custom input[type="reset"].secondary,
|
|
||||||
.colors-custom input[type="submit"].secondary,
|
|
||||||
.colors-custom .prev.page-numbers,
|
|
||||||
.colors-custom .next.page-numbers {
|
|
||||||
background-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .widget .tagcloud a,
|
|
||||||
.colors-custom .widget.widget_tag_cloud a,
|
|
||||||
.colors-custom .wp_widget_tag_cloud a {
|
|
||||||
border-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom.twentyseventeen-front-page article:not(.has-post-thumbnail):not(:first-child),
|
|
||||||
.colors-custom .widget ul li {
|
|
||||||
border-top-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .widget ul li {
|
|
||||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom pre,
|
|
||||||
.colors-custom mark,
|
|
||||||
.colors-custom ins {
|
|
||||||
background: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .navigation-top,
|
|
||||||
.colors-custom .main-navigation > div > ul,
|
|
||||||
.colors-custom .pagination,
|
|
||||||
.colors-custom .comments-pagination,
|
|
||||||
.colors-custom .entry-footer,
|
|
||||||
.colors-custom .site-footer {
|
|
||||||
border-top-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .navigation-top,
|
|
||||||
.colors-custom .main-navigation li,
|
|
||||||
.colors-custom .entry-footer,
|
|
||||||
.colors-custom .single-featured-image-header,
|
|
||||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item,
|
|
||||||
.colors-custom tr {
|
|
||||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .site-content .wp-playlist-light {
|
|
||||||
border-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .site-header,
|
|
||||||
.colors-custom .single-featured-image-header {
|
|
||||||
background-color: hsl( ' . $hue . ', ' . $saturation . ', 98% ); /* base: #fafafa; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom button,
|
|
||||||
.colors-custom input[type="button"],
|
|
||||||
.colors-custom input[type="submit"],
|
|
||||||
.colors-custom .entry-footer .edit-link a.post-edit-link,
|
|
||||||
.colors-custom .social-navigation a,
|
|
||||||
.colors-custom .site-content .wp-playlist-light a.wp-playlist-caption:hover,
|
|
||||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover a,
|
|
||||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus a,
|
|
||||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover,
|
|
||||||
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus,
|
|
||||||
.colors-custom .prev.page-numbers:focus,
|
|
||||||
.colors-custom .prev.page-numbers:hover,
|
|
||||||
.colors-custom .next.page-numbers:focus,
|
|
||||||
.colors-custom .next.page-numbers:hover,
|
|
||||||
.colors-custom.has-header-image .site-title,
|
|
||||||
.colors-custom.has-header-video .site-title,
|
|
||||||
.colors-custom.has-header-image .site-title a,
|
|
||||||
.colors-custom.has-header-video .site-title a,
|
|
||||||
.colors-custom.has-header-image .site-description,
|
|
||||||
.colors-custom.has-header-video .site-description {
|
|
||||||
color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
|
|
||||||
}
|
|
||||||
|
|
||||||
body.colors-custom,
|
|
||||||
.colors-custom .navigation-top,
|
|
||||||
.colors-custom .main-navigation ul {
|
|
||||||
background: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .widget ul li a,
|
|
||||||
.colors-custom .site-footer .widget-area ul li a {
|
|
||||||
-webkit-box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: rgba(255, 255, 255, 1); */
|
|
||||||
box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: rgba(255, 255, 255, 1); */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .menu-toggle,
|
|
||||||
.colors-custom .menu-toggle:hover,
|
|
||||||
.colors-custom .menu-toggle:focus,
|
|
||||||
.colors-custom .menu .dropdown-toggle,
|
|
||||||
.colors-custom .menu-scroll-down,
|
|
||||||
.colors-custom .menu-scroll-down:hover,
|
|
||||||
.colors-custom .menu-scroll-down:focus {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .widget .tagcloud a,
|
|
||||||
.colors-custom .widget .tagcloud a:focus,
|
|
||||||
.colors-custom .widget .tagcloud a:hover,
|
|
||||||
.colors-custom .widget.widget_tag_cloud a,
|
|
||||||
.colors-custom .widget.widget_tag_cloud a:focus,
|
|
||||||
.colors-custom .widget.widget_tag_cloud a:hover,
|
|
||||||
.colors-custom .wp_widget_tag_cloud a,
|
|
||||||
.colors-custom .wp_widget_tag_cloud a:focus,
|
|
||||||
.colors-custom .wp_widget_tag_cloud a:hover,
|
|
||||||
.colors-custom .entry-footer .edit-link a.post-edit-link:focus,
|
|
||||||
.colors-custom .entry-footer .edit-link a.post-edit-link:hover {
|
|
||||||
-webkit-box-shadow: none !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset non-customizable hover styling for links */
|
|
||||||
.colors-custom .entry-content a:hover,
|
|
||||||
.colors-custom .entry-content a:focus,
|
|
||||||
.colors-custom .entry-summary a:hover,
|
|
||||||
.colors-custom .entry-summary a:focus,
|
|
||||||
.colors-custom .comment-content a:focus,
|
|
||||||
.colors-custom .comment-content a:hover,
|
|
||||||
.colors-custom .widget a:hover,
|
|
||||||
.colors-custom .widget a:focus,
|
|
||||||
.colors-custom .site-footer .widget-area a:hover,
|
|
||||||
.colors-custom .site-footer .widget-area a:focus,
|
|
||||||
.colors-custom .posts-navigation a:hover,
|
|
||||||
.colors-custom .posts-navigation a:focus,
|
|
||||||
.colors-custom .widget_authors a:hover strong,
|
|
||||||
.colors-custom .widget_authors a:focus strong {
|
|
||||||
-webkit-box-shadow: inset 0 0 0 rgba(0, 0, 0, 0), 0 3px 0 rgba(0, 0, 0, 1);
|
|
||||||
box-shadow: inset 0 0 0 rgba(0, 0, 0, 0), 0 3px 0 rgba(0, 0, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .gallery-item a,
|
|
||||||
.colors-custom .gallery-item a:hover,
|
|
||||||
.colors-custom .gallery-item a:focus {
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 48em) {
|
|
||||||
|
|
||||||
.colors-custom .nav-links .nav-previous .nav-title .icon,
|
|
||||||
.colors-custom .nav-links .nav-next .nav-title .icon {
|
|
||||||
color: hsl( ' . $hue . ', ' . $saturation . ', 20% ); /* base: #222; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .main-navigation li li:hover,
|
|
||||||
.colors-custom .main-navigation li li.focus {
|
|
||||||
background: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .navigation-top .menu-scroll-down {
|
|
||||||
color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom abbr[title] {
|
|
||||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .main-navigation ul ul {
|
|
||||||
border-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
|
||||||
background: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .main-navigation ul li.menu-item-has-children:before,
|
|
||||||
.colors-custom .main-navigation ul li.page_item_has_children:before {
|
|
||||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .main-navigation ul li.menu-item-has-children:after,
|
|
||||||
.colors-custom .main-navigation ul li.page_item_has_children:after {
|
|
||||||
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.colors-custom .main-navigation li li.focus > a,
|
|
||||||
.colors-custom .main-navigation li li:focus > a,
|
|
||||||
.colors-custom .main-navigation li li:hover > a,
|
|
||||||
.colors-custom .main-navigation li li a:hover,
|
|
||||||
.colors-custom .main-navigation li li a:focus,
|
|
||||||
.colors-custom .main-navigation li li.current_page_item a:hover,
|
|
||||||
.colors-custom .main-navigation li li.current-menu-item a:hover,
|
|
||||||
.colors-custom .main-navigation li li.current_page_item a:focus,
|
|
||||||
.colors-custom .main-navigation li li.current-menu-item a:focus {
|
|
||||||
color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
@ -1,48 +0,0 @@
|
|||||||
package twentyseventeen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
)
|
|
||||||
|
|
||||||
func customHeader(h *wp.Handle) (r string) {
|
|
||||||
themeMods := h.CommonThemeMods()
|
|
||||||
headerTextColor := themeMods.HeaderTextcolor
|
|
||||||
if headerTextColor == "" || headerTextColor == themeMods.ThemeSupport.CustomHeader.DefaultTextColor {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
css := `
|
|
||||||
.site-title,
|
|
||||||
.site-description {
|
|
||||||
position: absolute;
|
|
||||||
clip: rect(1px, 1px, 1px, 1px);
|
|
||||||
}`
|
|
||||||
if headerTextColor != "blank" {
|
|
||||||
css = fmt.Sprintf(customHeaderCss, headerTextColor)
|
|
||||||
}
|
|
||||||
r = fmt.Sprintf(`<style id="twentyseventeen-custom-header-styles" type="text/css">%s</style>`, css)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var customHeaderCss = `
|
|
||||||
.site-title a,
|
|
||||||
.colors-dark .site-title a,
|
|
||||||
.colors-custom .site-title a,
|
|
||||||
body.has-header-image .site-title a,
|
|
||||||
body.has-header-video .site-title a,
|
|
||||||
body.has-header-image.colors-dark .site-title a,
|
|
||||||
body.has-header-video.colors-dark .site-title a,
|
|
||||||
body.has-header-image.colors-custom .site-title a,
|
|
||||||
body.has-header-video.colors-custom .site-title a,
|
|
||||||
.site-description,
|
|
||||||
.colors-dark .site-description,
|
|
||||||
.colors-custom .site-description,
|
|
||||||
body.has-header-image .site-description,
|
|
||||||
body.has-header-video .site-description,
|
|
||||||
body.has-header-image.colors-dark .site-description,
|
|
||||||
body.has-header-video.colors-dark .site-description,
|
|
||||||
body.has-header-image.colors-custom .site-description,
|
|
||||||
body.has-header-video.colors-custom .site-description {
|
|
||||||
color: #%s;
|
|
||||||
}
|
|
||||||
`
|
|
@ -1,53 +0,0 @@
|
|||||||
{{ define "layout/base"}}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{getLang}}" class="no-js no-svg">
|
|
||||||
|
|
||||||
{{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: 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}}>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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}}
|
|
||||||
|
|
||||||
</div><!-- .wrap -->
|
|
||||||
</div><!-- .site-branding -->
|
|
||||||
|
|
||||||
</div><!-- .custom-header -->
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{{block "content" .}}
|
|
||||||
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{{template "layout/footer" .}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
{{ end }}
|
|
@ -1,38 +0,0 @@
|
|||||||
{{define "respond"}}
|
|
||||||
<div id="respond" class="comment-respond">
|
|
||||||
<h3 id="reply-title" class="comment-reply-title">发表回复
|
|
||||||
<small>
|
|
||||||
<a rel="nofollow" id="cancel-comment-reply-link" href="/p/{{.post.Id}}#respond" style="display:none;">取消回复</a>
|
|
||||||
</small>
|
|
||||||
</h3>
|
|
||||||
<form action="/comment" method="post" id="commentform" class="comment-form"
|
|
||||||
novalidate="">
|
|
||||||
<p class="comment-notes">
|
|
||||||
<span id="email-notes">您的电子邮箱地址不会被公开。</span>
|
|
||||||
<span class="required-field-message" aria-hidden="true">必填项已用<span class="required" aria-hidden="true">*</span>标注</span>
|
|
||||||
</p>
|
|
||||||
<p class="comment-form-comment">
|
|
||||||
<label for="comment">评论 <span class="required" aria-hidden="true">*</span></label>
|
|
||||||
<textarea id="comment" name="comment" cols="45" rows="8" maxlength="65525" required=""></textarea></p>
|
|
||||||
<p class="comment-form-author">
|
|
||||||
<label for="author">显示名称 <span class="required" aria-hidden="true">*</span></label>
|
|
||||||
<input id="author" name="author" type="text" value="" size="30" maxlength="245"
|
|
||||||
required=""></p>
|
|
||||||
<p class="comment-form-email">
|
|
||||||
<label for="email">电子邮箱地址 <span class="required" aria-hidden="true">*</span></label>
|
|
||||||
<input id="email" name="email" type="email" value="" size="30" maxlength="100"
|
|
||||||
aria-describedby="email-notes" required="">
|
|
||||||
</p>
|
|
||||||
<p class="comment-form-url">
|
|
||||||
<label for="url">网站地址</label>
|
|
||||||
<input id="url" name="url" type="url" value="" size="30" maxlength="200">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p class="form-submit">
|
|
||||||
<input name="submit" type="submit" id="submit" class="submit" value="发表评论">
|
|
||||||
<input type="hidden" name="comment_post_ID" value="{{.post.Id}}" id="comment_post_ID">
|
|
||||||
<input type="hidden" name="comment_parent" id="comment_parent" value="0">
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
@ -1,48 +0,0 @@
|
|||||||
{{define "layout/empty"}}
|
|
||||||
<div class="wrap">
|
|
||||||
<div id="primary" class="content-area">
|
|
||||||
<main id="main" class="site-main">
|
|
||||||
<section class="error-404 not-found">
|
|
||||||
<header class="page-header">
|
|
||||||
<h1 class="page-title">
|
|
||||||
{{if .search}}
|
|
||||||
未找到
|
|
||||||
{{else}}
|
|
||||||
有点尴尬诶!该页无法显示。
|
|
||||||
{{end}}
|
|
||||||
</h1>
|
|
||||||
</header><!-- .page-header -->
|
|
||||||
|
|
||||||
<div class="page-content">
|
|
||||||
|
|
||||||
<p>{{if .search}}
|
|
||||||
抱歉,没有符合您搜索条件的结果。请换其它关键词再试。
|
|
||||||
{{else}}
|
|
||||||
这儿似乎什么都没有,试试搜索?
|
|
||||||
{{end}}
|
|
||||||
</p>
|
|
||||||
<form role="search" method="get" class="search-form" action="/">
|
|
||||||
<label for="search-form-1">
|
|
||||||
<span class="screen-reader-text">搜索:</span>
|
|
||||||
</label>
|
|
||||||
<input type="search" id="search-form-1" class="search-field" placeholder="搜索…" value="{{.search}}" name="s">
|
|
||||||
<button type="submit" class="search-submit">
|
|
||||||
<svg class="icon icon-search" aria-hidden="true" role="img"> <use href="#icon-search" xlink:href="#icon-search"></use> </svg>
|
|
||||||
<span class="screen-reader-text">搜索</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div><!-- .page-content -->
|
|
||||||
</section><!-- .no-results -->
|
|
||||||
|
|
||||||
</main><!-- .site-main -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{if .search }}
|
|
||||||
<aside id="secondary" class="widget-area" aria-label="博客边栏">
|
|
||||||
{{template "layout/sidebar" .}}
|
|
||||||
</aside>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
@ -1,5 +0,0 @@
|
|||||||
{{define "layout/footer"}}
|
|
||||||
{{template "common/footer" .}}
|
|
||||||
{{ block "footer" .}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
@ -1,164 +0,0 @@
|
|||||||
{{ define "footer"}}
|
|
||||||
<svg style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<defs>
|
|
||||||
<symbol id="icon-behance" viewBox="0 0 37 32">
|
|
||||||
<path class="path1" d="M33 6.054h-9.125v2.214h9.125v-2.214zM28.5 13.661q-1.607 0-2.607 0.938t-1.107 2.545h7.286q-0.321-3.482-3.571-3.482zM28.786 24.107q1.125 0 2.179-0.571t1.357-1.554h3.946q-1.786 5.482-7.625 5.482-3.821 0-6.080-2.357t-2.259-6.196q0-3.714 2.33-6.17t6.009-2.455q2.464 0 4.295 1.214t2.732 3.196 0.902 4.429q0 0.304-0.036 0.839h-11.75q0 1.982 1.027 3.063t2.973 1.080zM4.946 23.214h5.286q3.661 0 3.661-2.982 0-3.214-3.554-3.214h-5.393v6.196zM4.946 13.625h5.018q1.393 0 2.205-0.652t0.813-2.027q0-2.571-3.393-2.571h-4.643v5.25zM0 4.536h10.607q1.554 0 2.768 0.25t2.259 0.848 1.607 1.723 0.563 2.75q0 3.232-3.071 4.696 2.036 0.571 3.071 2.054t1.036 3.643q0 1.339-0.438 2.438t-1.179 1.848-1.759 1.268-2.161 0.75-2.393 0.232h-10.911v-22.5z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-deviantart" viewBox="0 0 18 32">
|
|
||||||
<path class="path1" d="M18.286 5.411l-5.411 10.393 0.429 0.554h4.982v7.411h-9.054l-0.786 0.536-2.536 4.875-0.536 0.536h-5.375v-5.411l5.411-10.411-0.429-0.536h-4.982v-7.411h9.054l0.786-0.536 2.536-4.875 0.536-0.536h5.375v5.411z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-medium" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M10.661 7.518v20.946q0 0.446-0.223 0.759t-0.652 0.313q-0.304 0-0.589-0.143l-8.304-4.161q-0.375-0.179-0.634-0.598t-0.259-0.83v-20.357q0-0.357 0.179-0.607t0.518-0.25q0.25 0 0.786 0.268l9.125 4.571q0.054 0.054 0.054 0.089zM11.804 9.321l9.536 15.464-9.536-4.75v-10.714zM32 9.643v18.821q0 0.446-0.25 0.723t-0.679 0.277-0.839-0.232l-7.875-3.929zM31.946 7.5q0 0.054-4.58 7.491t-5.366 8.705l-6.964-11.321 5.786-9.411q0.304-0.5 0.929-0.5 0.25 0 0.464 0.107l9.661 4.821q0.071 0.036 0.071 0.107z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-slideshare" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M15.589 13.214q0 1.482-1.134 2.545t-2.723 1.063-2.723-1.063-1.134-2.545q0-1.5 1.134-2.554t2.723-1.054 2.723 1.054 1.134 2.554zM24.554 13.214q0 1.482-1.125 2.545t-2.732 1.063q-1.589 0-2.723-1.063t-1.134-2.545q0-1.5 1.134-2.554t2.723-1.054q1.607 0 2.732 1.054t1.125 2.554zM28.571 16.429v-11.911q0-1.554-0.571-2.205t-1.982-0.652h-19.857q-1.482 0-2.009 0.607t-0.527 2.25v12.018q0.768 0.411 1.58 0.714t1.446 0.5 1.446 0.33 1.268 0.196 1.25 0.071 1.045 0.009 1.009-0.036 0.795-0.036q1.214-0.018 1.696 0.482 0.107 0.107 0.179 0.161 0.464 0.446 1.089 0.911 0.125-1.625 2.107-1.554 0.089 0 0.652 0.027t0.768 0.036 0.813 0.018 0.946-0.018 0.973-0.080 1.089-0.152 1.107-0.241 1.196-0.348 1.205-0.482 1.286-0.616zM31.482 16.339q-2.161 2.661-6.643 4.5 1.5 5.089-0.411 8.304-1.179 2.018-3.268 2.643-1.857 0.571-3.25-0.268-1.536-0.911-1.464-2.929l-0.018-5.821v-0.018q-0.143-0.036-0.438-0.107t-0.42-0.089l-0.018 6.036q0.071 2.036-1.482 2.929-1.411 0.839-3.268 0.268-2.089-0.643-3.25-2.679-1.875-3.214-0.393-8.268-4.482-1.839-6.643-4.5-0.446-0.661-0.071-1.125t1.071 0.018q0.054 0.036 0.196 0.125t0.196 0.143v-12.393q0-1.286 0.839-2.196t2.036-0.911h22.446q1.196 0 2.036 0.911t0.839 2.196v12.393l0.375-0.268q0.696-0.482 1.071-0.018t-0.071 1.125z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-snapchat-ghost" viewBox="0 0 30 32">
|
|
||||||
<path class="path1" d="M15.143 2.286q2.393-0.018 4.295 1.223t2.92 3.438q0.482 1.036 0.482 3.196 0 0.839-0.161 3.411 0.25 0.125 0.5 0.125 0.321 0 0.911-0.241t0.911-0.241q0.518 0 1 0.321t0.482 0.821q0 0.571-0.563 0.964t-1.232 0.563-1.232 0.518-0.563 0.848q0 0.268 0.214 0.768 0.661 1.464 1.83 2.679t2.58 1.804q0.5 0.214 1.429 0.411 0.5 0.107 0.5 0.625 0 1.25-3.911 1.839-0.125 0.196-0.196 0.696t-0.25 0.83-0.589 0.33q-0.357 0-1.107-0.116t-1.143-0.116q-0.661 0-1.107 0.089-0.571 0.089-1.125 0.402t-1.036 0.679-1.036 0.723-1.357 0.598-1.768 0.241q-0.929 0-1.723-0.241t-1.339-0.598-1.027-0.723-1.036-0.679-1.107-0.402q-0.464-0.089-1.125-0.089-0.429 0-1.17 0.134t-1.045 0.134q-0.446 0-0.625-0.33t-0.25-0.848-0.196-0.714q-3.911-0.589-3.911-1.839 0-0.518 0.5-0.625 0.929-0.196 1.429-0.411 1.393-0.571 2.58-1.804t1.83-2.679q0.214-0.5 0.214-0.768 0-0.5-0.563-0.848t-1.241-0.527-1.241-0.563-0.563-0.938q0-0.482 0.464-0.813t0.982-0.33q0.268 0 0.857 0.232t0.946 0.232q0.321 0 0.571-0.125-0.161-2.536-0.161-3.393 0-2.179 0.482-3.214 1.143-2.446 3.071-3.536t4.714-1.125z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-yelp" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M13.804 23.554v2.268q-0.018 5.214-0.107 5.446-0.214 0.571-0.911 0.714-0.964 0.161-3.241-0.679t-2.902-1.589q-0.232-0.268-0.304-0.643-0.018-0.214 0.071-0.464 0.071-0.179 0.607-0.839t3.232-3.857q0.018 0 1.071-1.25 0.268-0.339 0.705-0.438t0.884 0.063q0.429 0.179 0.67 0.518t0.223 0.75zM11.143 19.071q-0.054 0.982-0.929 1.25l-2.143 0.696q-4.911 1.571-5.214 1.571-0.625-0.036-0.964-0.643-0.214-0.446-0.304-1.339-0.143-1.357 0.018-2.973t0.536-2.223 1-0.571q0.232 0 3.607 1.375 1.25 0.518 2.054 0.839l1.5 0.607q0.411 0.161 0.634 0.545t0.205 0.866zM25.893 24.375q-0.125 0.964-1.634 2.875t-2.42 2.268q-0.661 0.25-1.125-0.125-0.25-0.179-3.286-5.125l-0.839-1.375q-0.25-0.375-0.205-0.821t0.348-0.821q0.625-0.768 1.482-0.464 0.018 0.018 2.125 0.714 3.625 1.179 4.321 1.42t0.839 0.366q0.5 0.393 0.393 1.089zM13.893 13.089q0.089 1.821-0.964 2.179-1.036 0.304-2.036-1.268l-6.75-10.679q-0.143-0.625 0.339-1.107 0.732-0.768 3.705-1.598t4.009-0.563q0.714 0.179 0.875 0.804 0.054 0.321 0.393 5.455t0.429 6.777zM25.714 15.018q0.054 0.696-0.464 1.054-0.268 0.179-5.875 1.536-1.196 0.268-1.625 0.411l0.018-0.036q-0.411 0.107-0.821-0.071t-0.661-0.571q-0.536-0.839 0-1.554 0.018-0.018 1.339-1.821 2.232-3.054 2.679-3.643t0.607-0.696q0.5-0.339 1.161-0.036 0.857 0.411 2.196 2.384t1.446 2.991v0.054z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-vine" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M26.732 14.768v3.536q-1.804 0.411-3.536 0.411-1.161 2.429-2.955 4.839t-3.241 3.848-2.286 1.902q-1.429 0.804-2.893-0.054-0.5-0.304-1.080-0.777t-1.518-1.491-1.83-2.295-1.92-3.286-1.884-4.357-1.634-5.616-1.259-6.964h5.054q0.464 3.893 1.25 7.116t1.866 5.661 2.17 4.205 2.5 3.482q3.018-3.018 5.125-7.25-2.536-1.286-3.982-3.929t-1.446-5.946q0-3.429 1.857-5.616t5.071-2.188q3.179 0 4.875 1.884t1.696 5.313q0 2.839-1.036 5.107-0.125 0.018-0.348 0.054t-0.821 0.036-1.125-0.107-1.107-0.455-0.902-0.92q0.554-1.839 0.554-3.286 0-1.554-0.518-2.357t-1.411-0.804q-0.946 0-1.518 0.884t-0.571 2.509q0 3.321 1.875 5.241t4.768 1.92q1.107 0 2.161-0.25z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-vk" viewBox="0 0 35 32">
|
|
||||||
<path class="path1" d="M34.232 9.286q0.411 1.143-2.679 5.25-0.429 0.571-1.161 1.518-1.393 1.786-1.607 2.339-0.304 0.732 0.25 1.446 0.304 0.375 1.446 1.464h0.018l0.071 0.071q2.518 2.339 3.411 3.946 0.054 0.089 0.116 0.223t0.125 0.473-0.009 0.607-0.446 0.491-1.054 0.223l-4.571 0.071q-0.429 0.089-1-0.089t-0.929-0.393l-0.357-0.214q-0.536-0.375-1.25-1.143t-1.223-1.384-1.089-1.036-1.009-0.277q-0.054 0.018-0.143 0.063t-0.304 0.259-0.384 0.527-0.304 0.929-0.116 1.384q0 0.268-0.063 0.491t-0.134 0.33l-0.071 0.089q-0.321 0.339-0.946 0.393h-2.054q-1.268 0.071-2.607-0.295t-2.348-0.946-1.839-1.179-1.259-1.027l-0.446-0.429q-0.179-0.179-0.491-0.536t-1.277-1.625-1.893-2.696-2.188-3.768-2.33-4.857q-0.107-0.286-0.107-0.482t0.054-0.286l0.071-0.107q0.268-0.339 1.018-0.339l4.893-0.036q0.214 0.036 0.411 0.116t0.286 0.152l0.089 0.054q0.286 0.196 0.429 0.571 0.357 0.893 0.821 1.848t0.732 1.455l0.286 0.518q0.518 1.071 1 1.857t0.866 1.223 0.741 0.688 0.607 0.25 0.482-0.089q0.036-0.018 0.089-0.089t0.214-0.393 0.241-0.839 0.17-1.446 0-2.232q-0.036-0.714-0.161-1.304t-0.25-0.821l-0.107-0.214q-0.446-0.607-1.518-0.768-0.232-0.036 0.089-0.429 0.304-0.339 0.679-0.536 0.946-0.464 4.268-0.429 1.464 0.018 2.411 0.232 0.357 0.089 0.598 0.241t0.366 0.429 0.188 0.571 0.063 0.813-0.018 0.982-0.045 1.259-0.027 1.473q0 0.196-0.018 0.75t-0.009 0.857 0.063 0.723 0.205 0.696 0.402 0.438q0.143 0.036 0.304 0.071t0.464-0.196 0.679-0.616 0.929-1.196 1.214-1.92q1.071-1.857 1.911-4.018 0.071-0.179 0.179-0.313t0.196-0.188l0.071-0.054 0.089-0.045t0.232-0.054 0.357-0.009l5.143-0.036q0.696-0.089 1.143 0.045t0.554 0.295z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-search" viewBox="0 0 30 32">
|
|
||||||
<path class="path1" d="M20.571 14.857q0-3.304-2.348-5.652t-5.652-2.348-5.652 2.348-2.348 5.652 2.348 5.652 5.652 2.348 5.652-2.348 2.348-5.652zM29.714 29.714q0 0.929-0.679 1.607t-1.607 0.679q-0.964 0-1.607-0.679l-6.125-6.107q-3.196 2.214-7.125 2.214-2.554 0-4.884-0.991t-4.018-2.679-2.679-4.018-0.991-4.884 0.991-4.884 2.679-4.018 4.018-2.679 4.884-0.991 4.884 0.991 4.018 2.679 2.679 4.018 0.991 4.884q0 3.929-2.214 7.125l6.125 6.125q0.661 0.661 0.661 1.607z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-envelope-o" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M29.714 26.857v-13.714q-0.571 0.643-1.232 1.179-4.786 3.679-7.607 6.036-0.911 0.768-1.482 1.196t-1.545 0.866-1.83 0.438h-0.036q-0.857 0-1.83-0.438t-1.545-0.866-1.482-1.196q-2.821-2.357-7.607-6.036-0.661-0.536-1.232-1.179v13.714q0 0.232 0.17 0.402t0.402 0.17h26.286q0.232 0 0.402-0.17t0.17-0.402zM29.714 8.089v-0.438t-0.009-0.232-0.054-0.223-0.098-0.161-0.161-0.134-0.25-0.045h-26.286q-0.232 0-0.402 0.17t-0.17 0.402q0 3 2.625 5.071 3.446 2.714 7.161 5.661 0.107 0.089 0.625 0.527t0.821 0.67 0.795 0.563 0.902 0.491 0.768 0.161h0.036q0.357 0 0.768-0.161t0.902-0.491 0.795-0.563 0.821-0.67 0.625-0.527q3.714-2.946 7.161-5.661 0.964-0.768 1.795-2.063t0.83-2.348zM32 7.429v19.429q0 1.179-0.839 2.018t-2.018 0.839h-26.286q-1.179 0-2.018-0.839t-0.839-2.018v-19.429q0-1.179 0.839-2.018t2.018-0.839h26.286q1.179 0 2.018 0.839t0.839 2.018z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-close" viewBox="0 0 25 32">
|
|
||||||
<path class="path1" d="M23.179 23.607q0 0.714-0.5 1.214l-2.429 2.429q-0.5 0.5-1.214 0.5t-1.214-0.5l-5.25-5.25-5.25 5.25q-0.5 0.5-1.214 0.5t-1.214-0.5l-2.429-2.429q-0.5-0.5-0.5-1.214t0.5-1.214l5.25-5.25-5.25-5.25q-0.5-0.5-0.5-1.214t0.5-1.214l2.429-2.429q0.5-0.5 1.214-0.5t1.214 0.5l5.25 5.25 5.25-5.25q0.5-0.5 1.214-0.5t1.214 0.5l2.429 2.429q0.5 0.5 0.5 1.214t-0.5 1.214l-5.25 5.25 5.25 5.25q0.5 0.5 0.5 1.214z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-angle-down" viewBox="0 0 21 32">
|
|
||||||
<path class="path1" d="M19.196 13.143q0 0.232-0.179 0.411l-8.321 8.321q-0.179 0.179-0.411 0.179t-0.411-0.179l-8.321-8.321q-0.179-0.179-0.179-0.411t0.179-0.411l0.893-0.893q0.179-0.179 0.411-0.179t0.411 0.179l7.018 7.018 7.018-7.018q0.179-0.179 0.411-0.179t0.411 0.179l0.893 0.893q0.179 0.179 0.179 0.411z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-folder-open" viewBox="0 0 34 32">
|
|
||||||
<path class="path1" d="M33.554 17q0 0.554-0.554 1.179l-6 7.071q-0.768 0.911-2.152 1.545t-2.563 0.634h-19.429q-0.607 0-1.080-0.232t-0.473-0.768q0-0.554 0.554-1.179l6-7.071q0.768-0.911 2.152-1.545t2.563-0.634h19.429q0.607 0 1.080 0.232t0.473 0.768zM27.429 10.857v2.857h-14.857q-1.679 0-3.518 0.848t-2.929 2.134l-6.107 7.179q0-0.071-0.009-0.223t-0.009-0.223v-17.143q0-1.643 1.179-2.821t2.821-1.179h5.714q1.643 0 2.821 1.179t1.179 2.821v0.571h9.714q1.643 0 2.821 1.179t1.179 2.821z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-twitter" viewBox="0 0 30 32">
|
|
||||||
<path class="path1" d="M28.929 7.286q-1.196 1.75-2.893 2.982 0.018 0.25 0.018 0.75 0 2.321-0.679 4.634t-2.063 4.437-3.295 3.759-4.607 2.607-5.768 0.973q-4.839 0-8.857-2.589 0.625 0.071 1.393 0.071 4.018 0 7.161-2.464-1.875-0.036-3.357-1.152t-2.036-2.848q0.589 0.089 1.089 0.089 0.768 0 1.518-0.196-2-0.411-3.313-1.991t-1.313-3.67v-0.071q1.214 0.679 2.607 0.732-1.179-0.786-1.875-2.054t-0.696-2.75q0-1.571 0.786-2.911 2.161 2.661 5.259 4.259t6.634 1.777q-0.143-0.679-0.143-1.321 0-2.393 1.688-4.080t4.080-1.688q2.5 0 4.214 1.821 1.946-0.375 3.661-1.393-0.661 2.054-2.536 3.179 1.661-0.179 3.321-0.893z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-facebook" viewBox="0 0 19 32">
|
|
||||||
<path class="path1" d="M17.125 0.214v4.714h-2.804q-1.536 0-2.071 0.643t-0.536 1.929v3.375h5.232l-0.696 5.286h-4.536v13.554h-5.464v-13.554h-4.554v-5.286h4.554v-3.893q0-3.321 1.857-5.152t4.946-1.83q2.625 0 4.071 0.214z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-github" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M13.714 2.286q3.732 0 6.884 1.839t4.991 4.991 1.839 6.884q0 4.482-2.616 8.063t-6.759 4.955q-0.482 0.089-0.714-0.125t-0.232-0.536q0-0.054 0.009-1.366t0.009-2.402q0-1.732-0.929-2.536 1.018-0.107 1.83-0.321t1.679-0.696 1.446-1.188 0.946-1.875 0.366-2.688q0-2.125-1.411-3.679 0.661-1.625-0.143-3.643-0.5-0.161-1.446 0.196t-1.643 0.786l-0.679 0.429q-1.661-0.464-3.429-0.464t-3.429 0.464q-0.286-0.196-0.759-0.482t-1.491-0.688-1.518-0.241q-0.804 2.018-0.143 3.643-1.411 1.554-1.411 3.679 0 1.518 0.366 2.679t0.938 1.875 1.438 1.196 1.679 0.696 1.83 0.321q-0.696 0.643-0.875 1.839-0.375 0.179-0.804 0.268t-1.018 0.089-1.17-0.384-0.991-1.116q-0.339-0.571-0.866-0.929t-0.884-0.429l-0.357-0.054q-0.375 0-0.518 0.080t-0.089 0.205 0.161 0.25 0.232 0.214l0.125 0.089q0.393 0.179 0.777 0.679t0.563 0.911l0.179 0.411q0.232 0.679 0.786 1.098t1.196 0.536 1.241 0.125 0.991-0.063l0.411-0.071q0 0.679 0.009 1.58t0.009 0.973q0 0.321-0.232 0.536t-0.714 0.125q-4.143-1.375-6.759-4.955t-2.616-8.063q0-3.732 1.839-6.884t4.991-4.991 6.884-1.839zM5.196 21.982q0.054-0.125-0.125-0.214-0.179-0.054-0.232 0.036-0.054 0.125 0.125 0.214 0.161 0.107 0.232-0.036zM5.75 22.589q0.125-0.089-0.036-0.286-0.179-0.161-0.286-0.054-0.125 0.089 0.036 0.286 0.179 0.179 0.286 0.054zM6.286 23.393q0.161-0.125 0-0.339-0.143-0.232-0.304-0.107-0.161 0.089 0 0.321t0.304 0.125zM7.036 24.143q0.143-0.143-0.071-0.339-0.214-0.214-0.357-0.054-0.161 0.143 0.071 0.339 0.214 0.214 0.357 0.054zM8.054 24.589q0.054-0.196-0.232-0.286-0.268-0.071-0.339 0.125t0.232 0.268q0.268 0.107 0.339-0.107zM9.179 24.679q0-0.232-0.304-0.196-0.286 0-0.286 0.196 0 0.232 0.304 0.196 0.286 0 0.286-0.196zM10.214 24.5q-0.036-0.196-0.321-0.161-0.286 0.054-0.25 0.268t0.321 0.143 0.25-0.25z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-bars" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M27.429 24v2.286q0 0.464-0.339 0.804t-0.804 0.339h-25.143q-0.464 0-0.804-0.339t-0.339-0.804v-2.286q0-0.464 0.339-0.804t0.804-0.339h25.143q0.464 0 0.804 0.339t0.339 0.804zM27.429 14.857v2.286q0 0.464-0.339 0.804t-0.804 0.339h-25.143q-0.464 0-0.804-0.339t-0.339-0.804v-2.286q0-0.464 0.339-0.804t0.804-0.339h25.143q0.464 0 0.804 0.339t0.339 0.804zM27.429 5.714v2.286q0 0.464-0.339 0.804t-0.804 0.339h-25.143q-0.464 0-0.804-0.339t-0.339-0.804v-2.286q0-0.464 0.339-0.804t0.804-0.339h25.143q0.464 0 0.804 0.339t0.339 0.804z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-google-plus" viewBox="0 0 41 32">
|
|
||||||
<path class="path1" d="M25.661 16.304q0 3.714-1.554 6.616t-4.429 4.536-6.589 1.634q-2.661 0-5.089-1.036t-4.179-2.786-2.786-4.179-1.036-5.089 1.036-5.089 2.786-4.179 4.179-2.786 5.089-1.036q5.107 0 8.768 3.429l-3.554 3.411q-2.089-2.018-5.214-2.018-2.196 0-4.063 1.107t-2.955 3.009-1.089 4.152 1.089 4.152 2.955 3.009 4.063 1.107q1.482 0 2.723-0.411t2.045-1.027 1.402-1.402 0.875-1.482 0.384-1.321h-7.429v-4.5h12.357q0.214 1.125 0.214 2.179zM41.143 14.125v3.75h-3.732v3.732h-3.75v-3.732h-3.732v-3.75h3.732v-3.732h3.75v3.732h3.732z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-linkedin" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M6.232 11.161v17.696h-5.893v-17.696h5.893zM6.607 5.696q0.018 1.304-0.902 2.179t-2.42 0.875h-0.036q-1.464 0-2.357-0.875t-0.893-2.179q0-1.321 0.92-2.188t2.402-0.866 2.375 0.866 0.911 2.188zM27.429 18.714v10.143h-5.875v-9.464q0-1.875-0.723-2.938t-2.259-1.063q-1.125 0-1.884 0.616t-1.134 1.527q-0.196 0.536-0.196 1.446v9.875h-5.875q0.036-7.125 0.036-11.554t-0.018-5.286l-0.018-0.857h5.875v2.571h-0.036q0.357-0.571 0.732-1t1.009-0.929 1.554-0.777 2.045-0.277q3.054 0 4.911 2.027t1.857 5.938z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-quote-right" viewBox="0 0 30 32">
|
|
||||||
<path class="path1" d="M13.714 5.714v12.571q0 1.857-0.723 3.545t-1.955 2.92-2.92 1.955-3.545 0.723h-1.143q-0.464 0-0.804-0.339t-0.339-0.804v-2.286q0-0.464 0.339-0.804t0.804-0.339h1.143q1.893 0 3.232-1.339t1.339-3.232v-0.571q0-0.714-0.5-1.214t-1.214-0.5h-4q-1.429 0-2.429-1t-1-2.429v-6.857q0-1.429 1-2.429t2.429-1h6.857q1.429 0 2.429 1t1 2.429zM29.714 5.714v12.571q0 1.857-0.723 3.545t-1.955 2.92-2.92 1.955-3.545 0.723h-1.143q-0.464 0-0.804-0.339t-0.339-0.804v-2.286q0-0.464 0.339-0.804t0.804-0.339h1.143q1.893 0 3.232-1.339t1.339-3.232v-0.571q0-0.714-0.5-1.214t-1.214-0.5h-4q-1.429 0-2.429-1t-1-2.429v-6.857q0-1.429 1-2.429t2.429-1h6.857q1.429 0 2.429 1t1 2.429z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-mail-reply" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M32 20q0 2.964-2.268 8.054-0.054 0.125-0.188 0.429t-0.241 0.536-0.232 0.393q-0.214 0.304-0.5 0.304-0.268 0-0.42-0.179t-0.152-0.446q0-0.161 0.045-0.473t0.045-0.42q0.089-1.214 0.089-2.196 0-1.804-0.313-3.232t-0.866-2.473-1.429-1.804-1.884-1.241-2.375-0.759-2.75-0.384-3.134-0.107h-4v4.571q0 0.464-0.339 0.804t-0.804 0.339-0.804-0.339l-9.143-9.143q-0.339-0.339-0.339-0.804t0.339-0.804l9.143-9.143q0.339-0.339 0.804-0.339t0.804 0.339 0.339 0.804v4.571h4q12.732 0 15.625 7.196 0.946 2.393 0.946 5.946z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-youtube" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M17.339 22.214v3.768q0 1.196-0.696 1.196-0.411 0-0.804-0.393v-5.375q0.393-0.393 0.804-0.393 0.696 0 0.696 1.196zM23.375 22.232v0.821h-1.607v-0.821q0-1.214 0.804-1.214t0.804 1.214zM6.125 18.339h1.911v-1.679h-5.571v1.679h1.875v10.161h1.786v-10.161zM11.268 28.5h1.589v-8.821h-1.589v6.75q-0.536 0.75-1.018 0.75-0.321 0-0.375-0.375-0.018-0.054-0.018-0.625v-6.5h-1.589v6.982q0 0.875 0.143 1.304 0.214 0.661 1.036 0.661 0.857 0 1.821-1.089v0.964zM18.929 25.857v-3.518q0-1.304-0.161-1.768-0.304-1-1.268-1-0.893 0-1.661 0.964v-3.875h-1.589v11.839h1.589v-0.857q0.804 0.982 1.661 0.982 0.964 0 1.268-0.982 0.161-0.482 0.161-1.786zM24.964 25.679v-0.232h-1.625q0 0.911-0.036 1.089-0.125 0.643-0.714 0.643-0.821 0-0.821-1.232v-1.554h3.196v-1.839q0-1.411-0.482-2.071-0.696-0.911-1.893-0.911-1.214 0-1.911 0.911-0.5 0.661-0.5 2.071v3.089q0 1.411 0.518 2.071 0.696 0.911 1.929 0.911 1.286 0 1.929-0.946 0.321-0.482 0.375-0.964 0.036-0.161 0.036-1.036zM14.107 9.375v-3.75q0-1.232-0.768-1.232t-0.768 1.232v3.75q0 1.25 0.768 1.25t0.768-1.25zM26.946 22.786q0 4.179-0.464 6.25-0.25 1.054-1.036 1.768t-1.821 0.821q-3.286 0.375-9.911 0.375t-9.911-0.375q-1.036-0.107-1.83-0.821t-1.027-1.768q-0.464-2-0.464-6.25 0-4.179 0.464-6.25 0.25-1.054 1.036-1.768t1.839-0.839q3.268-0.357 9.893-0.357t9.911 0.357q1.036 0.125 1.83 0.839t1.027 1.768q0.464 2 0.464 6.25zM9.125 0h1.821l-2.161 7.125v4.839h-1.786v-4.839q-0.25-1.321-1.089-3.786-0.661-1.839-1.161-3.339h1.893l1.268 4.696zM15.732 5.946v3.125q0 1.446-0.5 2.107-0.661 0.911-1.893 0.911-1.196 0-1.875-0.911-0.5-0.679-0.5-2.107v-3.125q0-1.429 0.5-2.089 0.679-0.911 1.875-0.911 1.232 0 1.893 0.911 0.5 0.661 0.5 2.089zM21.714 3.054v8.911h-1.625v-0.982q-0.946 1.107-1.839 1.107-0.821 0-1.054-0.661-0.143-0.429-0.143-1.339v-7.036h1.625v6.554q0 0.589 0.018 0.625 0.054 0.393 0.375 0.393 0.482 0 1.018-0.768v-6.804h1.625z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-dropbox" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M7.179 12.625l8.821 5.446-6.107 5.089-8.75-5.696zM24.786 22.536v1.929l-8.75 5.232v0.018l-0.018-0.018-0.018 0.018v-0.018l-8.732-5.232v-1.929l2.625 1.714 6.107-5.071v-0.036l0.018 0.018 0.018-0.018v0.036l6.125 5.071zM9.893 2.107l6.107 5.089-8.821 5.429-6.036-4.821zM24.821 12.625l6.036 4.839-8.732 5.696-6.125-5.089zM22.125 2.107l8.732 5.696-6.036 4.821-8.821-5.429z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-instagram" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M18.286 16q0-1.893-1.339-3.232t-3.232-1.339-3.232 1.339-1.339 3.232 1.339 3.232 3.232 1.339 3.232-1.339 1.339-3.232zM20.75 16q0 2.929-2.054 4.982t-4.982 2.054-4.982-2.054-2.054-4.982 2.054-4.982 4.982-2.054 4.982 2.054 2.054 4.982zM22.679 8.679q0 0.679-0.482 1.161t-1.161 0.482-1.161-0.482-0.482-1.161 0.482-1.161 1.161-0.482 1.161 0.482 0.482 1.161zM13.714 4.75q-0.125 0-1.366-0.009t-1.884 0-1.723 0.054-1.839 0.179-1.277 0.33q-0.893 0.357-1.571 1.036t-1.036 1.571q-0.196 0.518-0.33 1.277t-0.179 1.839-0.054 1.723 0 1.884 0.009 1.366-0.009 1.366 0 1.884 0.054 1.723 0.179 1.839 0.33 1.277q0.357 0.893 1.036 1.571t1.571 1.036q0.518 0.196 1.277 0.33t1.839 0.179 1.723 0.054 1.884 0 1.366-0.009 1.366 0.009 1.884 0 1.723-0.054 1.839-0.179 1.277-0.33q0.893-0.357 1.571-1.036t1.036-1.571q0.196-0.518 0.33-1.277t0.179-1.839 0.054-1.723 0-1.884-0.009-1.366 0.009-1.366 0-1.884-0.054-1.723-0.179-1.839-0.33-1.277q-0.357-0.893-1.036-1.571t-1.571-1.036q-0.518-0.196-1.277-0.33t-1.839-0.179-1.723-0.054-1.884 0-1.366 0.009zM27.429 16q0 4.089-0.089 5.661-0.179 3.714-2.214 5.75t-5.75 2.214q-1.571 0.089-5.661 0.089t-5.661-0.089q-3.714-0.179-5.75-2.214t-2.214-5.75q-0.089-1.571-0.089-5.661t0.089-5.661q0.179-3.714 2.214-5.75t5.75-2.214q1.571-0.089 5.661-0.089t5.661 0.089q3.714 0.179 5.75 2.214t2.214 5.75q0.089 1.571 0.089 5.661z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-flickr" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M22.286 2.286q2.125 0 3.634 1.509t1.509 3.634v17.143q0 2.125-1.509 3.634t-3.634 1.509h-17.143q-2.125 0-3.634-1.509t-1.509-3.634v-17.143q0-2.125 1.509-3.634t3.634-1.509h17.143zM12.464 16q0-1.571-1.107-2.679t-2.679-1.107-2.679 1.107-1.107 2.679 1.107 2.679 2.679 1.107 2.679-1.107 1.107-2.679zM22.536 16q0-1.571-1.107-2.679t-2.679-1.107-2.679 1.107-1.107 2.679 1.107 2.679 2.679 1.107 2.679-1.107 1.107-2.679z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-tumblr" viewBox="0 0 19 32">
|
|
||||||
<path class="path1" d="M16.857 23.732l1.429 4.232q-0.411 0.625-1.982 1.179t-3.161 0.571q-1.857 0.036-3.402-0.464t-2.545-1.321-1.696-1.893-0.991-2.143-0.295-2.107v-9.714h-3v-3.839q1.286-0.464 2.304-1.241t1.625-1.607 1.036-1.821 0.607-1.768 0.268-1.58q0.018-0.089 0.080-0.152t0.134-0.063h4.357v7.571h5.946v4.5h-5.964v9.25q0 0.536 0.116 1t0.402 0.938 0.884 0.741 1.455 0.25q1.393-0.036 2.393-0.518z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-dockerhub" viewBox="0 0 24 28">
|
|
||||||
<path class="path1" d="M1.597 10.257h2.911v2.83H1.597v-2.83zm3.573 0h2.91v2.83H5.17v-2.83zm0-3.627h2.91v2.829H5.17V6.63zm3.57 3.627h2.912v2.83H8.74v-2.83zm0-3.627h2.912v2.829H8.74V6.63zm3.573 3.627h2.911v2.83h-2.911v-2.83zm0-3.627h2.911v2.829h-2.911V6.63zm3.572 3.627h2.911v2.83h-2.911v-2.83zM12.313 3h2.911v2.83h-2.911V3zm-6.65 14.173c-.449 0-.812.354-.812.788 0 .435.364.788.812.788.447 0 .811-.353.811-.788 0-.434-.363-.788-.811-.788"></path>
|
|
||||||
<path class="path2" d="M28.172 11.721c-.978-.549-2.278-.624-3.388-.306-.136-1.146-.91-2.149-1.83-2.869l-.366-.286-.307.345c-.618.692-.8 1.845-.718 2.73.063.651.273 1.312.685 1.834-.313.183-.668.328-.985.434-.646.212-1.347.33-2.028.33H.083l-.042.429c-.137 1.432.065 2.866.674 4.173l.262.519.03.048c1.8 2.973 4.963 4.225 8.41 4.225 6.672 0 12.174-2.896 14.702-9.015 1.689.085 3.417-.4 4.243-1.968l.211-.4-.401-.223zM5.664 19.458c-.85 0-1.542-.671-1.542-1.497 0-.825.691-1.498 1.541-1.498.849 0 1.54.672 1.54 1.497s-.69 1.498-1.539 1.498z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-dribbble" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M18.286 26.786q-0.75-4.304-2.5-8.893h-0.036l-0.036 0.018q-0.286 0.107-0.768 0.295t-1.804 0.875-2.446 1.464-2.339 2.045-1.839 2.643l-0.268-0.196q3.286 2.679 7.464 2.679 2.357 0 4.571-0.929zM14.982 15.946q-0.375-0.875-0.946-1.982-5.554 1.661-12.018 1.661-0.018 0.125-0.018 0.375 0 2.214 0.786 4.223t2.214 3.598q0.893-1.589 2.205-2.973t2.545-2.223 2.33-1.446 1.777-0.857l0.661-0.232q0.071-0.018 0.232-0.063t0.232-0.080zM13.071 12.161q-2.143-3.804-4.357-6.75-2.464 1.161-4.179 3.321t-2.286 4.857q5.393 0 10.821-1.429zM25.286 17.857q-3.75-1.071-7.304-0.518 1.554 4.268 2.286 8.375 1.982-1.339 3.304-3.384t1.714-4.473zM10.911 4.625q-0.018 0-0.036 0.018 0.018-0.018 0.036-0.018zM21.446 7.214q-3.304-2.929-7.732-2.929-1.357 0-2.768 0.339 2.339 3.036 4.393 6.821 1.232-0.464 2.321-1.080t1.723-1.098 1.17-1.018 0.67-0.723zM25.429 15.875q-0.054-4.143-2.661-7.321l-0.018 0.018q-0.161 0.214-0.339 0.438t-0.777 0.795-1.268 1.080-1.786 1.161-2.348 1.152q0.446 0.946 0.786 1.696 0.036 0.107 0.116 0.313t0.134 0.295q0.643-0.089 1.33-0.125t1.313-0.036 1.232 0.027 1.143 0.071 1.009 0.098 0.857 0.116 0.652 0.107 0.446 0.080zM27.429 16q0 3.732-1.839 6.884t-4.991 4.991-6.884 1.839-6.884-1.839-4.991-4.991-1.839-6.884 1.839-6.884 4.991-4.991 6.884-1.839 6.884 1.839 4.991 4.991 1.839 6.884z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-skype" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M20.946 18.982q0-0.893-0.348-1.634t-0.866-1.223-1.304-0.875-1.473-0.607-1.563-0.411l-1.857-0.429q-0.536-0.125-0.786-0.188t-0.625-0.205-0.536-0.286-0.295-0.375-0.134-0.536q0-1.375 2.571-1.375 0.768 0 1.375 0.214t0.964 0.509 0.679 0.598 0.714 0.518 0.857 0.214q0.839 0 1.348-0.571t0.509-1.375q0-0.982-1-1.777t-2.536-1.205-3.25-0.411q-1.214 0-2.357 0.277t-2.134 0.839-1.589 1.554-0.598 2.295q0 1.089 0.339 1.902t1 1.348 1.429 0.866 1.839 0.58l2.607 0.643q1.607 0.393 2 0.643 0.571 0.357 0.571 1.071 0 0.696-0.714 1.152t-1.875 0.455q-0.911 0-1.634-0.286t-1.161-0.688-0.813-0.804-0.821-0.688-0.964-0.286q-0.893 0-1.348 0.536t-0.455 1.339q0 1.643 2.179 2.813t5.196 1.17q1.304 0 2.5-0.33t2.188-0.955 1.58-1.67 0.589-2.348zM27.429 22.857q0 2.839-2.009 4.848t-4.848 2.009q-2.321 0-4.179-1.429-1.375 0.286-2.679 0.286-2.554 0-4.884-0.991t-4.018-2.679-2.679-4.018-0.991-4.884q0-1.304 0.286-2.679-1.429-1.857-1.429-4.179 0-2.839 2.009-4.848t4.848-2.009q2.321 0 4.179 1.429 1.375-0.286 2.679-0.286 2.554 0 4.884 0.991t4.018 2.679 2.679 4.018 0.991 4.884q0 1.304-0.286 2.679 1.429 1.857 1.429 4.179z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-foursquare" viewBox="0 0 23 32">
|
|
||||||
<path class="path1" d="M17.857 7.75l0.661-3.464q0.089-0.411-0.161-0.714t-0.625-0.304h-12.714q-0.411 0-0.688 0.304t-0.277 0.661v19.661q0 0.125 0.107 0.018l5.196-6.286q0.411-0.464 0.679-0.598t0.857-0.134h4.268q0.393 0 0.661-0.259t0.321-0.527q0.429-2.321 0.661-3.411 0.071-0.375-0.205-0.714t-0.652-0.339h-5.25q-0.518 0-0.857-0.339t-0.339-0.857v-0.75q0-0.518 0.339-0.848t0.857-0.33h6.179q0.321 0 0.625-0.241t0.357-0.527zM21.911 3.786q-0.268 1.304-0.955 4.759t-1.241 6.25-0.625 3.098q-0.107 0.393-0.161 0.58t-0.25 0.58-0.438 0.589-0.688 0.375-1.036 0.179h-4.839q-0.232 0-0.393 0.179-0.143 0.161-7.607 8.821-0.393 0.446-1.045 0.509t-0.866-0.098q-0.982-0.393-0.982-1.75v-25.179q0-0.982 0.679-1.83t2.143-0.848h15.857q1.696 0 2.268 0.946t0.179 2.839zM21.911 3.786l-2.821 14.107q0.071-0.304 0.625-3.098t1.241-6.25 0.955-4.759z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-wordpress" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M2.268 16q0-2.911 1.196-5.589l6.554 17.946q-3.5-1.696-5.625-5.018t-2.125-7.339zM25.268 15.304q0 0.339-0.045 0.688t-0.179 0.884-0.205 0.786-0.313 1.054-0.313 1.036l-1.357 4.571-4.964-14.75q0.821-0.054 1.571-0.143 0.339-0.036 0.464-0.33t-0.045-0.554-0.509-0.241l-3.661 0.179q-1.339-0.018-3.607-0.179-0.214-0.018-0.366 0.089t-0.205 0.268-0.027 0.33 0.161 0.295 0.348 0.143l1.429 0.143 2.143 5.857-3 9-5-14.857q0.821-0.054 1.571-0.143 0.339-0.036 0.464-0.33t-0.045-0.554-0.509-0.241l-3.661 0.179q-0.125 0-0.411-0.009t-0.464-0.009q1.875-2.857 4.902-4.527t6.563-1.67q2.625 0 5.009 0.946t4.259 2.661h-0.179q-0.982 0-1.643 0.723t-0.661 1.705q0 0.214 0.036 0.429t0.071 0.384 0.143 0.411 0.161 0.375 0.214 0.402 0.223 0.375 0.259 0.429 0.25 0.411q1.125 1.911 1.125 3.786zM16.232 17.196l4.232 11.554q0.018 0.107 0.089 0.196-2.25 0.786-4.554 0.786-2 0-3.875-0.571zM28.036 9.411q1.696 3.107 1.696 6.589 0 3.732-1.857 6.884t-4.982 4.973l4.196-12.107q1.054-3.018 1.054-4.929 0-0.75-0.107-1.411zM16 0q3.25 0 6.214 1.268t5.107 3.411 3.411 5.107 1.268 6.214-1.268 6.214-3.411 5.107-5.107 3.411-6.214 1.268-6.214-1.268-5.107-3.411-3.411-5.107-1.268-6.214 1.268-6.214 3.411-5.107 5.107-3.411 6.214-1.268zM16 31.268q3.089 0 5.92-1.214t4.875-3.259 3.259-4.875 1.214-5.92-1.214-5.92-3.259-4.875-4.875-3.259-5.92-1.214-5.92 1.214-4.875 3.259-3.259 4.875-1.214 5.92 1.214 5.92 3.259 4.875 4.875 3.259 5.92 1.214z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-stumbleupon" viewBox="0 0 34 32">
|
|
||||||
<path class="path1" d="M18.964 12.714v-2.107q0-0.75-0.536-1.286t-1.286-0.536-1.286 0.536-0.536 1.286v10.929q0 3.125-2.25 5.339t-5.411 2.214q-3.179 0-5.42-2.241t-2.241-5.42v-4.75h5.857v4.679q0 0.768 0.536 1.295t1.286 0.527 1.286-0.527 0.536-1.295v-11.071q0-3.054 2.259-5.214t5.384-2.161q3.143 0 5.393 2.179t2.25 5.25v2.429l-3.482 1.036zM28.429 16.679h5.857v4.75q0 3.179-2.241 5.42t-5.42 2.241q-3.161 0-5.411-2.223t-2.25-5.366v-4.786l2.339 1.089 3.482-1.036v4.821q0 0.75 0.536 1.277t1.286 0.527 1.286-0.527 0.536-1.277v-4.911z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-digg" viewBox="0 0 37 32">
|
|
||||||
<path class="path1" d="M5.857 5.036h3.643v17.554h-9.5v-12.446h5.857v-5.107zM5.857 19.661v-6.589h-2.196v6.589h2.196zM10.964 10.143v12.446h3.661v-12.446h-3.661zM10.964 5.036v3.643h3.661v-3.643h-3.661zM16.089 10.143h9.518v16.821h-9.518v-2.911h5.857v-1.464h-5.857v-12.446zM21.946 19.661v-6.589h-2.196v6.589h2.196zM27.071 10.143h9.5v16.821h-9.5v-2.911h5.839v-1.464h-5.839v-12.446zM32.911 19.661v-6.589h-2.196v6.589h2.196z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-spotify" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M20.125 21.607q0-0.571-0.536-0.911-3.446-2.054-7.982-2.054-2.375 0-5.125 0.607-0.75 0.161-0.75 0.929 0 0.357 0.241 0.616t0.634 0.259q0.089 0 0.661-0.143 2.357-0.482 4.339-0.482 4.036 0 7.089 1.839 0.339 0.196 0.589 0.196 0.339 0 0.589-0.241t0.25-0.616zM21.839 17.768q0-0.714-0.625-1.089-4.232-2.518-9.786-2.518-2.732 0-5.411 0.75-0.857 0.232-0.857 1.143 0 0.446 0.313 0.759t0.759 0.313q0.125 0 0.661-0.143 2.179-0.589 4.482-0.589 4.982 0 8.714 2.214 0.429 0.232 0.679 0.232 0.446 0 0.759-0.313t0.313-0.759zM23.768 13.339q0-0.839-0.714-1.25-2.25-1.304-5.232-1.973t-6.125-0.67q-3.643 0-6.5 0.839-0.411 0.125-0.688 0.455t-0.277 0.866q0 0.554 0.366 0.929t0.92 0.375q0.196 0 0.714-0.143 2.375-0.661 5.482-0.661 2.839 0 5.527 0.607t4.527 1.696q0.375 0.214 0.714 0.214 0.518 0 0.902-0.366t0.384-0.92zM27.429 16q0 3.732-1.839 6.884t-4.991 4.991-6.884 1.839-6.884-1.839-4.991-4.991-1.839-6.884 1.839-6.884 4.991-4.991 6.884-1.839 6.884 1.839 4.991 4.991 1.839 6.884z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-soundcloud" viewBox="0 0 41 32">
|
|
||||||
<path class="path1" d="M14 24.5l0.286-4.304-0.286-9.339q-0.018-0.179-0.134-0.304t-0.295-0.125q-0.161 0-0.286 0.125t-0.125 0.304l-0.25 9.339 0.25 4.304q0.018 0.179 0.134 0.295t0.277 0.116q0.393 0 0.429-0.411zM19.286 23.982l0.196-3.768-0.214-10.464q0-0.286-0.232-0.429-0.143-0.089-0.286-0.089t-0.286 0.089q-0.232 0.143-0.232 0.429l-0.018 0.107-0.179 10.339q0 0.018 0.196 4.214v0.018q0 0.179 0.107 0.304 0.161 0.196 0.411 0.196 0.196 0 0.357-0.161 0.161-0.125 0.161-0.357zM0.625 17.911l0.357 2.286-0.357 2.25q-0.036 0.161-0.161 0.161t-0.161-0.161l-0.304-2.25 0.304-2.286q0.036-0.161 0.161-0.161t0.161 0.161zM2.161 16.5l0.464 3.696-0.464 3.625q-0.036 0.161-0.179 0.161-0.161 0-0.161-0.179l-0.411-3.607 0.411-3.696q0-0.161 0.161-0.161 0.143 0 0.179 0.161zM3.804 15.821l0.446 4.375-0.446 4.232q0 0.196-0.196 0.196-0.179 0-0.214-0.196l-0.375-4.232 0.375-4.375q0.036-0.214 0.214-0.214 0.196 0 0.196 0.214zM5.482 15.696l0.411 4.5-0.411 4.357q-0.036 0.232-0.25 0.232-0.232 0-0.232-0.232l-0.375-4.357 0.375-4.5q0-0.232 0.232-0.232 0.214 0 0.25 0.232zM7.161 16.018l0.375 4.179-0.375 4.393q-0.036 0.286-0.286 0.286-0.107 0-0.188-0.080t-0.080-0.205l-0.357-4.393 0.357-4.179q0-0.107 0.080-0.188t0.188-0.080q0.25 0 0.286 0.268zM8.839 13.411l0.375 6.786-0.375 4.393q0 0.125-0.089 0.223t-0.214 0.098q-0.286 0-0.321-0.321l-0.321-4.393 0.321-6.786q0.036-0.321 0.321-0.321 0.125 0 0.214 0.098t0.089 0.223zM10.518 11.875l0.339 8.357-0.339 4.357q0 0.143-0.098 0.241t-0.241 0.098q-0.321 0-0.357-0.339l-0.286-4.357 0.286-8.357q0.036-0.339 0.357-0.339 0.143 0 0.241 0.098t0.098 0.241zM12.268 11.161l0.321 9.036-0.321 4.321q-0.036 0.375-0.393 0.375-0.339 0-0.375-0.375l-0.286-4.321 0.286-9.036q0-0.161 0.116-0.277t0.259-0.116q0.161 0 0.268 0.116t0.125 0.277zM19.268 24.411v0 0zM15.732 11.089l0.268 9.107-0.268 4.268q0 0.179-0.134 0.313t-0.313 0.134-0.304-0.125-0.143-0.321l-0.25-4.268 0.25-9.107q0-0.196 0.134-0.321t0.313-0.125 0.313 0.125 0.134 0.321zM17.5 11.429l0.25 8.786-0.25 4.214q0 0.196-0.143 0.339t-0.339 0.143-0.339-0.143-0.161-0.339l-0.214-4.214 0.214-8.786q0.018-0.214 0.161-0.357t0.339-0.143 0.33 0.143 0.152 0.357zM21.286 20.214l-0.25 4.125q0 0.232-0.161 0.393t-0.393 0.161-0.393-0.161-0.179-0.393l-0.107-2.036-0.107-2.089 0.214-11.357v-0.054q0.036-0.268 0.214-0.429 0.161-0.125 0.357-0.125 0.143 0 0.268 0.089 0.25 0.143 0.286 0.464zM41.143 19.875q0 2.089-1.482 3.563t-3.571 1.473h-14.036q-0.232-0.036-0.393-0.196t-0.161-0.393v-16.054q0-0.411 0.5-0.589 1.518-0.607 3.232-0.607 3.482 0 6.036 2.348t2.857 5.777q0.946-0.393 1.964-0.393 2.089 0 3.571 1.482t1.482 3.589z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-codepen" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M3.857 20.875l10.768 7.179v-6.411l-5.964-3.982zM2.75 18.304l3.446-2.304-3.446-2.304v4.607zM17.375 28.054l10.768-7.179-4.804-3.214-5.964 3.982v6.411zM16 19.25l4.857-3.25-4.857-3.25-4.857 3.25zM8.661 14.339l5.964-3.982v-6.411l-10.768 7.179zM25.804 16l3.446 2.304v-4.607zM23.339 14.339l4.804-3.214-10.768-7.179v6.411zM32 11.125v9.75q0 0.732-0.607 1.143l-14.625 9.75q-0.375 0.232-0.768 0.232t-0.768-0.232l-14.625-9.75q-0.607-0.411-0.607-1.143v-9.75q0-0.732 0.607-1.143l14.625-9.75q0.375-0.232 0.768-0.232t0.768 0.232l14.625 9.75q0.607 0.411 0.607 1.143z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-twitch" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M16 7.75v7.75h-2.589v-7.75h2.589zM23.107 7.75v7.75h-2.589v-7.75h2.589zM23.107 21.321l4.518-4.536v-14.196h-21.321v18.732h5.821v3.875l3.875-3.875h7.107zM30.214 0v18.089l-7.75 7.75h-5.821l-3.875 3.875h-3.875v-3.875h-7.107v-20.679l1.946-5.161h26.482z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-meanpath" viewBox="0 0 27 32">
|
|
||||||
<path class="path1" d="M23.411 15.036v2.036q0 0.429-0.241 0.679t-0.67 0.25h-3.607q-0.429 0-0.679-0.25t-0.25-0.679v-2.036q0-0.429 0.25-0.679t0.679-0.25h3.607q0.429 0 0.67 0.25t0.241 0.679zM14.661 19.143v-4.464q0-0.946-0.58-1.527t-1.527-0.58h-2.375q-1.214 0-1.714 0.929-0.5-0.929-1.714-0.929h-2.321q-0.946 0-1.527 0.58t-0.58 1.527v4.464q0 0.393 0.375 0.393h0.982q0.393 0 0.393-0.393v-4.107q0-0.429 0.241-0.679t0.688-0.25h1.679q0.429 0 0.679 0.25t0.25 0.679v4.107q0 0.393 0.375 0.393h0.964q0.393 0 0.393-0.393v-4.107q0-0.429 0.25-0.679t0.679-0.25h1.732q0.429 0 0.67 0.25t0.241 0.679v4.107q0 0.393 0.393 0.393h0.982q0.375 0 0.375-0.393zM25.179 17.429v-2.75q0-0.946-0.589-1.527t-1.536-0.58h-4.714q-0.946 0-1.536 0.58t-0.589 1.527v7.321q0 0.375 0.393 0.375h0.982q0.375 0 0.375-0.375v-3.214q0.554 0.75 1.679 0.75h3.411q0.946 0 1.536-0.58t0.589-1.527zM27.429 6.429v19.143q0 1.714-1.214 2.929t-2.929 1.214h-19.143q-1.714 0-2.929-1.214t-1.214-2.929v-19.143q0-1.714 1.214-2.929t2.929-1.214h19.143q1.714 0 2.929 1.214t1.214 2.929z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-pinterest-p" viewBox="0 0 23 32">
|
|
||||||
<path class="path1" d="M0 10.661q0-1.929 0.67-3.634t1.848-2.973 2.714-2.196 3.304-1.393 3.607-0.464q2.821 0 5.25 1.188t3.946 3.455 1.518 5.125q0 1.714-0.339 3.357t-1.071 3.161-1.786 2.67-2.589 1.839-3.375 0.688q-1.214 0-2.411-0.571t-1.714-1.571q-0.179 0.696-0.5 2.009t-0.42 1.696-0.366 1.268-0.464 1.268-0.571 1.116-0.821 1.384-1.107 1.545l-0.25 0.089-0.161-0.179q-0.268-2.804-0.268-3.357 0-1.643 0.384-3.688t1.188-5.134 0.929-3.625q-0.571-1.161-0.571-3.018 0-1.482 0.929-2.786t2.357-1.304q1.089 0 1.696 0.723t0.607 1.83q0 1.179-0.786 3.411t-0.786 3.339q0 1.125 0.804 1.866t1.946 0.741q0.982 0 1.821-0.446t1.402-1.214 1-1.696 0.679-1.973 0.357-1.982 0.116-1.777q0-3.089-1.955-4.813t-5.098-1.723q-3.571 0-5.964 2.313t-2.393 5.866q0 0.786 0.223 1.518t0.482 1.161 0.482 0.813 0.223 0.545q0 0.5-0.268 1.304t-0.661 0.804q-0.036 0-0.304-0.054-0.911-0.268-1.616-1t-1.089-1.688-0.58-1.929-0.196-1.902z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-periscope" viewBox="0 0 24 28">
|
|
||||||
<path class="path1" d="M12.285,1C6.696,1,2.277,5.643,2.277,11.243c0,5.851,7.77,14.578,10.007,14.578c1.959,0,9.729-8.728,9.729-14.578 C22.015,5.643,17.596,1,12.285,1z M12.317,16.551c-3.473,0-6.152-2.611-6.152-5.664c0-1.292,0.39-2.472,1.065-3.438 c0.206,1.084,1.18,1.906,2.352,1.906c1.322,0,2.393-1.043,2.393-2.333c0-0.832-0.447-1.561-1.119-1.975 c0.467-0.105,0.955-0.161,1.46-0.161c3.133,0,5.81,2.611,5.81,5.998C18.126,13.94,15.449,16.551,12.317,16.551z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-get-pocket" viewBox="0 0 31 32">
|
|
||||||
<path class="path1" d="M27.946 2.286q1.161 0 1.964 0.813t0.804 1.973v9.268q0 3.143-1.214 6t-3.259 4.911-4.893 3.259-5.973 1.205q-3.143 0-5.991-1.205t-4.902-3.259-3.268-4.911-1.214-6v-9.268q0-1.143 0.821-1.964t1.964-0.821h25.161zM15.375 21.286q0.839 0 1.464-0.589l7.214-6.929q0.661-0.625 0.661-1.518 0-0.875-0.616-1.491t-1.491-0.616q-0.839 0-1.464 0.589l-5.768 5.536-5.768-5.536q-0.625-0.589-1.446-0.589-0.875 0-1.491 0.616t-0.616 1.491q0 0.911 0.643 1.518l7.232 6.929q0.589 0.589 1.446 0.589z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-vimeo" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M30.518 9.25q-0.179 4.214-5.929 11.625-5.946 7.696-10.036 7.696-2.536 0-4.286-4.696-0.786-2.857-2.357-8.607-1.286-4.679-2.804-4.679-0.321 0-2.268 1.357l-1.375-1.75q0.429-0.375 1.929-1.723t2.321-2.063q2.786-2.464 4.304-2.607 1.696-0.161 2.732 0.991t1.446 3.634q0.786 5.125 1.179 6.661 0.982 4.446 2.143 4.446 0.911 0 2.75-2.875 1.804-2.875 1.946-4.393 0.232-2.482-1.946-2.482-1.018 0-2.161 0.464 2.143-7.018 8.196-6.821 4.482 0.143 4.214 5.821z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-reddit-alien" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M32 15.107q0 1.036-0.527 1.884t-1.42 1.295q0.214 0.821 0.214 1.714 0 2.768-1.902 5.125t-5.188 3.723-7.143 1.366-7.134-1.366-5.179-3.723-1.902-5.125q0-0.839 0.196-1.679-0.911-0.446-1.464-1.313t-0.554-1.902q0-1.464 1.036-2.509t2.518-1.045q1.518 0 2.589 1.125 3.893-2.714 9.196-2.893l2.071-9.304q0.054-0.232 0.268-0.375t0.464-0.089l6.589 1.446q0.321-0.661 0.964-1.063t1.411-0.402q1.107 0 1.893 0.777t0.786 1.884-0.786 1.893-1.893 0.786-1.884-0.777-0.777-1.884l-5.964-1.321-1.857 8.429q5.357 0.161 9.268 2.857 1.036-1.089 2.554-1.089 1.482 0 2.518 1.045t1.036 2.509zM7.464 18.661q0 1.107 0.777 1.893t1.884 0.786 1.893-0.786 0.786-1.893-0.786-1.884-1.893-0.777q-1.089 0-1.875 0.786t-0.786 1.875zM21.929 25q0.196-0.196 0.196-0.464t-0.196-0.464q-0.179-0.179-0.446-0.179t-0.464 0.179q-0.732 0.75-2.161 1.107t-2.857 0.357-2.857-0.357-2.161-1.107q-0.196-0.179-0.464-0.179t-0.446 0.179q-0.196 0.179-0.196 0.455t0.196 0.473q0.768 0.768 2.116 1.214t2.188 0.527 1.625 0.080 1.625-0.080 2.188-0.527 2.116-1.214zM21.875 21.339q1.107 0 1.884-0.786t0.777-1.893q0-1.089-0.786-1.875t-1.875-0.786q-1.107 0-1.893 0.777t-0.786 1.884 0.786 1.893 1.893 0.786z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-whatsapp" viewBox="0 0 32 32">
|
|
||||||
<path d="M15.968 2.003a14.03 13.978 0 0 0-14.03 13.978 14.03 13.978 0 0 0 2.132 7.391L1.938 29.96l6.745-2.052a14.03 13.978 0 0 0 7.285 2.052 14.03 13.978 0 0 0 14.03-13.978 14.03 13.978 0 0 0-14.03-13.978z" stroke-width=".2000562"></path>
|
|
||||||
<path d="M10.454 8.236a2.57 3.401 51.533 0 0-1.475 3.184v.015c.01 2.04 4.045 10.076 10.017 12.688l.017-.013a2.57 3.401 51.533 0 0 3.454-.706 2.57 3.401 51.533 0 0 1.064-4.129 2.57 3.401 51.533 0 0-4.262.103 2.57 3.401 51.533 0 0-.505.473c-1.346-.639-2.952-1.463-4.168-2.98-.771-.962-1.257-2.732-1.549-4.206a2.57 3.401 51.533 0 0 .605-.403 2.57 3.401 51.533 0 0 1.064-4.129 2.57 3.401 51.533 0 0-4.262.103z" stroke-width=".372"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-telegram" viewBox="0 0 32 32">
|
|
||||||
<path d="M30.8,2.2L0.6,13.9c-0.8,0.3-0.7,1.3,0,1.6l7.4,2.8l2.9,9.2c0.2,0.6,0.9,0.8,1.4,0.4l4.1-3.4 c0.4-0.4,1-0.4,1.5,0l7.4,5.4c0.5,0.4,1.2,0.1,1.4-0.5L32,3.2C32.1,2.5,31.4,1.9,30.8,2.2z M25,8.3l-11.9,11 c-0.4,0.4-0.7,0.9-0.8,1.5l-0.4,3c-0.1,0.4-0.6,0.4-0.7,0.1l-1.6-5.5c-0.2-0.6,0.1-1.3,0.6-1.6l14.4-8.9C25,7.7,25.3,8.1,25,8.3z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-hashtag" viewBox="0 0 32 32">
|
|
||||||
<path class="path1" d="M17.696 18.286l1.143-4.571h-4.536l-1.143 4.571h4.536zM31.411 9.286l-1 4q-0.125 0.429-0.554 0.429h-5.839l-1.143 4.571h5.554q0.268 0 0.446 0.214 0.179 0.25 0.107 0.5l-1 4q-0.089 0.429-0.554 0.429h-5.839l-1.446 5.857q-0.125 0.429-0.554 0.429h-4q-0.286 0-0.464-0.214-0.161-0.214-0.107-0.5l1.393-5.571h-4.536l-1.446 5.857q-0.125 0.429-0.554 0.429h-4.018q-0.268 0-0.446-0.214-0.161-0.214-0.107-0.5l1.393-5.571h-5.554q-0.268 0-0.446-0.214-0.161-0.214-0.107-0.5l1-4q0.125-0.429 0.554-0.429h5.839l1.143-4.571h-5.554q-0.268 0-0.446-0.214-0.179-0.25-0.107-0.5l1-4q0.089-0.429 0.554-0.429h5.839l1.446-5.857q0.125-0.429 0.571-0.429h4q0.268 0 0.446 0.214 0.161 0.214 0.107 0.5l-1.393 5.571h4.536l1.446-5.857q0.125-0.429 0.571-0.429h4q0.268 0 0.446 0.214 0.161 0.214 0.107 0.5l-1.393 5.571h5.554q0.268 0 0.446 0.214 0.161 0.214 0.107 0.5z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-chain" viewBox="0 0 30 32">
|
|
||||||
<path class="path1" d="M26 21.714q0-0.714-0.5-1.214l-3.714-3.714q-0.5-0.5-1.214-0.5-0.75 0-1.286 0.571 0.054 0.054 0.339 0.33t0.384 0.384 0.268 0.339 0.232 0.455 0.063 0.491q0 0.714-0.5 1.214t-1.214 0.5q-0.268 0-0.491-0.063t-0.455-0.232-0.339-0.268-0.384-0.384-0.33-0.339q-0.589 0.554-0.589 1.304 0 0.714 0.5 1.214l3.679 3.696q0.482 0.482 1.214 0.482 0.714 0 1.214-0.464l2.625-2.607q0.5-0.5 0.5-1.196zM13.446 9.125q0-0.714-0.5-1.214l-3.679-3.696q-0.5-0.5-1.214-0.5-0.696 0-1.214 0.482l-2.625 2.607q-0.5 0.5-0.5 1.196 0 0.714 0.5 1.214l3.714 3.714q0.482 0.482 1.214 0.482 0.75 0 1.286-0.554-0.054-0.054-0.339-0.33t-0.384-0.384-0.268-0.339-0.232-0.455-0.063-0.491q0-0.714 0.5-1.214t1.214-0.5q0.268 0 0.491 0.063t0.455 0.232 0.339 0.268 0.384 0.384 0.33 0.339q0.589-0.554 0.589-1.304zM29.429 21.714q0 2.143-1.518 3.625l-2.625 2.607q-1.482 1.482-3.625 1.482-2.161 0-3.643-1.518l-3.679-3.696q-1.482-1.482-1.482-3.625 0-2.196 1.571-3.732l-1.571-1.571q-1.536 1.571-3.714 1.571-2.143 0-3.643-1.5l-3.714-3.714q-1.5-1.5-1.5-3.643t1.518-3.625l2.625-2.607q1.482-1.482 3.625-1.482 2.161 0 3.643 1.518l3.679 3.696q1.482 1.482 1.482 3.625 0 2.196-1.571 3.732l1.571 1.571q1.536-1.571 3.714-1.571 2.143 0 3.643 1.5l3.714 3.714q1.5 1.5 1.5 3.643z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-thumb-tack" viewBox="0 0 21 32">
|
|
||||||
<path class="path1" d="M8.571 15.429v-8q0-0.25-0.161-0.411t-0.411-0.161-0.411 0.161-0.161 0.411v8q0 0.25 0.161 0.411t0.411 0.161 0.411-0.161 0.161-0.411zM20.571 21.714q0 0.464-0.339 0.804t-0.804 0.339h-7.661l-0.911 8.625q-0.036 0.214-0.188 0.366t-0.366 0.152h-0.018q-0.482 0-0.571-0.482l-1.357-8.661h-7.214q-0.464 0-0.804-0.339t-0.339-0.804q0-2.196 1.402-3.955t3.17-1.759v-9.143q-0.929 0-1.607-0.679t-0.679-1.607 0.679-1.607 1.607-0.679h11.429q0.929 0 1.607 0.679t0.679 1.607-0.679 1.607-1.607 0.679v9.143q1.768 0 3.17 1.759t1.402 3.955z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-arrow-left" viewBox="0 0 43 32">
|
|
||||||
<path class="path1" d="M42.311 14.044c-0.178-0.178-0.533-0.356-0.711-0.356h-33.778l10.311-10.489c0.178-0.178 0.356-0.533 0.356-0.711 0-0.356-0.178-0.533-0.356-0.711l-1.6-1.422c-0.356-0.178-0.533-0.356-0.889-0.356s-0.533 0.178-0.711 0.356l-14.578 14.933c-0.178 0.178-0.356 0.533-0.356 0.711s0.178 0.533 0.356 0.711l14.756 14.933c0 0.178 0.356 0.356 0.533 0.356s0.533-0.178 0.711-0.356l1.6-1.6c0.178-0.178 0.356-0.533 0.356-0.711s-0.178-0.533-0.356-0.711l-10.311-10.489h33.778c0.178 0 0.533-0.178 0.711-0.356 0.356-0.178 0.533-0.356 0.533-0.711v-2.133c0-0.356-0.178-0.711-0.356-0.889z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-arrow-right" viewBox="0 0 43 32">
|
|
||||||
<path class="path1" d="M0.356 17.956c0.178 0.178 0.533 0.356 0.711 0.356h33.778l-10.311 10.489c-0.178 0.178-0.356 0.533-0.356 0.711 0 0.356 0.178 0.533 0.356 0.711l1.6 1.6c0.178 0.178 0.533 0.356 0.711 0.356s0.533-0.178 0.711-0.356l14.756-14.933c0.178-0.356 0.356-0.711 0.356-0.889s-0.178-0.533-0.356-0.711l-14.756-14.933c0-0.178-0.356-0.356-0.533-0.356s-0.533 0.178-0.711 0.356l-1.6 1.6c-0.178 0.178-0.356 0.533-0.356 0.711s0.178 0.533 0.356 0.711l10.311 10.489h-33.778c-0.178 0-0.533 0.178-0.711 0.356-0.356 0.178-0.533 0.356-0.533 0.711v2.311c0 0.178 0.178 0.533 0.356 0.711z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-play" viewBox="0 0 22 28">
|
|
||||||
<path d="M21.625 14.484l-20.75 11.531c-0.484 0.266-0.875 0.031-0.875-0.516v-23c0-0.547 0.391-0.781 0.875-0.516l20.75 11.531c0.484 0.266 0.484 0.703 0 0.969z"></path>
|
|
||||||
</symbol>
|
|
||||||
<symbol id="icon-pause" viewBox="0 0 24 28">
|
|
||||||
<path d="M24 3v22c0 0.547-0.453 1-1 1h-8c-0.547 0-1-0.453-1-1v-22c0-0.547 0.453-1 1-1h8c0.547 0 1 0.453 1 1zM10 3v22c0 0.547-0.453 1-1 1h-8c-0.547 0-1-0.453-1-1v-22c0-0.547 0.453-1 1-1h8c0.547 0 1 0.453 1 1z"></path>
|
|
||||||
</symbol>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
{{end}}
|
|
File diff suppressed because one or more lines are too long
@ -1,133 +0,0 @@
|
|||||||
<!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}}</title>
|
|
||||||
<style>
|
|
||||||
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 0 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>{{.messge}}</p>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<a href='javascript:history.back()'>« 返回</a>
|
|
||||||
</p>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,10 +0,0 @@
|
|||||||
{{define "layout/page"}}
|
|
||||||
{{if .pagination}}
|
|
||||||
<nav class="navigation pagination" aria-label="文章">
|
|
||||||
<h2 class="screen-reader-text">文章导航</h2>
|
|
||||||
<div class="nav-links">
|
|
||||||
{{.pagination|unescaped}}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
@ -1,3 +0,0 @@
|
|||||||
{{define "layout/sidebar" }}
|
|
||||||
{{template "common/sidebarWidget" .}}
|
|
||||||
{{end}}
|
|
File diff suppressed because one or more lines are too long
@ -1,143 +0,0 @@
|
|||||||
{{template "layout/base" .}}
|
|
||||||
|
|
||||||
{{define "content"}}
|
|
||||||
{{ 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}}">
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
<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">
|
|
||||||
<article id="post-{{.post.Id}}" class="{{ .post|postsFn .calPostClass}}">
|
|
||||||
|
|
||||||
<header class="entry-header">
|
|
||||||
<div class="entry-meta">
|
|
||||||
<span class="posted-on">
|
|
||||||
<span class="screen-reader-text">发布于</span>
|
|
||||||
<a href="/p/{{.post.Id}}" rel="bookmark">
|
|
||||||
<time class="entry-date published" datetime="{{.post.PostDateGmt}}">{{.post.PostDate|dateCh}}
|
|
||||||
</time>
|
|
||||||
<time class="updated" datetime="{{.post.PostModifiedGmt}}">{{.post.PostModified|dateCh}}
|
|
||||||
</time>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span class="byline">
|
|
||||||
由
|
|
||||||
<span class="author vcard">
|
|
||||||
<a class="url fn n" href="/p/author/{{.user.UserLogin}}">{{.user.UserLogin}}</a>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<h1 class="entry-title">{{.post.PostTitle}}</h1>
|
|
||||||
</header><!-- .entry-header -->
|
|
||||||
|
|
||||||
<div class="entry-content">
|
|
||||||
{{.post.PostContent|unescaped}}
|
|
||||||
</div><!-- .entry-content -->
|
|
||||||
|
|
||||||
<footer class="entry-footer">
|
|
||||||
<span class="cat-tags-links">
|
|
||||||
{{if .post.CategoriesHtml}}
|
|
||||||
<span class="cat-links">
|
|
||||||
<svg class="icon icon-folder-open" aria-hidden="true" role="img"> <use href="#icon-folder-open" xlink:href="#icon-folder-open"></use> </svg>
|
|
||||||
<span class="screen-reader-text">分类 </span>
|
|
||||||
{{.post.CategoriesHtml|unescaped}}
|
|
||||||
</span>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if .post.TagsHtml}}
|
|
||||||
<span class="tags-links">
|
|
||||||
<svg class="icon icon-hashtag" aria-hidden="true" role="img"> <use href="#icon-hashtag" xlink:href="#icon-hashtag"></use> </svg>
|
|
||||||
<span class="screen-reader-text">标签 </span>
|
|
||||||
{{.post.TagsHtml|unescaped}}
|
|
||||||
</span>
|
|
||||||
{{end}}
|
|
||||||
</span>
|
|
||||||
</footer>
|
|
||||||
<!-- .entry-footer -->
|
|
||||||
|
|
||||||
</article><!-- #post-1 -->
|
|
||||||
|
|
||||||
{{ if .showComment}}
|
|
||||||
<div id="comments" class="comments-area">
|
|
||||||
{{ 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" .}}
|
|
||||||
<!-- #respond -->
|
|
||||||
{{else}}
|
|
||||||
<p class="no-comments">评论已关闭。</p>
|
|
||||||
{{end}}
|
|
||||||
</div><!-- .comments-area -->
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
||||||
<nav class="navigation post-navigation" aria-label="文章">
|
|
||||||
<h2 class="screen-reader-text">文章导航</h2>
|
|
||||||
<div class="nav-links">
|
|
||||||
{{if gt .prev.Id 0}}
|
|
||||||
<div class="nav-previous">
|
|
||||||
<a href="/p/{{.prev.Id}}" rel="prev">
|
|
||||||
<span class="screen-reader-text">上一篇文章</span>
|
|
||||||
<span aria-hidden="true" class="nav-subtitle">上一篇</span>
|
|
||||||
<span class="nav-title">
|
|
||||||
<span class="nav-title-icon-wrapper">
|
|
||||||
<svg class="icon icon-arrow-left" aria-hidden="true" role="img"> <use href="#icon-arrow-left" xlink:href="#icon-arrow-left"></use>
|
|
||||||
</svg>
|
|
||||||
</span> {{.prev.PostTitle}}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{if gt .next.Id 0}}
|
|
||||||
<div class="nav-next">
|
|
||||||
<a href="/p/{{.next.Id}}" rel="next">
|
|
||||||
<span class="screen-reader-text" aria-hidden="true">下一篇文章</span>
|
|
||||||
<span aria-hidden="true" class="nav-subtitle">下一篇</span>
|
|
||||||
<span class="nav-title">{{.next.PostTitle}}
|
|
||||||
<span class="nav-title-icon-wrapper">
|
|
||||||
<svg class="icon icon-arrow-right" aria-hidden="true" role="img"> <use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use> </svg>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</main><!-- .site-main -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<aside id="secondary" class="widget-area" aria-label="博客边栏">
|
|
||||||
{{template "layout/sidebar" .}}
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "common/colophon" .}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{else}}
|
|
||||||
{{template "layout/empty"}}
|
|
||||||
{{end }}
|
|
||||||
{{end}}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
|||||||
{{template "layout/base" .}}
|
|
||||||
|
|
||||||
{{define "content"}}
|
|
||||||
<div class="site-content-contain">
|
|
||||||
<div id="content" class="site-content">
|
|
||||||
<div class="wrap">
|
|
||||||
<div id="primary" class="content-area">
|
|
||||||
<main id="main" class="site-main">
|
|
||||||
{{template "layout/empty"}}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
|||||||
{{template "layout/base" .}}
|
|
||||||
|
|
||||||
{{define "content" }}
|
|
||||||
<div class="site-content-contain">
|
|
||||||
<div id="content" class="site-content">
|
|
||||||
{{if .posts}}
|
|
||||||
|
|
||||||
<div class="wrap">
|
|
||||||
<header class="page-header">
|
|
||||||
{{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">
|
|
||||||
{{ range $k,$v:=.posts}}
|
|
||||||
<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>
|
|
||||||
<a href="/p/{{$v.Id}}" rel="bookmark">
|
|
||||||
<time class="entry-date published" datetime="{{$v.PostDateGmt}}">{{$v.PostDate|dateCh}}
|
|
||||||
</time><time class="updated" datetime="{{$v.PostModifiedGmt}}">{{$v.PostModified|dateCh}}
|
|
||||||
</time>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<h3 class="entry-title">
|
|
||||||
<a href="/p/{{$v.Id}}" rel="bookmark">{{$v.PostTitle}}</a>
|
|
||||||
</h3>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
|
|
||||||
{{if $v.Thumbnail.Path}}
|
|
||||||
<div class="post-thumbnail">
|
|
||||||
<a href="/p/{{$v.Id}}" >
|
|
||||||
<img width="{{$v.Thumbnail.Width}}" height="{{$v.Thumbnail.Height}}" src="{{$v.Thumbnail.Path}}" class="attachment-twentyseventeen-featured-image size-twentyseventeen-featured-image wp-post-image" alt="{{$v.PostTitle}}" decoding="async" loading="lazy" srcset="{{$v.Thumbnail.Srcset}}" sizes="{{$v.Thumbnail.Sizes}}">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
||||||
<!-- .entry-header -->
|
|
||||||
|
|
||||||
<div class="entry-content">
|
|
||||||
{{$v.PostContent|unescaped}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- .entry-content -->
|
|
||||||
|
|
||||||
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<!-- #post-{{$v.Id}} -->
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{template "layout/page" .}}
|
|
||||||
</main>
|
|
||||||
<!-- .site-main -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<aside id="secondary" class="widget-area" aria-label="博客边栏">
|
|
||||||
{{template "layout/sidebar" .}}
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{else }}
|
|
||||||
{{template "layout/empty" .}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{{template "common/colophon" .}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
@ -1,43 +0,0 @@
|
|||||||
package twentyseventeen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
)
|
|
||||||
|
|
||||||
func pushScripts(h *wp.Handle) {
|
|
||||||
h.PushCacheGroupHeadScript(constraints.AllScene, "{theme}.head", 30, func(h *wp.Handle) string {
|
|
||||||
head := headScript
|
|
||||||
if "dark" == wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light") {
|
|
||||||
head = fmt.Sprintf("%s\n%s", headScript, ` <link rel="stylesheet" id="twentyseventeen-colors-dark-css" href="/wp-content/themes/twentyseventeen/assets/css/colors-dark.css?ver=20191025" media="all">`)
|
|
||||||
}
|
|
||||||
return head
|
|
||||||
})
|
|
||||||
h.PushGroupFooterScript(constraints.AllScene, "{theme}.footer", 20.005, footerScript)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var headScript = `<link rel='stylesheet' id='twentyseventeen-style-css' href='/wp-content/themes/twentyseventeen/style.css?ver=20221101' media='all' />
|
|
||||||
<link rel='stylesheet' id='twentyseventeen-block-style-css' href='/wp-content/themes/twentyseventeen/assets/css/blocks.css?ver=20220912' media='all' />
|
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<link rel='stylesheet' id='twentyseventeen-ie8-css' href='/wp-content/themes/twentyseventeen/assets/css/ie8.css?ver=20161202' media='all' />
|
|
||||||
<![endif]-->
|
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<script src='/wp-content/themes/twentyseventeen/assets/js/html5.js?ver=20161020' id='html5-js'></script>
|
|
||||||
<![endif]-->
|
|
||||||
<script src='/wp-includes/js/jquery/jquery.min.js?ver=3.6.0' id='jquery-core-js'></script>
|
|
||||||
<script src='/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.3.2' id='jquery-migrate-js'></script>
|
|
||||||
<link rel="https://api.w.org/" href="/wp-json/" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="/xmlrpc.php?rsd" />
|
|
||||||
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="/wp-includes/wlwmanifest.xml" />
|
|
||||||
<meta name="generator" content="WordPress 6.1.1" />
|
|
||||||
<style>.recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}</style>`
|
|
||||||
|
|
||||||
var footerScript = `<script id="twentyseventeen-skip-link-focus-fix-js-extra">
|
|
||||||
var twentyseventeenScreenReaderText = {"quote":"<svg class=\"icon icon-quote-right\" aria-hidden=\"true\" role=\"img\"> <use href=\"#icon-quote-right\" xlink:href=\"#icon-quote-right\"><\/use> <\/svg>"};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script src="/wp-content/themes/twentyseventeen/assets/js/skip-link-focus-fix.js?ver=20161114" id="twentyseventeen-skip-link-focus-fix-js"></script>
|
|
||||||
<script src="/wp-content/themes/twentyseventeen/assets/js/global.js?ver=20211130" id="twentyseventeen-global-js"></script>
|
|
||||||
<script src="/wp-content/themes/twentyseventeen/assets/js/jquery.scrollTo.js?ver=2.1.3" id="jquery-scrollto-js"></script>`
|
|
@ -1,212 +0,0 @@
|
|||||||
package twentyseventeen
|
|
||||||
|
|
||||||
import "github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
|
|
||||||
type themeSupport struct {
|
|
||||||
CustomLineHeight bool `json:"custom-line-height"`
|
|
||||||
StarterContent StarterContent `json:"starter-content"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Widgets struct {
|
|
||||||
Sidebar1 []string `json:"sidebar-1"`
|
|
||||||
Sidebar2 []string `json:"sidebar-2"`
|
|
||||||
Sidebar3 []string `json:"sidebar-3"`
|
|
||||||
}
|
|
||||||
type About struct {
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
}
|
|
||||||
type Contact struct {
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
}
|
|
||||||
type Blog struct {
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
}
|
|
||||||
type HomepageSection struct {
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ImageEspresso struct {
|
|
||||||
PostTitle string `json:"post_title"`
|
|
||||||
File string `json:"file"`
|
|
||||||
}
|
|
||||||
type ImageSandwich struct {
|
|
||||||
PostTitle string `json:"post_title"`
|
|
||||||
File string `json:"file"`
|
|
||||||
}
|
|
||||||
type ImageCoffee struct {
|
|
||||||
PostTitle string `json:"post_title"`
|
|
||||||
File string `json:"file"`
|
|
||||||
}
|
|
||||||
type Attachments struct {
|
|
||||||
ImageEspresso ImageEspresso `json:"image-espresso"`
|
|
||||||
ImageSandwich ImageSandwich `json:"image-sandwich"`
|
|
||||||
ImageCoffee ImageCoffee `json:"image-coffee"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Image struct {
|
|
||||||
PostTitle string `json:"post_title"`
|
|
||||||
File string `json:"file"`
|
|
||||||
}
|
|
||||||
type Options struct {
|
|
||||||
ShowOnFront string `json:"show_on_front"`
|
|
||||||
PageOnFront string `json:"page_on_front"`
|
|
||||||
PageForPosts string `json:"page_for_posts"`
|
|
||||||
}
|
|
||||||
type ThemeMods struct {
|
|
||||||
Panel1 string `json:"panel_1"`
|
|
||||||
Panel2 string `json:"panel_2"`
|
|
||||||
Panel3 string `json:"panel_3"`
|
|
||||||
Panel4 string `json:"panel_4"`
|
|
||||||
}
|
|
||||||
type Menus struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Items []string `json:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type NavMenus struct {
|
|
||||||
Top Menus `json:"top"`
|
|
||||||
Social Menus `json:"social"`
|
|
||||||
}
|
|
||||||
type StarterContent struct {
|
|
||||||
Widgets Widgets `json:"widgets"`
|
|
||||||
Posts map[string]map[string]string `json:"posts"`
|
|
||||||
Attachments map[string]Image `json:"attachments"`
|
|
||||||
Options Options `json:"options"`
|
|
||||||
ThemeMods ThemeMods `json:"theme_mods"`
|
|
||||||
NavMenus NavMenus `json:"nav_menus"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var themesupport = themeSupport{
|
|
||||||
CustomLineHeight: true,
|
|
||||||
StarterContent: StarterContent{
|
|
||||||
Widgets: Widgets{
|
|
||||||
Sidebar1: []string{"text_business_info", "search", "text_about"},
|
|
||||||
Sidebar2: []string{"text_business_info"},
|
|
||||||
Sidebar3: []string{"text_about", "search"},
|
|
||||||
},
|
|
||||||
Posts: map[string]map[string]string{
|
|
||||||
"0": {
|
|
||||||
"home": "home",
|
|
||||||
},
|
|
||||||
"about": {
|
|
||||||
"thumbnail": "{{image-sandwich}}",
|
|
||||||
},
|
|
||||||
"contact": {
|
|
||||||
"thumbnail": "{{image-espresso}}",
|
|
||||||
},
|
|
||||||
"blog": {
|
|
||||||
"thumbnail": "{{image-coffee}}",
|
|
||||||
},
|
|
||||||
"homepage-section": {
|
|
||||||
"thumbnail": "{{image-espresso}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Attachments: map[string]Image{
|
|
||||||
"image-espresso": {
|
|
||||||
PostTitle: "浓缩咖啡",
|
|
||||||
File: "assets/images/espresso.jpg",
|
|
||||||
},
|
|
||||||
"image-sandwich": {
|
|
||||||
PostTitle: "三明治",
|
|
||||||
File: "assets/images/sandwich.jpg",
|
|
||||||
},
|
|
||||||
"image-coffee": {
|
|
||||||
PostTitle: "咖啡",
|
|
||||||
File: "assets/images/coffee.jpg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Options: Options{
|
|
||||||
ShowOnFront: "page",
|
|
||||||
PageOnFront: "{{home}}",
|
|
||||||
PageForPosts: "{{blog}}",
|
|
||||||
},
|
|
||||||
ThemeMods: ThemeMods{
|
|
||||||
Panel1: "{{homepage-section}}",
|
|
||||||
Panel2: "{{about}}",
|
|
||||||
Panel3: "{{blog}}",
|
|
||||||
Panel4: "{{contact}}",
|
|
||||||
},
|
|
||||||
NavMenus: NavMenus{
|
|
||||||
Top: Menus{
|
|
||||||
Name: "顶部菜单",
|
|
||||||
Items: []string{
|
|
||||||
"link_home",
|
|
||||||
"page_about",
|
|
||||||
"page_blog",
|
|
||||||
"page_contact",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Social: Menus{
|
|
||||||
Name: "社交网络链接菜单",
|
|
||||||
Items: []string{
|
|
||||||
"link_yelp",
|
|
||||||
"link_facebook",
|
|
||||||
"link_twitter",
|
|
||||||
"link_instagram",
|
|
||||||
"link_email",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = func() struct{} {
|
|
||||||
v := wpconfig.ThemeSupport{
|
|
||||||
CoreBlockPatterns: true,
|
|
||||||
WidgetsBlockEditor: true,
|
|
||||||
AutomaticFeedLinks: true,
|
|
||||||
TitleTag: true,
|
|
||||||
PostThumbnails: true,
|
|
||||||
Menus: true,
|
|
||||||
HTML5: []string{
|
|
||||||
"comment-form",
|
|
||||||
"comment-list",
|
|
||||||
"gallery",
|
|
||||||
"caption",
|
|
||||||
"script",
|
|
||||||
"style",
|
|
||||||
"navigation-widgets",
|
|
||||||
},
|
|
||||||
PostFormats: []string{
|
|
||||||
"aside",
|
|
||||||
"image",
|
|
||||||
"video",
|
|
||||||
"quote",
|
|
||||||
"link",
|
|
||||||
"gallery",
|
|
||||||
"audio",
|
|
||||||
},
|
|
||||||
CustomLogo: wpconfig.CustomLogo{
|
|
||||||
Width: 250,
|
|
||||||
Height: 250,
|
|
||||||
FlexWidth: true,
|
|
||||||
FlexHeight: false,
|
|
||||||
HeaderText: "",
|
|
||||||
UnlinkHomepageLogo: false,
|
|
||||||
},
|
|
||||||
CustomizeSelectiveRefreshWidgets: true,
|
|
||||||
EditorStyle: true,
|
|
||||||
EditorStyles: true,
|
|
||||||
WpBlockStyles: true,
|
|
||||||
ResponsiveEmbeds: true,
|
|
||||||
CustomHeader: wpconfig.CustomHeader{
|
|
||||||
DefaultImage: "http://wp.test/wp-content/themes/twentyseventeen/assets/images/header.jpg",
|
|
||||||
RandomDefault: false,
|
|
||||||
Width: 2000,
|
|
||||||
Height: 1200,
|
|
||||||
FlexHeight: true,
|
|
||||||
FlexWidth: false,
|
|
||||||
DefaultTextColor: "",
|
|
||||||
HeaderText: true,
|
|
||||||
Uploads: true,
|
|
||||||
WpHeadCallback: "twentyseventeen_header_style",
|
|
||||||
AdminHeadCallback: "",
|
|
||||||
AdminPreviewCallback: "",
|
|
||||||
Video: true,
|
|
||||||
VideoActiveCallback: "is_front_page",
|
|
||||||
},
|
|
||||||
Widgets: true,
|
|
||||||
}
|
|
||||||
wpconfig.SetThemeSupport(ThemeName, v)
|
|
||||||
return struct{}{}
|
|
||||||
}()
|
|
@ -1,229 +0,0 @@
|
|||||||
package twentyseventeen
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/app/plugins"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp/components"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp/middleware"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
"github.com/fthvgb1/wp-go/helper"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/fthvgb1/wp-go/plugin/pagination"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ThemeName = "twentyseventeen"
|
|
||||||
|
|
||||||
var paginate = func() plugins.PageEle {
|
|
||||||
p := plugins.TwentyFifteenPagination()
|
|
||||||
p.PrevEle = `<a class="prev page-numbers" href="%s"><svg class="icon icon-arrow-left" aria-hidden="true" role="img"> <use href="#icon-arrow-left" xlink:href="#icon-arrow-left"></use> </svg>
|
|
||||||
<span class="screen-reader-text">上一页</span></a>`
|
|
||||||
p.NextEle = strings.Replace(p.NextEle, "下一页", `<span class="screen-reader-text">下一页</span>
|
|
||||||
<svg class="icon icon-arrow-right" aria-hidden="true" role="img"> <use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use>
|
|
||||||
</svg>`, 1)
|
|
||||||
commentPageEle = plugins.PaginationNav{
|
|
||||||
Currents: p.Current,
|
|
||||||
Prevs: p.Prev,
|
|
||||||
Nexts: p.Next,
|
|
||||||
Dotss: p.Dots,
|
|
||||||
Middles: p.Middle,
|
|
||||||
Urlss: plugins.TwentyFifteenCommentPagination().Urls,
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}()
|
|
||||||
|
|
||||||
var commentPageEle pagination.Render
|
|
||||||
|
|
||||||
func Hook(h *wp.Handle) {
|
|
||||||
wp.Run(h, configs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func configs(h *wp.Handle) {
|
|
||||||
wp.InitPipe(h)
|
|
||||||
middleware.CommonMiddleware(h)
|
|
||||||
h.AddActionFilter("bodyClass", calClass)
|
|
||||||
h.PushCacheGroupHeadScript(constraints.AllScene, "colorScheme-customHeader", 10, colorScheme, customHeader)
|
|
||||||
components.WidgetArea(h)
|
|
||||||
pushScripts(h)
|
|
||||||
h.PushRender(constraints.AllStats, wp.NewHandleFn(calCustomHeader, 10.005, "calCustomHeader"))
|
|
||||||
wp.SetComponentsArgs(widgets.Widget, map[string]string{
|
|
||||||
"{$before_widget}": `<section id="%s" class="%s">`,
|
|
||||||
"{$after_widget}": `</section>`,
|
|
||||||
})
|
|
||||||
h.PushRender(constraints.AllStats,
|
|
||||||
wp.NewHandleFn(wp.PreTemplate, 70.005, "wp.PreTemplate"),
|
|
||||||
wp.NewHandleFn(errorsHandle, 80.005, "errorsHandle"),
|
|
||||||
)
|
|
||||||
videoHeader(h)
|
|
||||||
h.SetData("colophon", colophon)
|
|
||||||
setPaginationAndRender(h)
|
|
||||||
h.CommonComponents()
|
|
||||||
h.PushPostPlugin(postThumbnail)
|
|
||||||
wp.SetComponentsArgsForMap(widgets.Search, "{$form}", searchForm)
|
|
||||||
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 10.005, "wp.IndexRender"))
|
|
||||||
h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 10.005, "wp.DetailRender"))
|
|
||||||
h.PushDataHandler(constraints.Detail, wp.NewHandleFn(wp.Detail, 100.005, "wp.Detail"), wp.NewHandleFn(postThumb, 90.005, "{theme}.postThumb"))
|
|
||||||
wp.PushIndexHandler(constraints.PipeData, h, wp.NewHandleFn(wp.Index, 100.005, "wp.Index"))
|
|
||||||
h.PushDataHandler(constraints.AllScene, wp.NewHandleFn(wp.PreCodeAndStats, 90.005, "wp.PreCodeAndStats"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func setPaginationAndRender(h *wp.Handle) {
|
|
||||||
h.PushHandler(constraints.PipeRender, constraints.Detail, wp.NewHandleFn(func(hh *wp.Handle) {
|
|
||||||
d := hh.GetDetailHandle()
|
|
||||||
d.CommentRender = commentFormat
|
|
||||||
d.CommentPageEle = commentPageEle
|
|
||||||
d.CommentStep = 2
|
|
||||||
}, 150, "setPaginationAndRender"))
|
|
||||||
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(func(hh *wp.Handle) {
|
|
||||||
i := hh.GetIndexHandle()
|
|
||||||
i.SetPageEle(paginate)
|
|
||||||
}, 150, "setPaginationAndRender"))
|
|
||||||
}
|
|
||||||
|
|
||||||
var searchForm = `<form role="search" method="get" class="search-form" action="/">
|
|
||||||
<label for="search-form-1">
|
|
||||||
<span class="screen-reader-text">{$label}:</span>
|
|
||||||
</label>
|
|
||||||
<input type="search" id="search-form-1" class="search-field" placeholder="{$placeholder}…" value="{$value}" name="s">
|
|
||||||
<button type="submit" class="search-submit">
|
|
||||||
<svg class="icon icon-search" aria-hidden="true" role="img"> <use href="#icon-search" xlink:href="#icon-search"></use> </svg>
|
|
||||||
<span class="screen-reader-text">{$button}</span>
|
|
||||||
</button>
|
|
||||||
</form>`
|
|
||||||
|
|
||||||
func errorsHandle(h *wp.Handle) {
|
|
||||||
switch h.Stats {
|
|
||||||
case constraints.Error404, constraints.InternalErr, constraints.ParamError:
|
|
||||||
logs.IfError(h.Err(), "报错:")
|
|
||||||
h.SetTempl("twentyseventeen/posts/error.gohtml")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func postThumb(h *wp.Handle) {
|
|
||||||
d := h.GetDetailHandle()
|
|
||||||
if d.Post.Thumbnail.Path != "" {
|
|
||||||
img := wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "full", "", "thumbnail", "post-thumbnail")
|
|
||||||
img.Sizes = "100vw"
|
|
||||||
img.Srcset = fmt.Sprintf("%s %dw, %s", img.Path, img.Width, img.Srcset)
|
|
||||||
d.Post.Thumbnail = img
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var commentFormat = comment{}
|
|
||||||
|
|
||||||
type comment struct {
|
|
||||||
plugins.CommonCommentFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
var commentLi = plugins.CommonLi()
|
|
||||||
|
|
||||||
var respondFn = plugins.Responds(respondStr)
|
|
||||||
|
|
||||||
func (c comment) FormatLi(_ context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
|
|
||||||
return plugins.FormatLi(commentLi, m, respondFn, depth, maxDepth, page, isTls, isThreadComments, eo, parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
var colophon = `<footer id="colophon" class="site-footer">
|
|
||||||
<div class="wrap">
|
|
||||||
<div class="site-info">
|
|
||||||
<a href="https://github.com/fthvgb1/wp-go" class="imprint">自豪地采用 wp-go</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>`
|
|
||||||
|
|
||||||
var respondStr = `<a rel="nofollow" class="comment-reply-link"
|
|
||||||
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
|
|
||||||
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond"
|
|
||||||
data-replyto="回复给{{CommentAuthor}}"
|
|
||||||
aria-label="回复给{{CommentAuthor}}"><svg class="icon icon-mail-reply" aria-hidden="true" role="img"> <use href="#icon-mail-reply" xlink:href="#icon-mail-reply"></use> </svg>回复</a>`
|
|
||||||
|
|
||||||
func postThumbnail(h *wp.Handle, posts *models.Posts) {
|
|
||||||
if posts.Thumbnail.Path != "" {
|
|
||||||
posts.Thumbnail.Sizes = "(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px"
|
|
||||||
if h.Scene() == constraints.Detail {
|
|
||||||
posts.Thumbnail.Sizes = "100vw"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var header = reload.Vars(models.PostThumbnail{}, "twentyseventeen-headerImage")
|
|
||||||
|
|
||||||
func calCustomHeader(h *wp.Handle) {
|
|
||||||
h.SetData("HeaderImage", getHeaderImage(h))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHeaderImage(h *wp.Handle) (r models.PostThumbnail) {
|
|
||||||
img := header.Load()
|
|
||||||
if img.Path != "" {
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
image, rand := h.GetCustomHeaderImg()
|
|
||||||
if image.Path != "" {
|
|
||||||
r = image
|
|
||||||
r.Sizes = "100vw"
|
|
||||||
if !rand {
|
|
||||||
header.Store(r)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.Path = helper.CutUrlHost(h.CommonThemeMods().ThemeSupport.CustomHeader.DefaultImage)
|
|
||||||
r.Width = 2000
|
|
||||||
r.Height = 1200
|
|
||||||
header.Store(r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func calClass(h *wp.Handle, s string, _ ...any) string {
|
|
||||||
class := strings.Split(s, " ")
|
|
||||||
themeMods := h.CommonThemeMods()
|
|
||||||
u := wpconfig.GetThemeModsVal(ThemeName, "header_image", themeMods.ThemeSupport.CustomHeader.DefaultImage)
|
|
||||||
if u != "" && u != "remove-header" {
|
|
||||||
class = append(class, "has-header-image")
|
|
||||||
}
|
|
||||||
if len(themeMods.SidebarsWidgets.Data.Sidebar1) > 0 {
|
|
||||||
class = append(class, "has-sidebar")
|
|
||||||
}
|
|
||||||
if themeMods.HeaderTextcolor == "blank" {
|
|
||||||
class = append(class, "title-tagline-hidden")
|
|
||||||
}
|
|
||||||
class = append(class, "hfeed")
|
|
||||||
class = append(class, str.Join("colors-", wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light")))
|
|
||||||
if h.Scene() == constraints.Archive {
|
|
||||||
if "one-column" == wpconfig.GetThemeModsVal(ThemeName, "page_layout", "") {
|
|
||||||
class = append(class, "page-one-column")
|
|
||||||
} else {
|
|
||||||
class = append(class, "page-two-column")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(class, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func videoHeader(h *wp.Handle) {
|
|
||||||
h.AddActionFilter("videoSetting", videoPlay)
|
|
||||||
wp.CustomVideo(h, constraints.Home)
|
|
||||||
}
|
|
||||||
|
|
||||||
func videoPlay(h *wp.Handle, _ string, a ...any) string {
|
|
||||||
if len(a) < 1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
v, ok := a[0].(*wp.VideoSetting)
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
img := getHeaderImage(h)
|
|
||||||
v.Width = img.Width
|
|
||||||
v.Height = img.Height
|
|
||||||
v.PosterUrl = img.Path
|
|
||||||
v.L10n.Play = `<span class="screen-reader-text">播放背景视频</span><svg class="icon icon-play" aria-hidden="true" role="img"> <use href="#icon-play" xlink:href="#icon-play"></use> </svg>`
|
|
||||||
v.L10n.Pause = `<span class="screen-reader-text">暂停背景视频</span><svg class="icon icon-pause" aria-hidden="true" role="img"> <use href="#icon-pause" xlink:href="#icon-pause"></use> </svg>`
|
|
||||||
return ""
|
|
||||||
}
|
|
@ -1,131 +0,0 @@
|
|||||||
package wp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/number"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func bodyClass(h *Handle) func() string {
|
|
||||||
return func() string {
|
|
||||||
return h.BodyClass()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) BodyClass() string {
|
|
||||||
var class []string
|
|
||||||
if constraints.Ok != h.Stats {
|
|
||||||
class = append(class, "error404")
|
|
||||||
}
|
|
||||||
switch h.scene {
|
|
||||||
case constraints.Home:
|
|
||||||
class = append(class, "home", "blog")
|
|
||||||
|
|
||||||
case constraints.Archive:
|
|
||||||
class = append(class, "archive", "date")
|
|
||||||
|
|
||||||
case constraints.Search:
|
|
||||||
s := "search-no-results"
|
|
||||||
if len(h.GetIndexHandle().Posts) > 0 {
|
|
||||||
s = "search-results"
|
|
||||||
}
|
|
||||||
class = append(class, "search", s)
|
|
||||||
|
|
||||||
case constraints.Category, constraints.Tag:
|
|
||||||
class = append(class, "archive", "category")
|
|
||||||
cat := h.GetIndexHandle().Param.Category
|
|
||||||
if cat == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
_, cate := slice.SearchFirst(cache.CategoriesTags(h.C, h.scene), func(my models.TermsMy) bool {
|
|
||||||
return my.Name == cat
|
|
||||||
})
|
|
||||||
if cate.Slug[0] != '%' {
|
|
||||||
class = append(class, str.Join("category-", cate.Slug))
|
|
||||||
}
|
|
||||||
class = append(class, str.Join("category-", number.IntToString(cate.Terms.TermId)))
|
|
||||||
|
|
||||||
case constraints.Author:
|
|
||||||
class = append(class, "archive", "author")
|
|
||||||
author := h.GetIndexHandle().Param.Author
|
|
||||||
user, _ := cache.GetUserByName(h.C, author)
|
|
||||||
class = append(class, str.Join("author-", number.IntToString(user.Id)))
|
|
||||||
if user.DisplayName[0] != '%' {
|
|
||||||
class = append(class, str.Join("author-", user.DisplayName))
|
|
||||||
}
|
|
||||||
|
|
||||||
case constraints.Detail:
|
|
||||||
class = append(class, "post-template-default", "single", "single-post")
|
|
||||||
class = append(class, str.Join("postid-", number.IntToString(h.GetDetailHandle().Post.Id)))
|
|
||||||
if len(h.themeMods.ThemeSupport.PostFormats) > 0 {
|
|
||||||
class = append(class, "single-format-standard")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if wpconfig.IsCustomBackground(h.theme) {
|
|
||||||
class = append(class, "custom-background")
|
|
||||||
}
|
|
||||||
if h.themeMods.CustomLogo > 0 || str.ToInteger(wpconfig.GetOption("site_logo"), 0) > 0 {
|
|
||||||
class = append(class, "wp-custom-logo")
|
|
||||||
}
|
|
||||||
if h.themeMods.ThemeSupport.ResponsiveEmbeds {
|
|
||||||
class = append(class, "wp-embed-responsive")
|
|
||||||
}
|
|
||||||
return h.DoActionFilter("bodyClass", strings.Join(class, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func postClass(h *Handle) func(posts models.Posts) string {
|
|
||||||
return func(posts models.Posts) string {
|
|
||||||
return h.PostClass(posts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (h *Handle) PostClass(posts models.Posts) string {
|
|
||||||
var class []string
|
|
||||||
class = append(class, fmt.Sprintf("post-%d", posts.Id), posts.PostType,
|
|
||||||
str.Join("type-", posts.PostType), str.Join("status-", posts.PostStatus),
|
|
||||||
"hentry", "format-standard")
|
|
||||||
if h.CommonThemeMods().ThemeSupport.PostThumbnails && posts.Thumbnail.Path != "" {
|
|
||||||
class = append(class, "has-post-thumbnail")
|
|
||||||
}
|
|
||||||
|
|
||||||
if posts.PostPassword != "" {
|
|
||||||
if h.GetPassword() != posts.PostPassword {
|
|
||||||
class = append(class, "post-password-required")
|
|
||||||
} else {
|
|
||||||
class = append(class, "post-password-projected")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.scene == constraints.Home && h.IsStick(posts.Id) {
|
|
||||||
class = append(class, "sticky")
|
|
||||||
}
|
|
||||||
for _, id := range posts.TermIds {
|
|
||||||
term, ok := wpconfig.GetTermMy(id)
|
|
||||||
if !ok || term.Slug == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
class = append(class, TermClass(term))
|
|
||||||
}
|
|
||||||
|
|
||||||
return h.DoActionFilter("postClass", strings.Join(class, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TermClass(term models.TermsMy) string {
|
|
||||||
termClass := term.Slug
|
|
||||||
if strings.Contains(term.Slug, "%") {
|
|
||||||
termClass = strconv.FormatUint(term.TermTaxonomy.TermId, 10)
|
|
||||||
}
|
|
||||||
switch term.Taxonomy {
|
|
||||||
case "post_tag":
|
|
||||||
return str.Join("tag-", termClass)
|
|
||||||
case "post_format":
|
|
||||||
return fmt.Sprintf("format-%s", strings.ReplaceAll(term.Slug, "post-format-", ""))
|
|
||||||
}
|
|
||||||
return str.Join(term.Taxonomy, "-", termClass)
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
package wp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
|
||||||
"github.com/fthvgb1/wp-go/app/plugins"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
"github.com/fthvgb1/wp-go/cache"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RenderComment(ctx context.Context, page int, render plugins.CommentHtml, ids []uint64, timeout time.Duration, isTLS bool) (string, error) {
|
|
||||||
ca, _ := cachemanager.GetMapCache[uint64, models.Comments]("postCommentData")
|
|
||||||
children, _ := cachemanager.GetMapCache[uint64, []uint64]("commentChildren")
|
|
||||||
h := CommentHandle{
|
|
||||||
maxDepth: str.ToInteger(wpconfig.GetOption("thread_comments_depth"), 5),
|
|
||||||
depth: 1,
|
|
||||||
isTls: isTLS,
|
|
||||||
html: render,
|
|
||||||
order: wpconfig.GetOption("comment_order"),
|
|
||||||
ca: ca,
|
|
||||||
children: children,
|
|
||||||
threadComments: wpconfig.GetOption("thread_comments") == "1",
|
|
||||||
page: page,
|
|
||||||
}
|
|
||||||
return h.formatComments(ctx, ids, timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommentHandle struct {
|
|
||||||
maxDepth int
|
|
||||||
depth int
|
|
||||||
isTls bool
|
|
||||||
html plugins.CommentHtml
|
|
||||||
order string
|
|
||||||
page int
|
|
||||||
ca *cache.MapCache[uint64, models.Comments]
|
|
||||||
children *cache.MapCache[uint64, []uint64]
|
|
||||||
threadComments bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommentHandle) findGrandchildComments(ctx context.Context, timeout time.Duration, comments []models.Comments) ([]models.Comments, error) {
|
|
||||||
parentIds := slice.Map(comments, func(t models.Comments) uint64 {
|
|
||||||
return t.CommentId
|
|
||||||
})
|
|
||||||
children, err := c.children.GetCacheBatch(ctx, parentIds, timeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rr := slice.FilterAndMap(children, func(t []uint64) ([]uint64, bool) {
|
|
||||||
return t, len(t) > 0
|
|
||||||
})
|
|
||||||
if len(rr) < 1 {
|
|
||||||
return comments, nil
|
|
||||||
}
|
|
||||||
ids := slice.Decompress(rr)
|
|
||||||
r, err := c.ca.GetCacheBatch(ctx, ids, timeout)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rrr, err := c.findGrandchildComments(ctx, timeout, r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
comments = append(comments, rrr...)
|
|
||||||
slice.Sort(comments, func(i, j models.Comments) bool {
|
|
||||||
return c.html.FloorOrder(i, j)
|
|
||||||
})
|
|
||||||
return comments, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c CommentHandle) formatComments(ctx context.Context, ids []uint64, timeout time.Duration) (html string, err error) {
|
|
||||||
comments, err := c.ca.GetCacheBatch(ctx, ids, timeout)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if c.depth > 1 && c.depth < c.maxDepth {
|
|
||||||
slice.Sort(comments, func(i, j models.Comments) bool {
|
|
||||||
return c.html.FloorOrder(i, j)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fixChildren := false
|
|
||||||
if c.depth >= c.maxDepth {
|
|
||||||
comments, err = c.findGrandchildComments(ctx, timeout, comments)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
fixChildren = true
|
|
||||||
}
|
|
||||||
s := str.NewBuilder()
|
|
||||||
for i, comment := range comments {
|
|
||||||
eo := "even"
|
|
||||||
if (i+1)%2 == 0 {
|
|
||||||
eo = "odd"
|
|
||||||
}
|
|
||||||
parent := ""
|
|
||||||
fl := false
|
|
||||||
var children []uint64
|
|
||||||
if !fixChildren {
|
|
||||||
children, err = c.children.GetCache(ctx, comment.CommentId, timeout)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.threadComments && len(children) > 0 && c.depth < c.maxDepth+1 {
|
|
||||||
parent = "parent"
|
|
||||||
fl = true
|
|
||||||
}
|
|
||||||
s.WriteString(c.html.FormatLi(ctx, comment, c.depth, c.maxDepth, c.page, c.isTls, c.threadComments, eo, parent))
|
|
||||||
if fl {
|
|
||||||
c.depth++
|
|
||||||
ss, err := c.formatComments(ctx, children, timeout)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
s.WriteString(`<ol class="children">`, ss, `</ol>`)
|
|
||||||
c.depth--
|
|
||||||
}
|
|
||||||
s.WriteString("</li><!-- #comment-## -->")
|
|
||||||
}
|
|
||||||
|
|
||||||
html = s.String()
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,256 +0,0 @@
|
|||||||
package wp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
|
||||||
"github.com/fthvgb1/wp-go/cache/reload"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/maps"
|
|
||||||
"github.com/fthvgb1/wp-go/helper/slice"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"github.com/fthvgb1/wp-go/safety"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var handleComponents = safety.NewMap[string, map[string][]Components[string]]()
|
|
||||||
var handleComponentHook = safety.NewMap[string, map[string][]func(Components[string]) (Components[string], bool)]()
|
|
||||||
|
|
||||||
var componentsArgs = safety.NewMap[string, any]()
|
|
||||||
var componentFilterFns = safety.NewMap[string, []func(*Handle, string, ...any) string]()
|
|
||||||
|
|
||||||
func (h *Handle) DeleteComponents(scene, componentKey, componentName string) {
|
|
||||||
v, ok := handleComponentHook.Load(scene)
|
|
||||||
if !ok {
|
|
||||||
v = make(map[string][]func(Components[string]) (Components[string], bool))
|
|
||||||
}
|
|
||||||
|
|
||||||
v[componentKey] = append(v[componentKey], func(c Components[string]) (Components[string], bool) {
|
|
||||||
return c, c.Name != componentName
|
|
||||||
})
|
|
||||||
handleComponentHook.Store(scene, v)
|
|
||||||
}
|
|
||||||
func (h *Handle) ReplaceComponents(scene, componentKey, componentName string, components Components[string]) {
|
|
||||||
v, ok := handleComponentHook.Load(scene)
|
|
||||||
if !ok {
|
|
||||||
v = make(map[string][]func(Components[string]) (Components[string], bool))
|
|
||||||
}
|
|
||||||
v[componentKey] = append(v[componentKey], func(c Components[string]) (Components[string], bool) {
|
|
||||||
if c.Name == componentName {
|
|
||||||
c = components
|
|
||||||
}
|
|
||||||
return c, true
|
|
||||||
})
|
|
||||||
handleComponentHook.Store(scene, v)
|
|
||||||
}
|
|
||||||
func (h *Handle) PushComponentHooks(scene, componentKey string, fn func(Components[string]) (Components[string], bool)) {
|
|
||||||
v, ok := handleComponentHook.Load(scene)
|
|
||||||
if !ok {
|
|
||||||
v = make(map[string][]func(Components[string]) (Components[string], bool))
|
|
||||||
}
|
|
||||||
v[componentKey] = append(v[componentKey], fn)
|
|
||||||
handleComponentHook.Store(scene, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
var GetAndHookComponents = reload.BuildMapFnWithAnyParams[string]("calComponents", HookComponent)
|
|
||||||
|
|
||||||
func HookComponent(a ...any) []Components[string] {
|
|
||||||
componentKey := a[0].(string)
|
|
||||||
scene := a[1].(string)
|
|
||||||
mut := reload.GetGlobeMutex()
|
|
||||||
mut.Lock()
|
|
||||||
defer mut.Unlock()
|
|
||||||
components := GetComponents(scene, componentKey)
|
|
||||||
r := slice.FilterAndMap(components, func(component Components[string]) (Components[string], bool) {
|
|
||||||
keyHooks, ok := handleComponentHook.Load(scene)
|
|
||||||
if !ok {
|
|
||||||
return component, true
|
|
||||||
}
|
|
||||||
hooks := keyHooks[componentKey]
|
|
||||||
for _, fn := range hooks {
|
|
||||||
hookedComponent, ok := fn(component)
|
|
||||||
if !ok { // DeleteComponents fn
|
|
||||||
return hookedComponent, false
|
|
||||||
}
|
|
||||||
component = hookedComponent // ReplaceComponents fn
|
|
||||||
}
|
|
||||||
return component, true
|
|
||||||
})
|
|
||||||
slice.SimpleSort(r, slice.DESC, func(t Components[string]) float64 {
|
|
||||||
return t.Order
|
|
||||||
})
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetComponents(scene, key string) (r []Components[string]) {
|
|
||||||
sceneComponents, _ := handleComponents.Load(scene)
|
|
||||||
allSceneComponents, _ := handleComponents.Load(constraints.AllScene)
|
|
||||||
r = append(sceneComponents[key], allSceneComponents[key]...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var CacheComponent = reload.BuildMapFnWithAnyParams[string]("cacheComponents", cacheComponentFn)
|
|
||||||
|
|
||||||
func cacheComponentFn(a ...any) string {
|
|
||||||
return a[0].(Components[string]).Fn(a[1].(*Handle))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CalComponent(h *Handle) func(string) string {
|
|
||||||
return func(componentKey string) string {
|
|
||||||
cacheKey := str.Join("get-hook-Components-", h.scene, "-", componentKey)
|
|
||||||
hookedComponents := GetAndHookComponents(cacheKey, componentKey, h.scene)
|
|
||||||
var s = make([]string, 0, len(hookedComponents))
|
|
||||||
for _, component := range hookedComponents {
|
|
||||||
if component.Val != "" {
|
|
||||||
s = append(s, component.Val)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if component.Fn != nil {
|
|
||||||
v := ""
|
|
||||||
if component.Cached {
|
|
||||||
key := str.Join(h.scene, "-", componentKey, "-", component.Name)
|
|
||||||
v = CacheComponent(key, component, h)
|
|
||||||
} else {
|
|
||||||
v = component.Fn(h)
|
|
||||||
}
|
|
||||||
if v != "" {
|
|
||||||
s = append(s, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(s, "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) PushComponents(scene, componentType string, components ...Components[string]) {
|
|
||||||
c, ok := handleComponents.Load(scene)
|
|
||||||
if !ok {
|
|
||||||
c = make(map[string][]Components[string])
|
|
||||||
}
|
|
||||||
c[componentType] = append(c[componentType], components...)
|
|
||||||
handleComponents.Store(scene, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) PushGroupComponentStr(scene, componentType, name string, order float64, strs ...string) {
|
|
||||||
var component = Components[string]{
|
|
||||||
Val: strings.Join(slice.FilterAndMap(strs, func(t string) (string, bool) {
|
|
||||||
t = strings.Trim(t, " \n\r\t\v\x00")
|
|
||||||
if t == "" {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return t, true
|
|
||||||
}), "\n"),
|
|
||||||
Order: order,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
h.PushComponents(scene, componentType, component)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) PushCacheGroupHeadScript(scene, name string, order float64, fns ...func(*Handle) string) {
|
|
||||||
h.PushGroupCacheComponentFn(scene, constraints.HeadScript, name, order, fns...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) PushFooterScript(scene string, components ...Components[string]) {
|
|
||||||
h.PushComponents(scene, constraints.FooterScript, components...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) PushGroupFooterScript(scene, name string, order float64, strs ...string) {
|
|
||||||
h.PushGroupComponentStr(scene, constraints.FooterScript, name, order, strs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) PushCacheGroupFooterScript(scene, name string, order float64, fns ...func(*Handle) string) {
|
|
||||||
h.PushGroupCacheComponentFn(scene, constraints.FooterScript, name, order, fns...)
|
|
||||||
}
|
|
||||||
func (h *Handle) PushGroupCacheComponentFn(scene, componentType, name string, order float64, fns ...func(*Handle) string) {
|
|
||||||
h.PushComponents(scene, componentType, NewComponent(name, "", true, order, func(h *Handle) string {
|
|
||||||
return strings.Join(slice.Map(fns, func(t func(*Handle) string) string {
|
|
||||||
return t(h)
|
|
||||||
}), "\n")
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewComponent(name, val string, cached bool, order float64, fn func(handle *Handle) string) Components[string] {
|
|
||||||
return Components[string]{Fn: fn, Name: name, Cached: cached, Order: order, Val: val}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) AddCacheComponent(scene, componentType, name string, order float64, fn func(*Handle) string) {
|
|
||||||
h.PushComponents(scene, componentType, NewComponent(name, "", true, order, fn))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) PushHeadScript(scene string, components ...Components[string]) {
|
|
||||||
h.PushComponents(scene, constraints.HeadScript, components...)
|
|
||||||
}
|
|
||||||
func (h *Handle) PushGroupHeadScript(scene, name string, order float64, str ...string) {
|
|
||||||
h.PushGroupComponentStr(scene, constraints.HeadScript, name, order, str...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetComponentsArgs[T any](k string, defaults T) T {
|
|
||||||
v, ok := componentsArgs.Load(k)
|
|
||||||
if ok {
|
|
||||||
vv, ok := v.(T)
|
|
||||||
if ok {
|
|
||||||
return vv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
func PushComponentsArgsForSlice[T any](name string, v ...T) {
|
|
||||||
val, ok := componentsArgs.Load(name)
|
|
||||||
if !ok {
|
|
||||||
var vv []T
|
|
||||||
vv = append(vv, v...)
|
|
||||||
componentsArgs.Store(name, vv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vv, ok := val.([]T)
|
|
||||||
if ok {
|
|
||||||
vv = append(vv, v...)
|
|
||||||
componentsArgs.Store(name, vv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func SetComponentsArgsForMap[K comparable, V any](name string, key K, v V) {
|
|
||||||
val, ok := componentsArgs.Load(name)
|
|
||||||
if !ok {
|
|
||||||
vv := make(map[K]V)
|
|
||||||
vv[key] = v
|
|
||||||
componentsArgs.Store(name, vv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vv, ok := val.(map[K]V)
|
|
||||||
if ok {
|
|
||||||
vv[key] = v
|
|
||||||
componentsArgs.Store(name, vv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func MergeComponentsArgsForMap[K comparable, V any](name string, m map[K]V) {
|
|
||||||
val, ok := componentsArgs.Load(name)
|
|
||||||
if !ok {
|
|
||||||
componentsArgs.Store(name, m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vv, ok := val.(map[K]V)
|
|
||||||
if ok {
|
|
||||||
componentsArgs.Store(name, maps.Merge(vv, m))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetComponentsArgs(key string, value any) {
|
|
||||||
componentsArgs.Store(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) GetComponentFilterFn(name string) ([]func(*Handle, string, ...any) string, bool) {
|
|
||||||
return componentFilterFns.Load(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) AddActionFilter(name string, fns ...func(*Handle, string, ...any) string) {
|
|
||||||
v, _ := componentFilterFns.Load(name)
|
|
||||||
v = append(v, fns...)
|
|
||||||
componentFilterFns.Store(name, v)
|
|
||||||
}
|
|
||||||
func (h *Handle) DoActionFilter(name, s string, args ...any) string {
|
|
||||||
calls, ok := componentFilterFns.Load(name)
|
|
||||||
if ok {
|
|
||||||
return slice.Reduce(calls, func(fn func(*Handle, string, ...any) string, r string) string {
|
|
||||||
return fn(h, r, args...)
|
|
||||||
}, s)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package components
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
|
||||||
"github.com/fthvgb1/wp-go/app/theme/wp/components/block"
|
|
||||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var blockFn = map[string]func(*wp.Handle, string, block.ParserBlock) (func() string, error){
|
|
||||||
"core/categories": block.Category,
|
|
||||||
}
|
|
||||||
|
|
||||||
func Block(id string) (func(*wp.Handle) string, string) {
|
|
||||||
content := wpconfig.GetPHPArrayVal("widget_block", "", str.ToInteger[int64](id, 0), "content")
|
|
||||||
if content == "" {
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
var name string
|
|
||||||
v := block.ParseBlock(content)
|
|
||||||
if len(v.Output) > 0 {
|
|
||||||
name = v.Output[0].Name
|
|
||||||
}
|
|
||||||
return func(h *wp.Handle) string {
|
|
||||||
var out []string
|
|
||||||
for _, parserBlock := range v.Output {
|
|
||||||
fn, ok := blockFn[parserBlock.Name]
|
|
||||||
if ok {
|
|
||||||
s, err := fn(h, id, parserBlock)
|
|
||||||
if err != nil {
|
|
||||||
logs.Error(err, str.Join("parse block", parserBlock.Name, " fail "), parserBlock)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, s())
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(out, "\n")
|
|
||||||
}, name
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
package block
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/dlclark/regexp2"
|
|
||||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Block struct {
|
|
||||||
Name string
|
|
||||||
Attrs string
|
|
||||||
Len int
|
|
||||||
StartOffset int
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
type BockParser struct {
|
|
||||||
Document string
|
|
||||||
Offset int
|
|
||||||
Output []ParserBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParserBlock struct {
|
|
||||||
Name string
|
|
||||||
Attrs string
|
|
||||||
InnerBlocks string
|
|
||||||
InnerHtml string
|
|
||||||
InnerContent string
|
|
||||||
}
|
|
||||||
|
|
||||||
var block = regexp2.MustCompile(`<!--\s+(?<closer>/)?wp:(?<namespace>[a-z][a-z0-9_-]*\/)?(?<name>[a-z][a-z0-9_-]*)\s+(?<attrs>{(?:(?:[^}]+|}+(?=})|(?!}\s+\/?-->).)*)?}\s+)?(?<void>\/)?-->`, regexp2.IgnoreCase|regexp2.Singleline)
|
|
||||||
|
|
||||||
func ParseBlock(content string) (r BockParser) {
|
|
||||||
m, err := block.FindStringMatch(content)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
r.Document = content
|
|
||||||
for m != nil {
|
|
||||||
if m.GroupCount() < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
b := token(m.Groups())
|
|
||||||
bb := ParserBlock{}
|
|
||||||
bb.Name = b.Name
|
|
||||||
bb.Attrs = b.Attrs
|
|
||||||
r.Output = append(r.Output, bb)
|
|
||||||
m, _ = block.FindNextMatch(m)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func token(g []regexp2.Group) (b Block) {
|
|
||||||
if len(g) < 1 {
|
|
||||||
b.Type = "no-more-tokens"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var closer, name, void, nameSpace = "", "", "", ""
|
|
||||||
for i, group := range g {
|
|
||||||
v := group.String()
|
|
||||||
if v == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch i {
|
|
||||||
case 0:
|
|
||||||
b.Len = group.Length
|
|
||||||
b.StartOffset = group.Index
|
|
||||||
case 1:
|
|
||||||
closer = v
|
|
||||||
case 2:
|
|
||||||
nameSpace = v
|
|
||||||
case 3:
|
|
||||||
name = v
|
|
||||||
case 4:
|
|
||||||
b.Attrs = v
|
|
||||||
case 5:
|
|
||||||
void = v
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if nameSpace == "" {
|
|
||||||
nameSpace = "core/"
|
|
||||||
}
|
|
||||||
b.Name = str.Join(nameSpace, name)
|
|
||||||
if void != "" {
|
|
||||||
b.Type = "void-block"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if closer != "" {
|
|
||||||
b.Type = "block-closer"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.Type = "block-opener"
|
|
||||||
return b
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package block
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestParseBlock(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
s string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "t1",
|
|
||||||
args: args{
|
|
||||||
s: `<!-- wp:categories {"showPostCounts":true,"showEmpty":true} /-->`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
ParseBlock(tt.args.s)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user