Compare commits

..

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

858 changed files with 577826 additions and 26924 deletions

8
.gitignore vendored
View File

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

View File

@ -1,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
View File

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

View File

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

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

View File

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

View File

@ -1,293 +0,0 @@
package actions
import (
"bytes"
"compress/gzip"
"errors"
"fmt"
"github.com/fthvgb1/wp-go/app/mail"
"github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"io"
"net/http"
"net/url"
"strings"
"time"
)
type CommentForm struct {
CommentPostId uint64 `form:"comment_post_ID" binding:"required" json:"comment_post_ID"`
Author string `form:"author" binding:"required" label:"显示名称" json:"author"`
Email string `form:"email" binding:"required,email"`
Comment string `form:"comment" binding:"required" label:"评论" json:"comment"`
}
func PostComment(c *gin.Context) {
cli := &http.Client{
Timeout: time.Second * 3,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
data, err := c.GetRawData()
defer func() {
if err != nil {
c.Writer.WriteHeader(http.StatusConflict)
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
var v validator.ValidationErrors
if errors.As(err, &v) {
e := v.Translate(config.GetZh())
for _, v := range e {
fmt.Fprintf(c.Writer, fail, v)
return
}
} else {
c.Writer.WriteString("评论出错,请联系管理员或稍后再度")
}
}
}()
conf := config.GetConfig()
if err != nil {
logs.Error(err, "获取评论数据错误")
return
}
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
var comment CommentForm
if err = c.ShouldBind(&comment); err != nil {
return
}
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
req, err := http.NewRequest("POST", conf.PostCommentUrl, strings.NewReader(c.Request.PostForm.Encode()))
if err != nil {
logs.Error(err, "创建评论请求错误")
return
}
defer req.Body.Close()
req.Header = c.Request.Header.Clone()
home, err := url.Parse(wpconfig.GetOption("siteurl"))
if err != nil {
logs.Error(err, "解析评论接口错误")
return
}
req.Host = home.Host
res, err := cli.Do(req)
if err != nil && !errors.Is(err, http.ErrUseLastResponse) {
logs.Error(err, "请求评论接口错误")
return
}
if res.StatusCode == http.StatusFound {
for _, cookie := range res.Cookies() {
c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly)
}
u := res.Header.Get("Location")
up, er := url.Parse(u)
if er != nil {
err = er
return
}
cu, er := url.Parse(conf.PostCommentUrl)
if er != nil {
err = er
return
}
up.Host = cu.Host
up.Scheme = "http"
newReq, _ := http.NewRequest("GET", up.String(), nil)
newReq.Host = home.Host
newReq.Header.Set("Cookie", strings.Join(slice.Map(c.Request.Cookies(), func(t *http.Cookie) string {
return fmt.Sprintf("%s=%s", t.Name, t.Value)
}), "; "))
ress, er := http.DefaultClient.Do(newReq)
if er != nil {
err = er
return
}
cc := c.Copy()
go func() {
if gin.Mode() != gin.ReleaseMode {
return
}
id := comment.CommentPostId
if id <= 0 {
logs.Error(errors.New("获取文档id错误"), "", comment.CommentPostId)
return
}
post, err := cache.GetPostById(cc, id)
if err != nil {
logs.Error(err, "获取文档错误", id)
return
}
su := fmt.Sprintf("%s: %s[%s]发表了评论对文档[%v]的评论", wpconfig.GetOption("siteurl"), comment.Author, comment.Email, post.PostTitle)
err = mail.SendMail([]string{conf.Mail.User}, su, comment.Comment)
logs.IfError(err, "发送邮件", conf.Mail.User, su, comment)
}()
s, er := io.ReadAll(ress.Body)
if er != nil {
err = er
return
}
uuu := ""
uu, _ := url.Parse(res.Header.Get("Location"))
if up.RawQuery != "" {
cache.NewCommentCache().Set(c, up.RawQuery, string(s))
uuu = str.Join(uu.Path, "?", uu.RawQuery)
} else {
uuu = str.Join(uu.Path)
}
c.Redirect(http.StatusFound, uuu)
return
}
var r io.Reader
if res.Header.Get("Content-Encoding") == "gzip" {
r, err = gzip.NewReader(res.Body)
if err != nil {
logs.Error(err, "gzip解压错误")
return
}
} else {
r = res.Body
}
s, err := io.ReadAll(r)
if err != nil {
logs.Error(err, "读取结果错误")
return
}
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
c.Writer.WriteHeader(res.StatusCode)
_, _ = c.Writer.Write(s)
}
var fail = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width">
<meta name='robots' content='max-image-preview:large, noindex, follow' />
<title>评论提交失败</title>
<style type="text/css">
html {
background: #f1f1f1;
}
body {
background: #fff;
border: 1px solid #ccd0d4;
color: #444;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
margin: 2em auto;
padding: 1em 2em;
max-width: 700px;
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
box-shadow: 0 1px 1px rgba(0, 0, 0, .04);
}
h1 {
border-bottom: 1px solid #dadada;
clear: both;
color: #666;
font-size: 24px;
margin: 30px 0 0 0;
padding: 0;
padding-bottom: 7px;
}
#error-page {
margin-top: 50px;
}
#error-page p,
#error-page .wp-die-message {
font-size: 14px;
line-height: 1.5;
margin: 25px 0 20px;
}
#error-page code {
font-family: Consolas, Monaco, monospace;
}
ul li {
margin-bottom: 10px;
font-size: 14px ;
}
a {
color: #0073aa;
}
a:hover,
a:active {
color: #006799;
}
a:focus {
color: #124964;
-webkit-box-shadow:
0 0 0 1px #5b9dd9,
0 0 2px 1px rgba(30, 140, 190, 0.8);
box-shadow:
0 0 0 1px #5b9dd9,
0 0 2px 1px rgba(30, 140, 190, 0.8);
outline: none;
}
.button {
background: #f3f5f6;
border: 1px solid #016087;
color: #016087;
display: inline-block;
text-decoration: none;
font-size: 13px;
line-height: 2;
height: 28px;
margin: 0;
padding: 0 10px 1px;
cursor: pointer;
-webkit-border-radius: 3px;
-webkit-appearance: none;
border-radius: 3px;
white-space: nowrap;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
vertical-align: top;
}
.button.button-large {
line-height: 2.30769231;
min-height: 32px;
padding: 0 12px;
}
.button:hover,
.button:focus {
background: #f1f1f1;
}
.button:focus {
background: #f3f5f6;
border-color: #007cba;
-webkit-box-shadow: 0 0 0 1px #007cba;
box-shadow: 0 0 0 1px #007cba;
color: #016087;
outline: 2px solid transparent;
outline-offset: 0;
}
.button:active {
background: #f3f5f6;
border-color: #7e8993;
-webkit-box-shadow: none;
box-shadow: none;
}
</style>
</head>
<body id="error-page">
<div class="wp-die-message"><p><strong>错误</strong>%s</p></div>
<p><a href='javascript:history.back()'>&laquo; 返回</a></p></body>
</html>
`

View File

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

View File

@ -1,15 +0,0 @@
package actions
import (
"github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/gin-gonic/gin"
)
func ThemeHook(scene string) func(*gin.Context) {
return func(c *gin.Context) {
t := theme.GetCurrentTheme()
h := wp.NewHandle(c, scene, t)
theme.Hook(t, h)
}
}

View File

@ -1,111 +0,0 @@
package main
import (
"flag"
"fmt"
"github.com/fthvgb1/wp-go/app/ossigns"
"github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/db"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
"github.com/fthvgb1/wp-go/app/route"
"github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/model"
"os"
"regexp"
"strings"
"time"
)
var confPath string
var address string
var intReg = regexp.MustCompile(`^\d`)
func inits() {
flag.StringVar(&confPath, "c", "config.yaml", "config file support json,yaml or url")
flag.StringVar(&address, "p", "", "listen address and port")
flag.Parse()
if address == "" && os.Getenv("PORT") == "" {
address = "80"
}
if intReg.MatchString(address) && !strings.Contains(address, ":") {
address = ":" + address
}
err := initConf(confPath)
if err != nil {
panic(err)
}
cache.InitActionsCommonCache()
plugins.InitDigestCache()
theme.InitTheme()
go cronClearCache()
}
func initConf(c string) (err error) {
err = config.InitConfig(c)
if err != nil {
return
}
err = config.InitTrans()
if err != nil {
return err
}
err = logs.InitLogger()
if err != nil {
return err
}
database, err := db.InitDb()
if err != nil {
return
}
model.InitDB(db.QueryDb(database))
err = wpconfig.InitOptions()
if err != nil {
return
}
err = wpconfig.InitTerms()
if err != nil {
return
}
wphandle.LoadPlugins()
return
}
func cronClearCache() {
t := time.NewTicker(config.GetConfig().CacheTime.CrontabClearCacheTime)
for {
select {
case <-t.C:
cachemanager.ClearExpired()
}
}
}
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println(r)
os.Exit(1)
}
}()
inits()
ossigns.SetConfPath(confPath)
go ossigns.SignalNotify()
Gin := route.SetupRouter()
c := config.GetConfig()
if c.Ssl.Key != "" && c.Ssl.Cert != "" {
err := Gin.RunTLS(address, c.Ssl.Cert, c.Ssl.Key)
if err != nil {
panic(err)
}
return
}
err := Gin.Run(address)
if err != nil {
panic(err)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,69 +0,0 @@
package ossigns
import (
"fmt"
"github.com/fthvgb1/wp-go/app/mail"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/db"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/signs"
"log"
"syscall"
)
var confPath string
func SetConfPath(path string) {
confPath = path
}
func FlushCache() {
defer func() {
if r := recover(); r != nil {
err := mail.SendMail([]string{config.GetConfig().Mail.User}, "清空缓存失败", fmt.Sprintf("err:[%s]", r))
logs.IfError(err, "发邮件失败")
}
}()
cachemanager.Flush()
log.Println("all cache flushed")
}
func Reloads() {
defer func() {
if r := recover(); r != nil {
log.Println(r)
}
}()
err := config.InitConfig(confPath)
logs.IfError(err, "获取配置文件失败", confPath)
err = logs.InitLogger()
logs.IfError(err, "日志配置错误")
_, err = db.InitDb()
logs.IfError(err, "重新读取db失败", config.GetConfig().Mysql)
err = wpconfig.InitOptions()
logs.IfError(err, "获取网站设置WpOption失败")
err = wpconfig.InitTerms()
logs.IfError(err, "获取WpTerms表失败")
wphandle.LoadPlugins()
reload.Reloads("themeArgAndConfig")
FlushCache()
log.Println("reload complete")
}
func SignalNotify() {
rel := func() bool {
go Reloads()
return true
}
flu := func() bool {
go FlushCache()
return true
}
signs.Install(syscall.SIGUSR1, rel, "reload")
signs.Install(syscall.SIGUSR2, flu, "flush")
signs.Wait()
}

View File

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

View File

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

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

@ -1,126 +0,0 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/cache/reload"
"time"
)
func InitActionsCommonCache() {
c := config.GetConfig()
cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.SearchPostCacheTime, "searchPostIds", func() time.Duration {
return config.GetConfig().CacheTime.SearchPostCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
return config.GetConfig().CacheTime.PostListCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.MonthPost, c.CacheTime.MonthPostCacheTime, "monthPostIds", func() time.Duration {
return config.GetConfig().CacheTime.MonthPostCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.GetPostContext, c.CacheTime.ContextPostCacheTime, "postContext", func() time.Duration {
return config.GetConfig().CacheTime.ContextPostCacheTime
})
cachemanager.NewMemoryMapCache(dao.GetPostsByIds, nil, c.CacheTime.PostDataCacheTime, "postData", func() time.Duration {
return config.GetConfig().CacheTime.PostDataCacheTime
})
cachemanager.NewMemoryMapCache(dao.GetPostMetaByPostIds, nil, c.CacheTime.PostDataCacheTime, "postMetaData", func() time.Duration {
return config.GetConfig().CacheTime.PostDataCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.CategoriesAndTags, c.CacheTime.CategoryCacheTime, "categoryAndTagsData", func() time.Duration {
return config.GetConfig().CacheTime.CategoryCacheTime
})
cachemanager.NewVarMemoryCache(dao.RecentPosts, c.CacheTime.RecentPostCacheTime, "recentPosts", func() time.Duration {
return config.GetConfig().CacheTime.RecentPostCacheTime
})
cachemanager.NewVarMemoryCache(RecentComment, c.CacheTime.RecentCommentsCacheTime, "recentComments", func() time.Duration {
return config.GetConfig().CacheTime.RecentCommentsCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.CommentNum, 30*time.Second, "commentNumber", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
cachemanager.NewMemoryMapCache(nil, PostTopComments, 30*time.Second, "PostCommentsIds", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
cachemanager.NewMemoryMapCache(nil, dao.PostTopCommentNum, 30*time.Second, "postTopCommentsNum", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
cachemanager.NewMemoryMapCache(dao.GetCommentByIds, nil, time.Hour, "postCommentData", func() time.Duration {
return config.GetConfig().CacheTime.CommentsCacheTime
})
cachemanager.NewMemoryMapCache(dao.CommentChildren, nil, time.Minute, "commentChildren", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
cachemanager.NewVarMemoryCache(dao.GetMaxPostId, c.CacheTime.MaxPostIdCacheTime, "maxPostId", func() time.Duration {
return config.GetConfig().CacheTime.MaxPostIdCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.GetUserById, c.CacheTime.UserInfoCacheTime, "userData", func() time.Duration {
return config.GetConfig().CacheTime.UserInfoCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.GetUserByName, c.CacheTime.UserInfoCacheTime, "usernameToUserData", func() time.Duration {
return config.GetConfig().CacheTime.UserInfoCacheTime
})
cachemanager.NewVarMemoryCache(dao.AllUsername, c.CacheTime.UserInfoCacheTime, "allUsername", func() time.Duration {
return config.GetConfig().CacheTime.UserInfoCacheTime
})
cachemanager.NewVarMemoryCache(SiteFeed, time.Hour, "siteFeed")
cachemanager.NewMemoryMapCache(nil, PostFeed, time.Hour, "postFeed")
cachemanager.NewVarMemoryCache(CommentsFeed, time.Hour, "commentsFeed")
cachemanager.NewMemoryMapCache[string, string](nil, nil, 15*time.Minute, "NewComment")
InitFeed()
}
type Arch struct {
data []models.PostArchive
fn func(context.Context) ([]models.PostArchive, error)
month time.Month
}
var arch = reload.Vars(Arch{
fn: dao.Archives,
}, "archives-year-month-data")
func Archives(ctx context.Context) []models.PostArchive {
a := arch.Load()
data := a.data
l := len(data)
m := time.Now().Month()
if l < 1 || a.month != m {
r, err := a.fn(ctx)
if err != nil {
logs.Error(err, "set cache Archives fail")
return nil
}
a.month = m
a.data = r
arch.Store(a)
data = r
}
return data
}

View File

@ -1,39 +0,0 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/slice"
"time"
)
// CategoriesTags get all categories or tags
//
// query func see dao.CategoriesAndTags
//
// t is constraints.Tag or constraints.Category
func CategoriesTags(ctx context.Context, t ...string) []models.TermsMy {
tt := ""
if len(t) > 0 {
tt = t[0]
}
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
logs.IfError(err, "get category fail")
return r
}
func AllCategoryTagsNames(ctx context.Context, t ...string) map[string]struct{} {
tt := ""
if len(t) > 0 {
tt = t[0]
}
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
if err != nil {
logs.Error(err, "get category fail")
return nil
}
return slice.ToMap(r, func(t models.TermsMy) (string, struct{}) {
return t.Name, struct{}{}
}, true)
}

View File

@ -1,124 +0,0 @@
package cache
import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/number"
str "github.com/fthvgb1/wp-go/helper/strings"
"time"
)
// RecentComments query func see RecentComment
func RecentComments(ctx context.Context, n int) (r []models.Comments) {
nn := number.Max(n, 10)
r, err := cachemanager.GetVarVal[[]models.Comments]("recentComments", ctx, time.Second, ctx, nn)
if len(r) > n {
r = r[0:n]
}
logs.IfError(err, "get recent comment fail")
return
}
// PostTopLevelCommentIds query func see PostTopComments
func PostTopLevelCommentIds(ctx context.Context, postId uint64, page, limit, total int, order string, a ...any) ([]uint64, error) {
var key string
if len(a) > 0 {
key = helper.ParseArgs("", a...)
}
if key == "" {
key = fmt.Sprintf("%d-%d-%d-%d-%s", postId, page, limit, total, order)
}
return cachemanager.GetBy[[]uint64]("PostCommentsIds", ctx,
key, time.Second, postId, page, limit, 0, order)
}
// GetCommentById query func see dao.GetCommentByIds
func GetCommentById(ctx context.Context, id uint64) (models.Comments, error) {
return cachemanager.GetBy[models.Comments]("postCommentData", ctx, id, time.Second)
}
// GetCommentDataByIds query func see dao.GetCommentByIds
func GetCommentDataByIds(ctx context.Context, ids []uint64) ([]models.Comments, error) {
return cachemanager.GetBatchBy[models.Comments]("postCommentData", ctx, ids, time.Second)
}
func NewCommentCache() *cache.MapCache[string, string] {
r, _ := cachemanager.GetMapCache[string, string]("NewComment")
return r
}
func PostTopComments(ctx context.Context, _ string, a ...any) ([]uint64, error) {
postId := a[0].(uint64)
page := a[1].(int)
limit := a[2].(int)
total := a[3].(int)
order := helper.ParseArgs("", a...)
if order == "" {
order = wpconfig.GetOption("comment_order")
}
v, _, err := dao.PostCommentsIds(ctx, postId, page, limit, total, order)
if err != nil {
return nil, err
}
return v, nil
}
func RecentComment(ctx context.Context, a ...any) (r []models.Comments, err error) {
r, err = dao.RecentComments(ctx, a...)
if err != nil {
return r, err
}
for i, comment := range r {
r[i].CommentAuthorUrl, err = GetCommentUrl(ctx, comment.CommentId, comment.CommentPostId)
if err != nil {
return nil, err
}
}
return
}
func GetCommentUrl(ctx context.Context, commentId, postId uint64) (string, error) {
if wpconfig.GetOption("page_comments") != "1" {
return fmt.Sprintf("/p/%d#comment-%d", postId, commentId), nil
}
commentsPerPage := str.ToInteger(wpconfig.GetOption("comments_per_page"), 5)
topCommentId, err := AncestorCommentId(ctx, commentId)
if err != nil {
return "", err
}
totalNum, err := cachemanager.GetBy[int]("postTopCommentsNum", ctx, postId, time.Second)
if err != nil {
return "", err
}
if totalNum <= commentsPerPage {
return fmt.Sprintf("/p/%d#comment-%d", postId, commentId), nil
}
num, err := dao.PreviousCommentNum(ctx, topCommentId, postId)
if err != nil {
return "", err
}
order := wpconfig.GetOption("comment_order")
page := number.DivideCeil(num+1, commentsPerPage)
if order == "desc" {
page = number.DivideCeil(totalNum-num, commentsPerPage)
}
return fmt.Sprintf("/p/%d/comment-page-%d/#comment-%d", postId, page, commentId), nil
}
func AncestorCommentId(ctx context.Context, commentId uint64) (uint64, error) {
comment, err := GetCommentById(ctx, commentId)
if err != nil {
return 0, err
}
if comment.CommentParent == 0 {
return comment.CommentId, nil
}
return AncestorCommentId(ctx, comment.CommentParent)
}

213
app/pkg/cache/feed.go vendored
View File

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

View File

@ -1,17 +0,0 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"time"
)
// GetPostMetaByPostIds query func see dao.GetPostMetaByPostIds
func GetPostMetaByPostIds(ctx context.Context, ids []uint64) ([]map[string]any, error) {
return cachemanager.GetBatchBy[map[string]any]("postMetaData", ctx, ids, time.Second)
}
// GetPostMetaByPostId query func see dao.GetPostMetaByPostIds
func GetPostMetaByPostId(ctx context.Context, id uint64) (map[string]any, error) {
return cachemanager.GetBy[map[string]any]("postMetaData", ctx, id, time.Second)
}

View File

@ -1,91 +0,0 @@
package cache
import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"time"
)
// GetPostById query func see dao.GetPostsByIds
func GetPostById(ctx context.Context, id uint64) (models.Posts, error) {
return cachemanager.GetBy[models.Posts]("postData", ctx, id, time.Second)
}
// GetPostsByIds query func see dao.GetPostsByIds
func GetPostsByIds(ctx context.Context, ids []uint64) ([]models.Posts, error) {
return cachemanager.GetBatchBy[models.Posts]("postData", ctx, ids, time.Second)
}
// SearchPost query func see dao.SearchPostIds
func SearchPost(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
ids, err := cachemanager.GetBy[dao.PostIds]("searchPostIds", ctx, key, time.Second, args...)
if err != nil {
return
}
total = ids.Length
r, err = GetPostsByIds(ctx, ids.Ids)
return
}
// PostLists query func see dao.SearchPostIds
func PostLists(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
ids, err := cachemanager.GetBy[dao.PostIds]("listPostIds", ctx, key, time.Second, args...)
if err != nil {
return
}
total = ids.Length
r, err = GetPostsByIds(ctx, ids.Ids)
return
}
// GetMaxPostId query func see dao.GetMaxPostId
func GetMaxPostId(ctx context.Context) (uint64, error) {
return cachemanager.GetVarVal[uint64]("maxPostId", ctx, time.Second)
}
// RecentPosts query func see dao.RecentPosts
func RecentPosts(ctx context.Context, n int) (r []models.Posts) {
nn := n
feedNum := str.ToInteger(wpconfig.GetOption("posts_per_rss"), 10)
nn = number.Max(n, feedNum)
r, err := cachemanager.GetVarVal[[]models.Posts]("recentPosts", ctx, time.Second, nn)
if n < len(r) {
r = r[:n]
}
logs.IfError(err, "get recent post")
return
}
// GetContextPost query func see dao.GetPostContext
func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next models.Posts, err error) {
postCtx, err := cachemanager.GetBy[dao.PostContext]("postContext", ctx, id, time.Second, date)
if err != nil {
return models.Posts{}, models.Posts{}, err
}
prev = postCtx.Prev
next = postCtx.Next
return
}
// GetMonthPostIds query func see dao.MonthPost
func GetMonthPostIds(ctx context.Context, year, month string, page, limit int, order string) (r []models.Posts, total int, err error) {
res, err := cachemanager.GetBy[[]uint64]("monthPostIds", ctx, fmt.Sprintf("%s%s", year, month), time.Second, year, month)
if err != nil {
return
}
if order == "desc" {
res = slice.Reverse(res)
}
total = len(res)
rr := slice.Pagination(res, page, limit)
r, err = GetPostsByIds(ctx, rr)
return
}

View File

@ -1,26 +0,0 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"time"
)
// GetUserByName query func see dao.GetUserByName
func GetUserByName(ctx context.Context, username string) (models.Users, error) {
return cachemanager.GetBy[models.Users]("usernameToUserData", ctx, username, time.Second)
}
// GetAllUsername query func see dao.AllUsername
func GetAllUsername(ctx context.Context) (map[string]uint64, error) {
return cachemanager.GetVarVal[map[string]uint64]("allUsername", ctx, time.Second)
}
// GetUserById query func see dao.GetUserById
func GetUserById(ctx context.Context, uid uint64) models.Users {
r, err := cachemanager.GetBy[models.Users]("userData", ctx, uid, time.Second)
logs.IfError(err, "get user", uid)
return r
}

View File

@ -1,147 +0,0 @@
package config
import (
"fmt"
"github.com/fthvgb1/wp-go/safety"
"gopkg.in/yaml.v2"
"io"
"net/http"
"os"
"strings"
"time"
)
var config safety.Var[Config]
func GetConfig() Config {
return config.Load()
}
type Config struct {
Ssl Ssl `yaml:"ssl" json:"ssl"`
Mysql Mysql `yaml:"mysql" json:"mysql"`
Mail Mail `yaml:"mail" json:"mail"`
CacheTime CacheTime `yaml:"cacheTime" json:"cacheTime"`
PluginPath string `yaml:"pluginPath" json:"pluginPath"`
ExternScript []string `json:"externScript" yaml:"externScript"`
DigestWordCount int `yaml:"digestWordCount" json:"digestWordCount,omitempty"`
DigestAllowTag string `yaml:"digestAllowTag" json:"digestAllowTag"`
MaxRequestSleepNum int64 `yaml:"maxRequestSleepNum" json:"maxRequestSleepNum,omitempty"`
MaxRequestNum int64 `yaml:"maxRequestNum" json:"maxRequestNum,omitempty"`
SingleIpSearchNum int64 `yaml:"singleIpSearchNum" json:"singleIpSearchNum,omitempty"`
Gzip bool `yaml:"gzip" json:"gzip,omitempty"`
PostCommentUrl string `yaml:"postCommentUrl" json:"postCommentUrl,omitempty"`
TrustIps []string `yaml:"trustIps" json:"trustIps,omitempty"`
TrustServerNames []string `yaml:"trustServerNames" json:"trustServerNames,omitempty"`
Theme string `yaml:"theme" json:"theme,omitempty"`
PostOrder string `yaml:"postOrder" json:"postOrder,omitempty"`
UploadDir string `yaml:"uploadDir" json:"uploadDir,omitempty"`
Pprof string `yaml:"pprof" json:"pprof,omitempty"`
ListPagePlugins []string `yaml:"listPagePlugins" json:"listPagePlugins,omitempty"`
PaginationStep int `yaml:"paginationStep" json:"paginationStep,omitempty"`
ShowQuerySql bool `yaml:"showQuerySql" json:"showQuerySql,omitempty"`
Plugins []string `yaml:"plugins" json:"plugins,omitempty"`
LogOutput string `yaml:"logOutput" json:"logOutput,omitempty"`
WpDir string `yaml:"wpDir" json:"wpDir"`
}
type CacheTime struct {
CacheControl time.Duration `yaml:"cacheControl" json:"cacheControl,omitempty"`
RecentPostCacheTime time.Duration `yaml:"recentPostCacheTime" json:"recentPostCacheTime,omitempty"`
CategoryCacheTime time.Duration `yaml:"categoryCacheTime" json:"categoryCacheTime,omitempty"`
ArchiveCacheTime time.Duration `yaml:"archiveCacheTime" json:"archiveCacheTime,omitempty"`
ContextPostCacheTime time.Duration `yaml:"contextPostCacheTime" json:"contextPostCacheTime,omitempty"`
RecentCommentsCacheTime time.Duration `yaml:"recentCommentsCacheTime" json:"recentCommentsCacheTime,omitempty"`
DigestCacheTime time.Duration `yaml:"digestCacheTime" json:"digestCacheTime,omitempty"`
PostListCacheTime time.Duration `yaml:"postListCacheTime" json:"postListCacheTime,omitempty"`
SearchPostCacheTime time.Duration `yaml:"searchPostCacheTime" json:"searchPostCacheTime,omitempty"`
MonthPostCacheTime time.Duration `yaml:"monthPostCacheTime" json:"monthPostCacheTime,omitempty"`
PostDataCacheTime time.Duration `yaml:"postDataCacheTime" json:"postDataCacheTime,omitempty"`
PostCommentsCacheTime time.Duration `yaml:"postCommentsCacheTime" json:"postCommentsCacheTime,omitempty"`
CrontabClearCacheTime time.Duration `yaml:"crontabClearCacheTime" json:"crontabClearCacheTime,omitempty"`
MaxPostIdCacheTime time.Duration `yaml:"maxPostIdCacheTime" json:"maxPostIdCacheTime,omitempty"`
UserInfoCacheTime time.Duration `yaml:"userInfoCacheTime" json:"userInfoCacheTime,omitempty"`
CommentsCacheTime time.Duration `yaml:"commentsCacheTime" json:"commentsCacheTime,omitempty"`
SleepTime []time.Duration `yaml:"sleepTime" json:"sleepTime,omitempty"`
CommentsIncreaseUpdateTime time.Duration `yaml:"commentsIncreaseUpdateTime" json:"commentsIncreaseUpdateTime"`
}
type Ssl struct {
Cert string `yaml:"cert" json:"cert,omitempty"`
Key string `yaml:"key" json:"key,omitempty"`
}
type Mail struct {
User string `yaml:"user" json:"user,omitempty"`
Alias string `yaml:"alias" json:"alias,omitempty"`
Pass string `yaml:"pass" json:"pass,omitempty"`
Host string `yaml:"host" json:"host,omitempty"`
Port int `yaml:"port" json:"port,omitempty"`
InsecureSkipVerify bool `yaml:"insecureSkipVerify" json:"insecureSkipVerify,omitempty"`
}
type Mysql struct {
Dsn Dsn `yaml:"dsn" json:"dsn"`
Pool Pool `yaml:"pool" json:"pool"`
}
func GetCustomizedConfig[T any]() (T, error) {
var r T
err := yaml.Unmarshal(fileData.Load(), &r)
return r, err
}
var fileData = safety.NewVar([]byte{})
func InitConfig(conf string) error {
if conf == "" {
conf = "config.yaml"
}
var file []byte
var err error
if strings.Contains(conf, "http") {
get, err := http.Get(conf)
if err != nil {
return err
}
defer get.Body.Close()
file, err = io.ReadAll(get.Body)
} else {
file, err = os.ReadFile(conf)
}
if err != nil {
return err
}
fileData.Store(file)
var c Config
err = yaml.Unmarshal(file, &c)
if err != nil {
return err
}
config.Store(c)
return nil
}
type Dsn struct {
Host string `yaml:"host" json:"host,omitempty"`
Port string `yaml:"port" json:"port,omitempty"`
Db string `yaml:"db" json:"db,omitempty"`
User string `yaml:"user" json:"user,omitempty"`
Password string `yaml:"password" json:"password,omitempty"`
Charset string `yaml:"charset" json:"charset,omitempty"`
}
func (m Dsn) GetDsn() string {
if m.Charset == "" {
m.Charset = "utf8"
}
t := "%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local"
return fmt.Sprintf(t, m.User, m.Password, m.Host, m.Port, m.Db, m.Charset)
}
type Pool struct {
ConnMaxIdleTime time.Duration `yaml:"connMaxIdleTime" json:"connMaxIdleTime,omitempty"`
MaxOpenConn int `yaml:"maxOpenConn" json:"maxOpenConn,omitempty"`
MaxIdleConn int `yaml:"maxIdleConn" json:"maxIdleConn,omitempty"`
ConnMaxLifetime time.Duration `yaml:"connMaxLifetime" json:"connMaxLifetime,omitempty"`
}

View File

@ -1,43 +0,0 @@
package config
import (
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
enTrans "github.com/go-playground/validator/v10/translations/en"
zhTrans "github.com/go-playground/validator/v10/translations/zh"
"reflect"
)
var enT ut.Translator
var zhT ut.Translator
func GetZh() ut.Translator {
return zhT
}
func GetEn() ut.Translator {
return enT
}
func InitTrans() error {
if validate, ok := binding.Validator.Engine().(*validator.Validate); ok {
ens := en.New()
uni := ut.New(ens, zh.New(), ens)
zhT, _ = uni.GetTranslator("zh")
enT, _ = uni.GetTranslator("en")
err := enTrans.RegisterDefaultTranslations(validate, enT)
if err != nil {
return err
}
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
return field.Tag.Get("label")
})
err = zhTrans.RegisterDefaultTranslations(validate, zhT)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,10 +0,0 @@
package blocks
const (
Search = "block-search"
RecentPosts = "block-recent-posts"
RecentComments = "block-recent-comments"
Archive = "block-archives"
Categories = "block-categories"
Meta = "block-meta"
)

View File

@ -1,30 +0,0 @@
package constraints
const (
Home = "Home"
Archive = "Archive"
Category = "Category"
Tag = "Tag"
Search = "Search"
Author = "Author"
Detail = "Detail"
NoRoute = "NoRoute"
Ok = "Ok"
Error404 = "Error404"
ParamError = "ParamError"
InternalErr = "InternalErr"
AllStats = "AllStats"
AllScene = "AllScene"
PipeData = "PipeData"
PipeMiddleware = "PipeMiddleware"
PipeRender = "PipeRender"
Defaults = "default"
HeadScript = "headScript"
FooterScript = "footerScript"
SidebarsWidgets = "sidebarsWidgets"
)

View File

@ -1,12 +0,0 @@
package widgets
const (
Search = "widget-search"
RecentPosts = "widget-recent-posts"
RecentComments = "widget-recent-comments"
Archive = "widget-archives"
Categories = "widget-categories"
Meta = "widget-meta"
Widget = "widget"
)

View File

@ -1,183 +0,0 @@
package dao
import (
"context"
"database/sql"
"errors"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/model"
)
// RecentComments
// param context.Context
func RecentComments(ctx context.Context, a ...any) (r []models.Comments, err error) {
n := helper.ParseArgs(10, a...)
return model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"},
{"post_status", "publish"},
}),
model.Fields("comment_ID,comment_author,comment_post_ID,post_title"),
model.Order(model.SqlBuilder{{"comment_date_gmt", "desc"}}),
model.Join(model.SqlBuilder{{"a", "left join", "wp_posts b", "a.comment_post_ID=b.ID"}}),
model.Limit(n),
))
}
// PostComments
// param1 context.Context
// param2 postId
func PostComments(ctx context.Context, postId uint64, _ ...any) ([]uint64, error) {
r, err := model.ChunkFind[models.Comments](ctx, 300, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
}),
model.Fields("comment_ID"),
model.Order(model.SqlBuilder{
{"comment_date_gmt", "asc"},
{"comment_ID", "asc"},
})),
)
if err != nil {
return nil, err
}
return slice.Map(r, func(t models.Comments) uint64 {
return t.CommentId
}), err
}
func GetCommentByIds(ctx context.Context, ids []uint64, _ ...any) (map[uint64]models.Comments, error) {
if len(ids) < 1 {
return nil, nil
}
m := make(map[uint64]models.Comments)
off := 0
for {
id := slice.Slice(ids, off, 500)
if len(id) < 1 {
break
}
r, err := model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_ID", "in", ""}, {"comment_approved", "1"},
}),
model.Fields("*"),
model.In(slice.ToAnySlice(id)),
))
if err != nil {
return m, err
}
for _, comments := range r {
m[comments.CommentId] = comments
}
off += 500
}
return m, nil
}
func CommentNum(ctx context.Context, postId uint64, _ ...any) (int, error) {
n, err := model.GetField[models.Posts](ctx, "comment_count", model.Conditions(
model.Where(model.SqlBuilder{{"ID", "=", number.IntToString(postId), "int"}})))
if err != nil {
return 0, err
}
return str.ToInteger(n, 0), err
}
func PostTopCommentNum(ctx context.Context, postId uint64, _ ...any) (int, error) {
v, err := model.GetField[models.Comments](ctx, "count(*) num", model.Conditions(
model.Where(postTopCommentNumWhere(postId)),
))
if err != nil {
return 0, err
}
return str.ToInteger(v, 0), nil
}
func postTopCommentNumWhere(postId uint64) model.SqlBuilder {
threadComments := wpconfig.GetOption("thread_comments")
pageComments := wpconfig.GetOption("page_comments")
where := model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
}
if pageComments != "1" || threadComments == "1" || "1" == wpconfig.GetOption("thread_comments_depth") {
where = append(where, []string{"comment_parent", "0"})
}
return where
}
func PostCommentsIds(ctx context.Context, postId uint64, page, limit, totalRaw int, order string) ([]uint64, int, error) {
condition := model.Conditions(
model.Where(postTopCommentNumWhere(postId)),
model.TotalRaw(totalRaw),
model.Fields("comment_ID"),
model.Order(model.SqlBuilder{
{"comment_date_gmt", order},
{"comment_ID", "asc"},
}),
)
var r []models.Comments
var total int
var err error
if limit < 1 {
r, err = model.ChunkFind[models.Comments](ctx, 300, condition)
total = len(r)
} else {
r, total, err = model.Pagination[models.Comments](ctx, condition, page, limit)
}
if err != nil && errors.Is(err, sql.ErrNoRows) {
err = nil
}
return slice.Map(r, func(t models.Comments) uint64 {
return t.CommentId
}), total, err
}
func CommentChildren(ctx context.Context, commentIds []uint64, _ ...any) (r map[uint64][]uint64, err error) {
rr, err := model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_parent", "in", ""},
{"comment_approved", "1"},
}),
model.In(slice.ToAnySlice(commentIds)),
model.Fields("comment_ID,comment_parent"),
))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
return
}
rrr := slice.GroupBy(rr, func(v models.Comments) (uint64, uint64) {
return v.CommentParent, v.CommentId
})
r = make(map[uint64][]uint64)
for _, id := range commentIds {
r[id] = rrr[id]
}
return
}
func PreviousCommentNum(ctx context.Context, commentId, postId uint64) (int, error) {
v, err := model.GetField[models.Comments](ctx, "count(*)", model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
{"comment_ID", "<", number.IntToString(commentId), "int"},
{"comment_parent", "=", "0", "int"},
}),
))
if err != nil {
return 0, err
}
return str.ToInteger(v, 0), nil
}

View File

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

View File

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

View File

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

View File

@ -1,32 +0,0 @@
package dao
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/model"
)
func GetUserById(ctx context.Context, uid uint64, _ ...any) (r models.Users, err error) {
r, err = model.FindOneById[models.Users](ctx, uid)
return
}
func AllUsername(ctx context.Context, _ ...any) (map[string]uint64, error) {
r, err := model.SimpleFind[models.Users](ctx, model.SqlBuilder{
{"user_status", "=", "0", "int"},
}, "display_name,ID")
if err != nil {
return nil, err
}
return slice.ToMap(r, func(t models.Users) (string, uint64) {
return t.DisplayName, t.Id
}, true), nil
}
func GetUserByName(ctx context.Context, u string, _ ...any) (r models.Users, err error) {
r, err = model.FirstOne[models.Users](ctx, model.SqlBuilder{{
"display_name", u,
}}, "*", nil)
return
}

View File

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

View File

@ -1,76 +0,0 @@
package logs
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/safety"
"io"
"log"
"os"
"runtime"
"strings"
)
var logs = safety.NewVar[*log.Logger](nil)
var logFile = safety.NewVar[*os.File](nil)
func InitLogger() error {
c := config.GetConfig()
return SetLogger(c.LogOutput)
}
func SetLogger(loggerFile string) error {
if loggerFile == "" {
loggerFile = "stderr"
}
preFD := logFile.Load()
l := &log.Logger{}
var out io.Writer
switch loggerFile {
case "stdout":
out = os.Stdout
case "stderr":
out = os.Stderr
default:
file, err := os.OpenFile(loggerFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0777)
if err != nil {
return err
}
out = file
logFile.Store(file)
}
logs.Store(l)
if preFD != nil {
_ = preFD.Close()
}
l.SetFlags(log.Ldate | log.Ltime)
l.SetOutput(out)
return nil
}
func Errs(err error, depth int, desc string, args ...any) {
var pcs [1]uintptr
runtime.Callers(depth, pcs[:])
f := runtime.CallersFrames([]uintptr{pcs[0]})
ff, _ := f.Next()
s := strings.Builder{}
_, _ = fmt.Fprintf(&s, "%s:%d %s err:[%s]", ff.File, ff.Line, desc, err)
if len(args) > 0 {
s.WriteString(" args:")
for _, arg := range args {
_, _ = fmt.Fprintf(&s, "%v", arg)
}
}
logs.Load().Println(s.String())
}
func Error(err error, desc string, args ...any) {
Errs(err, 3, desc, args...)
}
func IfError(err error, desc string, args ...any) {
if err == nil {
return
}
Errs(err, 3, desc, args...)
}

View File

@ -1,19 +0,0 @@
package relation
import (
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/model"
)
var PostsWithAuthor = model.RelationHasOne(func(m *models.Posts) uint64 {
return m.PostAuthor
}, func(p *models.Users) uint64 {
return p.Id
}, func(m *models.Posts, p *models.Users) {
m.Author = p
}, model.Relationship{
RelationType: model.HasOne,
Table: "wp_users user",
ForeignKey: "ID",
Local: "post_author",
})

View File

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

View File

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

View File

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

View File

@ -1,81 +0,0 @@
package main
import (
"embed"
"github.com/fthvgb1/wp-go/app/cmd/route"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
"github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components"
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget"
route2 "github.com/fthvgb1/wp-go/app/theme/wp/route"
"github.com/gin-gonic/gin"
"html/template"
"net/http"
"plugintt/xx"
)
//go:embed a.gohtml
var em embed.FS
var tt *template.Template
func init() {
// register as theme
theme.AddThemeHookFunc("themename", hook)
//use the local template
//note: must use embed.FS
t, err := template.ParseFS(em, "a.gohtml")
if err != nil {
logs.Error(err, "")
}
tt = t
// register gin route. it will be effecting when server restart.
route.Hook(func(r *gin.Engine) {
r.GET("xx", func(c *gin.Context) {
c.String(http.StatusOK, "xxoo")
})
})
}
func hook(h *wp.Handle) {
wp.Run(h, config)
}
func config(h *wp.Handle) {
// same theme config
wphandle.UsePlugins(h)
wp.InitPipe(h)
h.PushHandler(constraints.PipeMiddleware, constraints.Home,
wp.NewHandleFn(widget.IsCategory, 100, "widget.IsCategory"))
components.WidgetArea(h)
h.PushHandler(constraints.PipeRender, constraints.Home, wp.NewHandleFn(func(h *wp.Handle) {
h.SetData("aa", "xyxxxx")
h.RenderHtml(tt, http.StatusOK, "a.gohtml")
h.Abort()
h.StopPipe()
}, 10, "renderHome"))
// use simple reg route
route2.PushRoute(`(?P<control>\w+)/(?P<method>\w+)`, route2.Route{
Path: `(?P<control>\w+)/(?P<method>\w+)`,
Scene: constraints.Home,
Method: []string{"GET"},
Type: "reg",
})
//...
}
// Xo to be a func when theme init
func Xo(h *wp.Handle) {
xx.Xo()
route2.Delete(`(?P<control>\w+)/(?P<method>\w+)`)
h.ReplaceHandle(constraints.PipeRender, "wp.RenderTemplate", func(h *wp.Handle) {
h.SetData("aa", "xyxxxx")
h.RenderHtml(tt, http.StatusOK, "a.gohtml")
h.StopPipe()
})
}

View File

@ -1,7 +0,0 @@
#/bin/bash
# copy plugintt to other dir and remove .dev suffix
# note the go version and build tool flag must same to server build
# eg: -gcflags all="-N -l" --race may used in ide debug
go mod tidy
go build -buildmode=plugin -o xx.so main.go

View File

@ -1,141 +0,0 @@
package main
import (
"context"
"errors"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/helper/strings"
"github.com/redis/go-redis/v9"
"strconv"
str "strings"
"time"
)
type RdmCache[K comparable, V any] struct {
expired func() time.Duration
rdb *redis.Client
keyFn func(K) string
name string
resFn func(map[string]string) V
saveData func(V) map[string]string
}
func (r *RdmCache[K, V]) SetExpiredTime(f func() time.Duration) {
r.expired = f
}
func (r *RdmCache[K, V]) Get(ctx context.Context, key K) (V, bool) {
var re V
result, err := r.rdb.Exists(ctx, r.keyFn(key)).Result()
if result <= 0 || err != nil {
return re, false
}
rr, err := r.rdb.HGetAll(ctx, r.keyFn(key)).Result()
if errors.Is(err, redis.Nil) {
return re, false
}
if err != nil {
return re, false
}
return r.resFn(rr), true
}
func (r *RdmCache[K, V]) Set(ctx context.Context, key K, val V) {
k := r.keyFn(key)
result, err := r.rdb.HSet(ctx, k, r.saveData(val)).Result()
b, err := r.rdb.Expire(ctx, k, r.expired()).Result()
if err != nil {
fmt.Println(result, b, err)
return
}
fmt.Println(result, err)
}
func (r *RdmCache[K, V]) GetExpireTime(ctx context.Context) time.Duration {
return r.expired()
}
func (r *RdmCache[K, V]) Ttl(ctx context.Context, key K) time.Duration {
result, err := r.rdb.TTL(ctx, r.keyFn(key)).Result()
if err != nil {
return 0
}
return result
}
func (r *RdmCache[K, V]) Flush(ctx context.Context) {
fmt.Println("flush redis cache")
}
func (r *RdmCache[K, V]) Del(ctx context.Context, key ...K) {
r.rdb.Del(ctx, slice.Map(key, r.keyFn)...)
}
func (r *RdmCache[K, V]) ClearExpired(ctx context.Context) {
fmt.Println("clear expired redis cache")
}
// RedisCache use step:
// 1 go build -gcflags all="-N -l" --race -buildmode=plugin -o redisCache.so main.go && cp ./redisCache.so ../wp-go/plugins/
// 2 wp-go config add redisCache plugin
func RedisCache(h *wp.Handle) {
vv, ok := cachemanager.GetMapCache[string, dao.PostIds]("listPostIds")
if ok {
_, ok := any(vv.Cache).(*RdmCache[string, dao.PostIds])
if ok {
return
}
}
reload.AppendOnceFn(func() {
err := cachemanager.SetMapCache("listPostIds", vv)
if err != nil {
logs.Error(err, "set recovery listPostIds cache err")
} else {
cachemanager.PushOrSetFlush(cachemanager.Queue{Name: "listPostIds", Fn: vv.Flush})
cachemanager.PushOrSetClearExpired(cachemanager.Queue{Name: "listPostIds", Fn: vv.Flush})
fmt.Println("recovery listPostIds cache ok")
}
})
rdm := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
r := RdmCache[string, dao.PostIds]{
expired: func() time.Duration {
return time.Minute
},
keyFn: func(u string) string {
return strings.Join("postIds:", u)
},
rdb: rdm,
name: "",
resFn: func(m map[string]string) dao.PostIds {
return dao.PostIds{
Ids: slice.Map(str.Split(m["ids"], ","), strings.ToInt[uint64]),
Length: strings.ToInt[int](m["length"]),
}
},
saveData: func(ids dao.PostIds) map[string]string {
t := slice.Map(ids.Ids, number.IntToString[uint64])
return map[string]string{
"ids": str.Join(t, ","),
"length": strconv.Itoa(ids.Length),
}
},
}
cachemanager.NewMapCache[string, dao.PostIds](&r, nil, dao.SearchPostIds, config.GetConfig().CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
return config.GetConfig().CacheTime.PostListCacheTime
})
fmt.Println("redis cache inited ok")
}

View File

@ -1,50 +0,0 @@
module redisCache
go 1.21
require (
github.com/fthvgb1/wp-go v0.0.0-20231210111549-d72bed0c8c4e
github.com/redis/go-redis/v9 v9.3.0
)
replace github.com/fthvgb1/wp-go v0.0.0-20231210111549-d72bed0c8c4e => ../wp-go
require (
github.com/bytedance/sonic v1.10.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/elliotchance/phpserialize v1.3.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sessions v0.0.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -1,11 +0,0 @@
package xx
import (
"fmt"
"github.com/shopspring/decimal"
)
func Xo() {
fmt.Println("xxoo")
fmt.Println(decimal.Max(decimal.NewFromFloat(32.3333333333333), decimal.NewFromFloat(32.33333333333331)).String())
}

View File

@ -1,187 +0,0 @@
package plugins
import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/maps"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/plugin/digest"
"github.com/fthvgb1/wp-go/safety"
"regexp"
"strings"
"time"
"unicode/utf8"
)
var more = regexp.MustCompile("<!--more(.*?)?-->")
var removeWpBlock = regexp.MustCompile("<!-- /?wp:.*-->")
type DigestConfig struct {
DigestWordCount int `yaml:"digestWordCount"`
DigestAllowTag string `yaml:"digestAllowTag"`
DigestRegex string `yaml:"digestRegex"`
DigestTagOccupyNum []struct {
Tag string `yaml:"tag"`
Num int `yaml:"num"`
ChuckOvered bool `yaml:"chuckOvered"`
EscapeCharacter []struct {
Tags string `yaml:"tags"`
Character []string `yaml:"character"`
Num int `yaml:"num"`
ChuckOvered bool `yaml:"chuckOvered"`
} `yaml:"escapeCharacter"`
} `yaml:"digestTagOccupyNum"`
specialSolve map[string]digest.SpecialSolveConf
}
var digestConfig *safety.Var[DigestConfig]
func InitDigestCache() {
cachemanager.NewMemoryMapCache(nil, digestRaw, config.GetConfig().CacheTime.DigestCacheTime, "digestPlugin", func() time.Duration {
return config.GetConfig().CacheTime.DigestCacheTime
})
digestConfig = reload.VarsBy(func() DigestConfig {
c, err := config.GetCustomizedConfig[DigestConfig]()
if err != nil {
logs.Error(err, "get digest config fail")
c.DigestWordCount = config.GetConfig().DigestWordCount
c.DigestAllowTag = config.GetConfig().DigestAllowTag
return c
}
if c.DigestRegex != "" {
digest.SetQutos(c.DigestRegex)
}
if len(c.DigestTagOccupyNum) <= 1 {
return c
}
c.specialSolve = ParseDigestConf(c)
return c
}, "digestConfig")
}
func ParseDigestConf(c DigestConfig) map[string]digest.SpecialSolveConf {
specialSolve := map[string]digest.SpecialSolveConf{}
for _, item := range c.DigestTagOccupyNum {
tags := strings.Split(strings.ReplaceAll(item.Tag, " ", ""), "<")
for _, tag := range tags {
if tag == "" {
continue
}
ec := make(map[rune]digest.SpecialSolve)
specialTags := make(map[string]digest.SpecialSolve)
tag = str.Join("<", tag)
if len(item.EscapeCharacter) > 0 {
for _, esc := range item.EscapeCharacter {
for _, i := range esc.Character {
s := []rune(i)
if len(s) == 1 {
ec[s[0]] = digest.SpecialSolve{
Num: esc.Num,
ChuckOvered: esc.ChuckOvered,
}
}
}
if esc.Tags == "" {
continue
}
tagss := strings.Split(strings.ReplaceAll(esc.Tags, " ", ""), "<")
for _, t := range tagss {
if t == "" {
continue
}
t = str.Join("<", t)
specialTags[t] = digest.SpecialSolve{
Num: esc.Num,
ChuckOvered: esc.ChuckOvered,
}
}
}
}
v, ok := specialSolve[tag]
if !ok {
specialSolve[tag] = digest.SpecialSolveConf{
Num: item.Num,
ChuckOvered: item.ChuckOvered,
EscapeCharacter: ec,
Tags: specialTags,
}
continue
}
v.Num = item.Num
v.ChuckOvered = item.ChuckOvered
v.EscapeCharacter = maps.Merge(v.EscapeCharacter, ec)
v.Tags = maps.Merge(v.Tags, specialTags)
specialSolve[tag] = v
}
}
return specialSolve
}
func RemoveWpBlock(s string) string {
return removeWpBlock.ReplaceAllString(s, "")
}
func digestRaw(ctx context.Context, id uint64, arg ...any) (string, error) {
s := arg[1].(string)
limit := arg[3].(int)
if limit < 0 {
return s, nil
} else if limit == 0 {
return "", nil
}
s = more.ReplaceAllString(s, "")
fn := helper.GetContextVal(ctx, "postMoreFn", PostsMore)
return Digests(s, id, limit, fn), nil
}
func Digests(content string, id uint64, limit int, fn func(id uint64, content, closeTag string) string) string {
closeTag := ""
content = RemoveWpBlock(content)
c := digestConfig.Load()
tag := c.DigestAllowTag
if tag == "" {
tag = "<a><b><blockquote><br><cite><code><dd><del><div><dl><dt><em><h1><h2><h3><h4><h5><h6><i><img><li><ol><p><pre><span><strong><ul>"
}
content = digest.StripTags(content, tag)
length := utf8.RuneCountInString(content) + 1
if length <= limit {
return content
}
if len(c.specialSolve) > 0 {
content, closeTag = digest.CustomizeHtml(content, limit, c.specialSolve)
} else {
content, closeTag = digest.Html(content, limit)
}
if fn == nil {
return PostsMore(id, content, closeTag)
}
return fn(id, content, closeTag)
}
func PostsMore(id uint64, content, closeTag string) string {
tmp := `%s......%s<p class="read-more"><a href="/p/%d">继续阅读</a></p>`
if strings.Contains(closeTag, "pre") || strings.Contains(closeTag, "code") {
tmp = `%s%s......<p class="read-more"><a href="/p/%d">继续阅读</a></p>`
}
content = fmt.Sprintf(tmp, content, closeTag, id)
return content
}
func Digest(ctx context.Context, post *models.Posts, limit int) {
content, _ := cachemanager.GetBy[string]("digestPlugin", ctx, post.Id, time.Second, ctx, post.PostContent, post.Id, limit)
post.PostContent = content
}
func PostExcerpt(post *models.Posts) {
post.PostContent = strings.Replace(post.PostExcerpt, "\n", "<br/>", -1)
}

View File

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

View File

@ -1,176 +0,0 @@
package plugins
import (
"fmt"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper"
str "github.com/fthvgb1/wp-go/helper/strings"
"net/url"
"regexp"
"strconv"
"strings"
)
type PageEle struct {
PrevEle string
NextEle string
DotsEle string
MiddleEle string
CurrentEle string
}
func TwentyFifteenPagination() PageEle {
return twentyFifteen
}
func TwentyFifteenCommentPagination() CommentPageEle {
return twentyFifteenComment
}
type CommentPageEle struct {
PageEle
}
var twentyFifteen = PageEle{
PrevEle: `<a class="prev page-numbers" href="%s">上一页</a>`,
NextEle: `<a class="next page-numbers" href="%s">下一页</a>`,
DotsEle: `<span class="page-numbers dots">…</span>`,
MiddleEle: `<a class="page-numbers" href="%s"><span class="meta-nav screen-reader-text"> </span>%d</a>
`,
CurrentEle: `<span aria-current="page" class="page-numbers current">
<span class="meta-nav screen-reader-text"> </span>%d</span>`,
}
var twentyFifteenComment = CommentPageEle{
PageEle{
PrevEle: `<div class="nav-previous"><a href="%s">%s</a></div>`,
NextEle: `<div class="nav-next"><a href="%s">%s</a></div>`,
},
}
func (p PageEle) Current(page, totalPage, totalRow int) string {
return fmt.Sprintf(p.CurrentEle, page)
}
func (p PageEle) Prev(url string) string {
return fmt.Sprintf(p.PrevEle, url)
}
func (p PageEle) Next(url string) string {
return fmt.Sprintf(p.NextEle, url)
}
func (p PageEle) Dots() string {
return p.DotsEle
}
func (p PageEle) Middle(page int, url string) string {
return fmt.Sprintf(p.MiddleEle, url, page)
}
var reg = regexp.MustCompile(`(/page)/(\d+)`)
var commentReg = regexp.MustCompile(`/comment-page-(\d+)`)
var queryParam = []string{"paged", "cat", "m", "author", "tag"}
func (p PageEle) Urls(u url.URL, page int, isTLS bool) string {
var path, query = u.Path, u.RawQuery
if path == "/" {
v := u.Query()
for _, q := range queryParam {
if v.Get(q) != "" {
v.Set("paged", strconv.Itoa(page))
return str.Join(path, "?", v.Encode())
}
}
}
if !strings.Contains(path, "/page/") {
path = fmt.Sprintf("%s%s", path, "/page/1")
}
if page == 1 {
path = reg.ReplaceAllString(path, "")
} else {
s := fmt.Sprintf("$1/%d", page)
path = reg.ReplaceAllString(path, s)
}
path = strings.Replace(path, "//", "/", -1)
if path == "" {
path = "/"
}
if query != "" {
return str.Join(path, "?", query)
}
return path
}
func (p CommentPageEle) Urls(u url.URL, page int, isTLS bool) string {
var path, query = u.Path, u.RawQuery
if path == "/" {
v := u.Query()
if v.Get("p") != "" {
v.Set("cpage", strconv.Itoa(page))
return str.Join(path, "?", v.Encode(), "#comments")
}
}
if !strings.Contains(path, "/comment-page-") {
path = fmt.Sprintf("%s%s", path, "/comment-page-1")
}
path = commentReg.ReplaceAllString(path, fmt.Sprintf("/comment-page-%d", page))
path = strings.Replace(path, "//", "/", -1)
ur := path
if query != "" {
ur = str.Join(path, "?", query)
}
ur = str.Join(ur, "#comments")
return ur
}
func (p CommentPageEle) Middle(page int, url string) string {
return ""
}
func (p CommentPageEle) Dots() string {
return ""
}
func (p CommentPageEle) Current(page, totalPage, totalRow int) string {
return ""
}
func (p CommentPageEle) Prev(url string) string {
return fmt.Sprintf(p.PrevEle, url, helper.Or(wpconfig.GetOption("comment_order") == "asc", "较早评论", "较新评论"))
}
func (p CommentPageEle) Next(url string) string {
return fmt.Sprintf(p.NextEle, url, helper.Or(wpconfig.GetOption("comment_order") == "asc", "较新评论", "较早评论"))
}
type PaginationNav struct {
Currents func(page, totalPage, totalRows int) string
Prevs func(url string) string
Nexts func(url string) string
Dotss func() string
Middles func(page int, url string) string
Urlss func(u url.URL, page int, isTLS bool) string
}
func (p PaginationNav) Current(page, totalPage, totalRows int) string {
return p.Currents(page, totalPage, totalRows)
}
func (p PaginationNav) Prev(url string) string {
return p.Prevs(url)
}
func (p PaginationNav) Next(url string) string {
return p.Nexts(url)
}
func (p PaginationNav) Dots() string {
return p.Dotss()
}
func (p PaginationNav) Middle(page int, url string) string {
return p.Middles(page, url)
}
func (p PaginationNav) Urls(u url.URL, page int, isTLS bool) string {
return p.Urlss(u, page, isTLS)
}

View File

@ -1,30 +0,0 @@
package apply
import "github.com/fthvgb1/wp-go/safety"
var contains = safety.NewMap[string, any]()
func SetVal(key string, val any) {
contains.Store(key, val)
}
func DelVal(key string) {
contains.Delete(key)
}
func GetVal[V any](key string) (V, bool) {
v, ok := contains.Load(key)
if !ok {
var vv V
return vv, ok
}
return v.(V), ok
}
func GetRawVal(key string) (any, bool) {
return contains.Load(key)
}
func GetPlugins() any {
v, _ := contains.Load("wp-plugins")
return v
}

View File

@ -1,83 +0,0 @@
package enlightjs
import (
"fmt"
"github.com/fthvgb1/wp-go/app/phphelper"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/maps"
"github.com/goccy/go-json"
)
type Config struct {
Selectors Selectors `json:"selectors"`
Options Options `json:"options"`
}
type Options struct {
Indent int64 `json:"indent,omitempty"`
AmpersandCleanup bool `json:"ampersandCleanup,omitempty"`
Linehover bool `json:"linehover,omitempty"`
RawcodeDbclick bool `json:"rawcodeDbclick,omitempty"`
TextOverflow string `json:"textOverflow,omitempty"`
Linenumbers bool `json:"linenumbers,omitempty"`
Theme string `json:"theme,omitempty"`
Language string `json:"language,omitempty"`
RetainCssClasses bool `json:"retainCssClasses,omitempty"`
Collapse bool `json:"collapse,omitempty"`
ToolbarOuter string `json:"toolbarOuter,omitempty"`
ToolbarTop string `json:"toolbarTop,omitempty"`
ToolbarBottom string `json:"toolbarBottom,omitempty"`
}
type Selectors struct {
Block string `json:"block,omitempty"`
Inline string `json:"inline,omitempty"`
}
func EnlighterJS(h *wp.Handle) {
h.PushGroupHeadScript(constraints.AllScene, "enlighterjs-css", 20, `<link rel='stylesheet' id='enlighterjs-css' href='/wp-content/plugins/enlighter/cache/enlighterjs.min.css' media='all' />`)
h.PushCacheGroupFooterScript(constraints.AllScene, "enlighterJs", 10, func(h *wp.Handle) string {
op := wpconfig.GetOption("enlighter-options")
opp, err := phphelper.UnPHPSerializeToStrAnyMap(op)
if err != nil {
logs.Error(err, "获取enlighter-option失败", op)
return ""
}
v := Config{
Selectors: Selectors{
Block: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-selector-block", "pre.EnlighterJSRAW"),
Inline: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-selector-inline", "code.EnlighterJSRAW"),
},
Options: Options{
Indent: maps.GetStrAnyValWithDefaults[int64](opp, "enlighterjs-indent", 4),
AmpersandCleanup: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-ampersandcleanup", true),
Linehover: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linehover", true),
RawcodeDbclick: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-rawcodedbclick", true),
TextOverflow: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-textoverflow", "break"),
Linenumbers: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linenumbers", true),
Theme: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-theme", "enlighter"),
Language: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-language", "generic"),
RetainCssClasses: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-retaincss", false),
Collapse: false,
ToolbarOuter: "",
ToolbarTop: "{BTN_RAW}{BTN_COPY}{BTN_WINDOW}{BTN_WEBSITE}",
ToolbarBottom: "",
},
}
conf, err := json.Marshal(v)
if err != nil {
logs.Error(err, "json化enlighterjs配置失败")
return ""
}
return fmt.Sprintf(enlighterjs, conf)
})
}
var enlighterjs = `<script src='/wp-content/plugins/enlighter/cache/enlighterjs.min.js?ver=0A0B0C' id='enlighterjs-js'></script>
<script id='enlighterjs-js-after'>
!function(e,n){if("undefined"!=typeof EnlighterJS){var o=%s;(e.EnlighterJSINIT=function(){EnlighterJS.init(o.selectors.block,o.selectors.inline,o.options)})()}else{(n&&(n.error||n.log)||function(){})("Error: EnlighterJS resources not loaded yet!")}}(window,console);
</script>`

View File

@ -1,72 +0,0 @@
package wphandle
import (
"errors"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins/wphandle/apply"
"github.com/fthvgb1/wp-go/app/plugins/wphandle/enlightjs"
"github.com/fthvgb1/wp-go/app/plugins/wphandle/hiddenlogin"
"github.com/fthvgb1/wp-go/app/theme/wp"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
"path/filepath"
"plugin"
)
var plugins = func() *safety.Map[string, func(*wp.Handle)] {
m := safety.NewMap[string, func(*wp.Handle)]()
m.Store("Enlightjs", enlightjs.EnlighterJS)
m.Store("HiddenLogin", hiddenlogin.HiddenLogin)
return m
}()
func RegisterPlugin(name string, fn func(*wp.Handle)) {
plugins.Store(name, fn)
}
func UsePlugins(h *wp.Handle, calls ...string) {
calls = append(calls, config.GetConfig().Plugins...)
for _, call := range calls {
call = str.FirstUpper(call)
if fn, ok := plugins.Load(call); ok {
fn(h)
}
}
}
func LoadPlugins() {
dirPath := config.GetConfig().PluginPath
if dirPath == "" {
return
}
glob, err := filepath.Glob(filepath.Join(dirPath, "*.so"))
if err != nil {
logs.Error(err, "读取插件目录错误", dirPath)
return
}
for _, entry := range glob {
p, err := plugin.Open(entry)
if err != nil {
logs.Error(err, "读取插件错误", entry)
continue
}
name := filepath.Ext(entry)
name = filepath.Base(entry[0 : len(entry)-len(name)])
name = str.FirstUpper(name)
pl, err := p.Lookup(name)
if err != nil {
logs.Error(err, "插件lookup错误", entry)
continue
}
plu, ok := pl.(func(*wp.Handle))
if !ok {
logs.Error(errors.New("switch func(*wp.Handle) fail"), "插件转换错误", entry)
continue
}
RegisterPlugin(name, plu)
}
apply.SetVal("wp-plugins", func(h *wp.Handle) {
UsePlugins(h)
})
}

View File

@ -1,17 +0,0 @@
package hiddenlogin
import (
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/theme/wp"
str "github.com/fthvgb1/wp-go/helper/strings"
)
func HiddenLogin(h *wp.Handle) {
h.AddActionFilter(widgets.Meta, func(h *wp.Handle, s string, args ...any) string {
return str.Replace(s, map[string]string{
`<li><a href="/wp-login.php">登录</a></li>`: "",
`<li><a href="/feed">登录</a></li>`: "",
`<li><a href="/comments/feed">登录</a></li>`: "",
})
})
}

View File

@ -1,22 +0,0 @@
package tests
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme/wp"
)
func Tt(h *wp.Handle) {
h.HookHandle(constraints.PipeMiddleware, constraints.AllScene, func(call wp.HandleCall) (wp.HandleCall, bool) {
return call, false
})
/*h.PushPipeHook(constraints.Home, func(pipe wp.Pipe) (wp.Pipe, bool) {
return wp.Pipe{}, false
})*/
//h.DeletePipe(constraints.Home, constraints.PipeMiddleware)
h.ReplacePipe(constraints.Home, constraints.PipeMiddleware, wp.NewPipe("log", 500, func(next wp.HandleFn[*wp.Handle], h *wp.Handle) {
fmt.Println("ffff")
next(h)
fmt.Println("iiiii")
}))
}

View File

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

View File

@ -1,147 +0,0 @@
package route
import (
"github.com/fthvgb1/wp-go/app/actions"
"github.com/fthvgb1/wp-go/app/middleware"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-contrib/gzip"
"github.com/gin-contrib/pprof"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
type GinSetter func(*gin.Engine)
var setters mockmap.Map[string, GinSetter]
var setterHooks []func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool)
// SetGinAction 方便插件在init时使用
func SetGinAction(name string, hook GinSetter, orders ...float64) {
setters.Set(name, hook, orders...)
}
func HookGinSetter(fn func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool)) {
setterHooks = append(setterHooks, fn)
}
// DelGinSetter 方便插件在init时使用
func DelGinSetter(name string) {
setterHooks = append(setterHooks, func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool) {
return item, item.Name != name
})
}
func SetupRouter() *gin.Engine {
// Disable Console Color
// gin.DisableConsoleColor()
r := gin.New()
c := config.GetConfig()
SetGinAction("initTrustIp", func(r *gin.Engine) {
if len(c.TrustIps) > 0 {
err := r.SetTrustedProxies(c.TrustIps)
if err != nil {
panic(err)
}
}
}, 99.5)
SetGinAction("setTemplate", func(r *gin.Engine) {
r.HTMLRender = theme.BuildTemplate()
wpconfig.SetTemplateFs(theme.TemplateFs)
}, 90.5)
siteFlowLimitMiddleware, siteFlow := middleware.FlowLimit(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
reload.Append(func() {
c = config.GetConfig()
siteFlow(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
}, "site-flowLimit-config")
SetGinAction("setGlobalMiddleware", func(r *gin.Engine) {
r.Use(
gin.Logger(),
middleware.ValidateServerNames(),
middleware.RecoverAndSendMail(gin.DefaultErrorWriter),
siteFlowLimitMiddleware,
middleware.SetStaticFileCache,
)
}, 88.5)
SetGinAction("setGzip", func(r *gin.Engine) {
//gzip 因为一般会用nginx做反代时自动使用gzip,所以go这边本身可以不用
if c.Gzip {
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{
"/wp-includes/", "/wp-content/",
})))
}
}, 87.6)
SetGinAction("setWpDir", func(r *gin.Engine) {
if c.WpDir == "" {
panic("wordpress path can't be empty")
}
r.Static("/wp-content/uploads", str.Join(c.WpDir, "/wp-content/uploads"))
r.Static("/wp-content/themes", str.Join(c.WpDir, "/wp-content/themes"))
r.Static("/wp-content/plugins", str.Join(c.WpDir, "/wp-content/plugins"))
r.Static("/wp-includes/css", str.Join(c.WpDir, "/wp-includes/css"))
r.Static("/wp-includes/fonts", str.Join(c.WpDir, "/wp-includes/fonts"))
r.Static("/wp-includes/js", str.Join(c.WpDir, "/wp-includes/js"))
}, 86.1)
SetGinAction("setSession", func(r *gin.Engine) {
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("go-wp", store))
}, 85.1)
SetGinAction("setRoute", func(r *gin.Engine) {
r.GET("/", actions.Feed, middleware.SearchLimit(c.SingleIpSearchNum),
actions.ThemeHook(constraints.Home))
r.GET("/page/:page", actions.ThemeHook(constraints.Home))
r.GET("/p/category/:category", actions.ThemeHook(constraints.Category))
r.GET("/p/category/:category/page/:page", actions.ThemeHook(constraints.Category))
r.GET("/p/tag/:tag", actions.ThemeHook(constraints.Tag))
r.GET("/p/tag/:tag/page/:page", actions.ThemeHook(constraints.Tag))
r.GET("/p/date/:year/:month", actions.ThemeHook(constraints.Archive))
r.GET("/p/date/:year/:month/page/:page", actions.ThemeHook(constraints.Archive))
r.GET("/p/author/:author", actions.ThemeHook(constraints.Author))
r.GET("/p/author/:author/page/:page", actions.ThemeHook(constraints.Author))
r.POST("/login", actions.Login)
r.GET("/p/:id", actions.ThemeHook(constraints.Detail))
r.GET("/p/:id/comment-page-:page", actions.ThemeHook(constraints.Detail))
r.GET("/p/:id/feed", actions.PostFeed)
r.GET("/feed", actions.SiteFeed)
r.GET("/comments/feed", actions.CommentsFeed)
commentMiddleWare, _ := middleware.FlowLimit(c.MaxRequestSleepNum, 5, c.CacheTime.SleepTime)
r.POST("/comment", commentMiddleWare, actions.PostComment)
r.NoRoute(actions.ThemeHook(constraints.NoRoute))
}, 84.6)
SetGinAction("setpprof", func(r *gin.Engine) {
if c.Pprof != "" {
pprof.Register(r, c.Pprof)
}
}, 80.8)
for _, hook := range setterHooks {
setters = slice.FilterAndMap(setters, hook)
}
slice.SimpleSort(setters, slice.DESC, func(t mockmap.Item[string, GinSetter]) float64 {
return t.Order
})
for _, fn := range setters {
fn.Value(r)
}
return r
}

View File

@ -1,82 +0,0 @@
package theme
import (
"embed"
"github.com/fthvgb1/wp-go/multipTemplate"
"github.com/fthvgb1/wp-go/safety"
"html/template"
"io/fs"
"path/filepath"
"strings"
)
//go:embed *[^.go]
var TemplateFs embed.FS
var templates = safety.NewMap[string, *template.Template]() //方便外部获取模板render后的字符串不然在gin中获取不了
var multiple *multipTemplate.MultipleFsTemplate
func BuildTemplate() *multipTemplate.MultipleFsTemplate {
if multiple != nil {
tt := multipTemplate.NewFsTemplate(TemplateFs)
commonTemplate(tt)
for k, v := range map[string]*template.Template(any(tt.Template).(multipTemplate.TemplateMaps)) {
multiple.Template.Store(k, v)
}
} else {
multiple = multipTemplate.NewFsTemplates(TemplateFs, templates)
commonTemplate(multiple)
}
/*t.AddTemplate("twentyfifteen/*[^layout]/*.gohtml", FuncMap(), "twentyfifteen/layout/*.gohtml","wp/template.gohtml"). //单个主题设置
AddTemplate("twentyseventeen/*[^layout]/*.gohtml", FuncMap(), "twentyseventeen/layout/*.gohtml","wp/template.gohtml")*/
return multiple
}
func GetMultipleTemplate() *multipTemplate.MultipleFsTemplate {
if multiple == nil {
BuildTemplate()
}
return multiple
}
func GetTemplate(name string) (*template.Template, bool) {
t, ok := templates.Load(name)
return t, ok
}
// 所有主题模板通用设置
func commonTemplate(t *multipTemplate.MultipleFsTemplate) {
m, err := fs.Glob(t.Fs, "*/posts/*.gohtml")
if err != nil {
panic(err)
}
funMap := FuncMap()
for _, main := range m {
file := filepath.Base(main)
dir := strings.Split(main, "/")[0]
templ := template.Must(template.New(file).Funcs(funMap).ParseFS(t.Fs, main, filepath.Join(dir, "layout/*.gohtml"), "wp/template.gohtml"))
t.SetTemplate(main, templ)
}
}
func IsTemplateDirExists(tml string) bool {
arr, err := TemplateFs.ReadDir(tml)
if err != nil {
return false
}
if len(arr) > 0 {
return true
}
return false
}
func IsTemplateExists(tml string) bool {
t, ok := templates.Load(tml)
return ok && t != nil
}
func SetTemplate(name string, val *template.Template) {
templates.Store(name, val)
}

View File

@ -1,35 +0,0 @@
package theme
import (
"github.com/fthvgb1/wp-go/app/theme/wp"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
)
var themeMap = safety.NewMap[string, func(*wp.Handle)]()
func AddTheme(name string, fn func(handle *wp.Handle)) {
themeMap.Store(name, fn)
}
func DelTheme(name string) {
themeMap.Delete(name)
}
func GetTheme(name string) (func(*wp.Handle), bool) {
return themeMap.Load(name)
}
func IsThemeHookFuncExist(name string) bool {
_, ok := themeMap.Load(name)
return ok
}
func Hook(themeName string, h *wp.Handle) {
fn, ok := themeMap.Load(themeName)
if ok && fn != nil {
fn(h)
return
}
panic(str.Join("theme ", themeName, " don't exist"))
}

View File

@ -1,37 +0,0 @@
package theme
import (
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"html/template"
"time"
)
func postsFn(fn func(models.Posts) string, a models.Posts) string {
return fn(a)
}
func FuncMap() template.FuncMap {
return template.FuncMap{
"unescaped": func(s string) any {
return template.HTML(s)
},
"dateCh": func(t time.Time) any {
return t.Format("2006年 01月 02日")
},
"timeFormat": func(t time.Time, format string) any {
return t.Format(format)
},
"getOption": func(k string) string {
return wpconfig.GetOption(k)
},
"getLang": wpconfig.GetLang,
"postsFn": postsFn,
"exec": func(fn func() string) template.HTML {
return template.HTML(fn())
},
"callFuncString": func(fn func(string) string, s string) template.HTML {
return template.HTML(fn(s))
},
}
}

View File

@ -1,24 +0,0 @@
package theme
import (
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/theme/twentyfifteen"
"github.com/fthvgb1/wp-go/app/theme/twentyseventeen"
"github.com/fthvgb1/wp-go/app/wpconfig"
)
func InitTheme() {
AddTheme(twentyfifteen.ThemeName, twentyfifteen.Hook)
AddTheme(twentyseventeen.ThemeName, twentyseventeen.Hook)
}
func GetCurrentTheme() string {
themeName := config.GetConfig().Theme
if themeName == "" {
themeName = wpconfig.GetOption("template")
}
if !IsTemplateDirExists(themeName) {
themeName = "twentyfifteen"
}
return themeName
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,295 +0,0 @@
package twentyfifteen
import "github.com/fthvgb1/wp-go/app/wpconfig"
type themeSupport struct {
CustomBackground customBackground `json:"custom-background"`
EditorColorPalette []EditorColorPalette `json:"editor-color-palette"`
EditorGradientPresets []EditorGradientPresets `json:"editor-gradient-presets"`
}
type customBackground struct {
DefaultImage string `json:"default-image"`
DefaultPreset string `json:"default-preset"`
DefaultPositionX string `json:"default-position-x"`
DefaultPositionY string `json:"default-position-y"`
DefaultSize string `json:"default-size"`
DefaultRepeat string `json:"default-repeat"`
DefaultAttachment string `json:"default-attachment"`
DefaultColor string `json:"default-color"`
WpHeadCallback string `json:"wp-head-callback"`
AdminHeadCallback string `json:"admin-head-callback"`
AdminPreviewCallback string `json:"admin-preview-callback"`
}
type EditorColorPalette struct {
Name string `json:"name"`
Slug string `json:"slug"`
Color string `json:"color"`
}
type EditorGradientPresets struct {
Name string `json:"name"`
Slug string `json:"slug"`
Gradient string `json:"gradient"`
}
var themesupport = themeSupport{
CustomBackground: customBackground{
DefaultImage: "",
DefaultPreset: "default",
DefaultPositionX: "left",
DefaultPositionY: "top",
DefaultSize: "auto",
DefaultRepeat: "repeat",
DefaultAttachment: "fixed",
DefaultColor: "f1f1f1",
WpHeadCallback: "_custom_background_cb",
AdminHeadCallback: "",
AdminPreviewCallback: "",
},
EditorColorPalette: []EditorColorPalette{
{
Name: "暗灰色",
Slug: "dark-gray",
Color: "#111",
},
{
Name: "亮灰色",
Slug: "light-gray",
Color: "#f1f1f1",
},
{
Name: "白色",
Slug: "white",
Color: "#fff",
},
{
Name: "黄色",
Slug: "yellow",
Color: "#f4ca16",
},
{
Name: "暗棕色",
Slug: "dark-brown",
Color: "#352712",
},
{
Name: "粉色",
Slug: "medium-pink",
Color: "#e53b51",
},
{
Name: "浅粉色",
Slug: "light-pink",
Color: "#ffe5d1",
},
{
Name: "暗紫色",
Slug: "dark-purple",
Color: "#2e2256",
},
{
Name: "紫色",
Slug: "purple",
Color: "#674970",
},
{
Name: "蓝灰色",
Slug: "blue-gray",
Color: "#22313f",
},
{
Name: "亮蓝色",
Slug: "bright-blue",
Color: "#55c3dc",
},
{
Name: "浅蓝色",
Slug: "light-blue",
Color: "#e9f2f9",
},
},
EditorGradientPresets: []EditorGradientPresets{
{
Name: "Dark Gray Gradient",
Slug: "dark-gray-gradient-gradient",
Gradient: "linear-gradient(90deg, rgba(17,17,17,1) 0%, rgba(42,42,42,1) 100%)",
},
{
Name: "Light Gray Gradient",
Slug: "light-gray-gradient",
Gradient: "linear-gradient(90deg, rgba(241,241,241,1) 0%, rgba(215,215,215,1) 100%)",
},
{
Name: "White Gradient",
Slug: "white-gradient",
Gradient: "linear-gradient(90deg, rgba(255,255,255,1) 0%, rgba(230,230,230,1) 100%)",
},
{
Name: "Yellow Gradient",
Slug: "yellow-gradient",
Gradient: "linear-gradient(90deg, rgba(244,202,22,1) 0%, rgba(205,168,10,1) 100%)",
},
{
Name: "Dark Brown Gradient",
Slug: "dark-brown-gradient",
Gradient: "linear-gradient(90deg, rgba(53,39,18,1) 0%, rgba(91,67,31,1) 100%)",
},
{
Name: "Medium Pink Gradient",
Slug: "medium-pink-gradient",
Gradient: "linear-gradient(90deg, rgba(229,59,81,1) 0%, rgba(209,28,51,1) 100%)",
},
{
Name: "Light Pink Gradient",
Slug: "light-pink-gradient",
Gradient: "linear-gradient(90deg, rgba(255,229,209,1) 0%, rgba(255,200,158,1) 100%)",
},
{
Name: "Dark Purple Gradient",
Slug: "dark-purple-gradient",
Gradient: "linear-gradient(90deg, rgba(46,34,86,1) 0%, rgba(66,48,123,1) 100%)",
},
{
Name: "Purple Gradient",
Slug: "purple-gradient",
Gradient: "linear-gradient(90deg, rgba(103,73,112,1) 0%, rgba(131,93,143,1) 100%)",
},
{
Name: "Blue Gray Gradient",
Slug: "blue-gray-gradient",
Gradient: "linear-gradient(90deg, rgba(34,49,63,1) 0%, rgba(52,75,96,1) 100%)",
},
{
Name: "Bright Blue Gradient",
Slug: "bright-blue-gradient",
Gradient: "linear-gradient(90deg, rgba(85,195,220,1) 0%, rgba(43,180,211,1) 100%)",
},
{
Name: "Light Blue Gradient",
Slug: "light-blue-gradient",
Gradient: "linear-gradient(90deg, rgba(233,242,249,1) 0%, rgba(193,218,238,1) 100%)",
},
},
}
var colorscheme = map[string]ColorScheme{
"default": {
Label: "Default",
Colors: []string{
"#f1f1f1",
"#ffffff",
"#ffffff",
"#333333",
"#333333",
"#f7f7f7",
},
},
"dark": {
Label: "Dark",
Colors: []string{
"#111111",
"#202020",
"#202020",
"#bebebe",
"#bebebe",
"#1b1b1b",
},
},
"pink": {
Label: "Pink",
Colors: []string{
"#ffe5d1",
"#e53b51",
"#ffffff",
"#352712",
"#ffffff",
"#f1f1f1",
},
},
"purple": {
Label: "Purple",
Colors: []string{
"#674970",
"#2e2256",
"#ffffff",
"#2e2256",
"#ffffff",
"#f1f1f1",
},
},
"blue": {
Label: "Blue",
Colors: []string{
"#e9f2f9",
"#55c3dc",
"#ffffff",
"#22313f",
"#ffffff",
"#f1f1f1",
},
},
}
var _ = func() struct{} {
v := wpconfig.ThemeSupport{
CoreBlockPatterns: true,
WidgetsBlockEditor: true,
AutomaticFeedLinks: true,
TitleTag: true,
PostThumbnails: true,
Menus: true,
HTML5: []string{
"search-form",
"comment-form",
"comment-list",
"gallery",
"caption",
"script",
"style",
"navigation-widgets",
},
PostFormats: []string{
"aside",
"image",
"video",
"quote",
"link",
"gallery",
"status",
"audio",
"chat",
},
CustomLogo: wpconfig.CustomLogo{
Width: 248,
Height: 248,
FlexWidth: false,
FlexHeight: true,
HeaderText: "",
UnlinkHomepageLogo: false,
},
CustomizeSelectiveRefreshWidgets: true,
EditorStyle: true,
EditorStyles: true,
WpBlockStyles: true,
ResponsiveEmbeds: true,
CustomHeader: wpconfig.CustomHeader{
DefaultImage: "",
RandomDefault: false,
Width: 954,
Height: 1300,
FlexHeight: false,
FlexWidth: false,
DefaultTextColor: "333333",
HeaderText: true,
Uploads: true,
WpHeadCallback: "twentyfifteen_header_style",
AdminHeadCallback: "",
AdminPreviewCallback: "",
Video: false,
VideoActiveCallback: "is_front_page",
},
Widgets: true,
}
wpconfig.SetThemeSupport(ThemeName, v)
return struct{}{}
}()

View File

@ -1,63 +0,0 @@
package twentyfifteen
import (
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components"
"github.com/fthvgb1/wp-go/app/theme/wp/middleware"
"github.com/fthvgb1/wp-go/app/wpconfig"
"strings"
)
const ThemeName = "twentyfifteen"
func Hook(h *wp.Handle) {
wp.Run(h, configs)
}
func configs(h *wp.Handle) {
h.AddActionFilter(widgets.Search, func(h *wp.Handle, s string, args ...any) string {
return strings.ReplaceAll(s, `class="search-submit"`, `class="search-submit screen-reader-text"`)
})
wp.InitPipe(h)
middleware.CommonMiddleware(h)
setPaginationAndRender(h)
h.PushCacheGroupHeadScript(constraints.AllScene, "CalCustomBackGround", 10.005, CalCustomBackGround)
h.PushCacheGroupHeadScript(constraints.AllScene, "colorSchemeCss", 10.0056, colorSchemeCss)
h.CommonComponents()
components.WidgetArea(h)
h.PushRender(constraints.AllScene, wp.NewHandleFn(renderCustomHeader, 20.5, "renderCustomHeader"))
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 50.005, "wp.IndexRender"))
h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 50.005, "wp.DetailRender"))
h.PushRender(constraints.Detail, wp.NewHandleFn(postThumb, 60.005, "postThumb"))
h.PushDataHandler(constraints.Detail, wp.NewHandleFn(wp.Detail, 100.005, "wp.Detail"))
wp.PushIndexHandler(constraints.PipeData, h, wp.NewHandleFn(wp.Index, 100.005, "wp.Index"))
h.PushDataHandler(constraints.AllScene, wp.NewHandleFn(wp.PreCodeAndStats, 80.005, "wp.PreCodeAndStats"))
h.PushRender(constraints.AllScene, wp.NewHandleFn(wp.PreTemplate, 70.005, "wp.PreTemplate"))
}
func setPaginationAndRender(h *wp.Handle) {
h.PushHandler(constraints.PipeRender, constraints.Detail, wp.NewHandleFn(func(hh *wp.Handle) {
d := hh.GetDetailHandle()
d.CommentRender = plugins.CommentRender()
d.CommentPageEle = plugins.TwentyFifteenCommentPagination()
}, 150, "setPaginationAndRender"))
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(func(hh *wp.Handle) {
i := hh.GetIndexHandle()
i.SetPageEle(plugins.TwentyFifteenPagination())
}, 150, "setPaginationAndRender"))
}
func postThumb(h *wp.Handle) {
d := h.GetDetailHandle()
if d.Post.Thumbnail.Path != "" {
d.Post.Thumbnail = wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "post-thumbnail", "")
}
}
func renderCustomHeader(h *wp.Handle) {
h.SetData("customHeader", customHeader(h))
}

View File

@ -1,574 +0,0 @@
package twentyseventeen
import (
"fmt"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/number"
str "github.com/fthvgb1/wp-go/helper/strings"
)
func colorScheme(h *wp.Handle) (r string) {
if "custom" != wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light") {
return
}
s := str.NewBuilder()
hue := number.ToString(wpconfig.GetThemeModsVal[int64](ThemeName, "colorscheme_hue", 250))
reducedSaturation := fmt.Sprintf("%d%%", int(.8*50))
saturation := fmt.Sprintf("%d%%", 50)
css := str.Replace(customCss, map[string]string{
"' . $hue . '": hue,
"' . esc_attr( $hue ) . '": hue,
"' . $saturation . '": saturation,
"' . esc_attr( $saturation ) .' ": saturation,
"' . $reduced_saturation . '": reducedSaturation,
})
s.Sprintf(`<style type="text/css" id="custom-theme-colors">%s</style>`, css)
r = s.String()
return
}
var customCss = `
/**
* Twenty Seventeen: Color Patterns
*
* Colors are ordered from dark to light.
*/
.colors-custom a:hover,
.colors-custom a:active,
.colors-custom .entry-content a:focus,
.colors-custom .entry-content a:hover,
.colors-custom .entry-summary a:focus,
.colors-custom .entry-summary a:hover,
.colors-custom .comment-content a:focus,
.colors-custom .comment-content a:hover,
.colors-custom .widget a:focus,
.colors-custom .widget a:hover,
.colors-custom .site-footer .widget-area a:focus,
.colors-custom .site-footer .widget-area a:hover,
.colors-custom .posts-navigation a:focus,
.colors-custom .posts-navigation a:hover,
.colors-custom .comment-metadata a:focus,
.colors-custom .comment-metadata a:hover,
.colors-custom .comment-metadata a.comment-edit-link:focus,
.colors-custom .comment-metadata a.comment-edit-link:hover,
.colors-custom .comment-reply-link:focus,
.colors-custom .comment-reply-link:hover,
.colors-custom .widget_authors a:focus strong,
.colors-custom .widget_authors a:hover strong,
.colors-custom .entry-title a:focus,
.colors-custom .entry-title a:hover,
.colors-custom .entry-meta a:focus,
.colors-custom .entry-meta a:hover,
.colors-custom.blog .entry-meta a.post-edit-link:focus,
.colors-custom.blog .entry-meta a.post-edit-link:hover,
.colors-custom.archive .entry-meta a.post-edit-link:focus,
.colors-custom.archive .entry-meta a.post-edit-link:hover,
.colors-custom.search .entry-meta a.post-edit-link:focus,
.colors-custom.search .entry-meta a.post-edit-link:hover,
.colors-custom .page-links a:focus .page-number,
.colors-custom .page-links a:hover .page-number,
.colors-custom .entry-footer a:focus,
.colors-custom .entry-footer a:hover,
.colors-custom .entry-footer .cat-links a:focus,
.colors-custom .entry-footer .cat-links a:hover,
.colors-custom .entry-footer .tags-links a:focus,
.colors-custom .entry-footer .tags-links a:hover,
.colors-custom .post-navigation a:focus,
.colors-custom .post-navigation a:hover,
.colors-custom .pagination a:not(.prev):not(.next):focus,
.colors-custom .pagination a:not(.prev):not(.next):hover,
.colors-custom .comments-pagination a:not(.prev):not(.next):focus,
.colors-custom .comments-pagination a:not(.prev):not(.next):hover,
.colors-custom .logged-in-as a:focus,
.colors-custom .logged-in-as a:hover,
.colors-custom a:focus .nav-title,
.colors-custom a:hover .nav-title,
.colors-custom .edit-link a:focus,
.colors-custom .edit-link a:hover,
.colors-custom .site-info a:focus,
.colors-custom .site-info a:hover,
.colors-custom .widget .widget-title a:focus,
.colors-custom .widget .widget-title a:hover,
.colors-custom .widget ul li a:focus,
.colors-custom .widget ul li a:hover {
color: hsl( ' . $hue . ', ' . $saturation . ', 0% ); /* base: #000; */
}
.colors-custom .entry-content a,
.colors-custom .entry-summary a,
.colors-custom .comment-content a,
.colors-custom .widget a,
.colors-custom .site-footer .widget-area a,
.colors-custom .posts-navigation a,
.colors-custom .widget_authors a strong {
-webkit-box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 6% ); /* base: rgba(15, 15, 15, 1); */
box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 6% ); /* base: rgba(15, 15, 15, 1); */
}
.colors-custom button,
.colors-custom input[type="button"],
.colors-custom input[type="submit"],
.colors-custom .entry-footer .edit-link a.post-edit-link {
background-color: hsl( ' . $hue . ', ' . $saturation . ', 13% ); /* base: #222; */
}
.colors-custom input[type="text"]:focus,
.colors-custom input[type="email"]:focus,
.colors-custom input[type="url"]:focus,
.colors-custom input[type="password"]:focus,
.colors-custom input[type="search"]:focus,
.colors-custom input[type="number"]:focus,
.colors-custom input[type="tel"]:focus,
.colors-custom input[type="range"]:focus,
.colors-custom input[type="date"]:focus,
.colors-custom input[type="month"]:focus,
.colors-custom input[type="week"]:focus,
.colors-custom input[type="time"]:focus,
.colors-custom input[type="datetime"]:focus,
.colors-custom .colors-custom input[type="datetime-local"]:focus,
.colors-custom input[type="color"]:focus,
.colors-custom textarea:focus,
.colors-custom button.secondary,
.colors-custom input[type="reset"],
.colors-custom input[type="button"].secondary,
.colors-custom input[type="reset"].secondary,
.colors-custom input[type="submit"].secondary,
.colors-custom a,
.colors-custom .site-title,
.colors-custom .site-title a,
.colors-custom .navigation-top a,
.colors-custom .dropdown-toggle,
.colors-custom .menu-toggle,
.colors-custom .page .panel-content .entry-title,
.colors-custom .page-title,
.colors-custom.page:not(.twentyseventeen-front-page) .entry-title,
.colors-custom .page-links a .page-number,
.colors-custom .comment-metadata a.comment-edit-link,
.colors-custom .comment-reply-link .icon,
.colors-custom h2.widget-title,
.colors-custom mark,
.colors-custom .post-navigation a:focus .icon,
.colors-custom .post-navigation a:hover .icon,
.colors-custom .site-content .site-content-light,
.colors-custom .twentyseventeen-panel .recent-posts .entry-header .edit-link {
color: hsl( ' . $hue . ', ' . $saturation . ', 13% ); /* base: #222; */
}
.colors-custom .entry-content a:focus,
.colors-custom .entry-content a:hover,
.colors-custom .entry-summary a:focus,
.colors-custom .entry-summary a:hover,
.colors-custom .comment-content a:focus,
.colors-custom .comment-content a:hover,
.colors-custom .widget a:focus,
.colors-custom .widget a:hover,
.colors-custom .site-footer .widget-area a:focus,
.colors-custom .site-footer .widget-area a:hover,
.colors-custom .posts-navigation a:focus,
.colors-custom .posts-navigation a:hover,
.colors-custom .comment-metadata a:focus,
.colors-custom .comment-metadata a:hover,
.colors-custom .comment-metadata a.comment-edit-link:focus,
.colors-custom .comment-metadata a.comment-edit-link:hover,
.colors-custom .comment-reply-link:focus,
.colors-custom .comment-reply-link:hover,
.colors-custom .widget_authors a:focus strong,
.colors-custom .widget_authors a:hover strong,
.colors-custom .entry-title a:focus,
.colors-custom .entry-title a:hover,
.colors-custom .entry-meta a:focus,
.colors-custom .entry-meta a:hover,
.colors-custom.blog .entry-meta a.post-edit-link:focus,
.colors-custom.blog .entry-meta a.post-edit-link:hover,
.colors-custom.archive .entry-meta a.post-edit-link:focus,
.colors-custom.archive .entry-meta a.post-edit-link:hover,
.colors-custom.search .entry-meta a.post-edit-link:focus,
.colors-custom.search .entry-meta a.post-edit-link:hover,
.colors-custom .page-links a:focus .page-number,
.colors-custom .page-links a:hover .page-number,
.colors-custom .entry-footer .cat-links a:focus,
.colors-custom .entry-footer .cat-links a:hover,
.colors-custom .entry-footer .tags-links a:focus,
.colors-custom .entry-footer .tags-links a:hover,
.colors-custom .post-navigation a:focus,
.colors-custom .post-navigation a:hover,
.colors-custom .pagination a:not(.prev):not(.next):focus,
.colors-custom .pagination a:not(.prev):not(.next):hover,
.colors-custom .comments-pagination a:not(.prev):not(.next):focus,
.colors-custom .comments-pagination a:not(.prev):not(.next):hover,
.colors-custom .logged-in-as a:focus,
.colors-custom .logged-in-as a:hover,
.colors-custom a:focus .nav-title,
.colors-custom a:hover .nav-title,
.colors-custom .edit-link a:focus,
.colors-custom .edit-link a:hover,
.colors-custom .site-info a:focus,
.colors-custom .site-info a:hover,
.colors-custom .widget .widget-title a:focus,
.colors-custom .widget .widget-title a:hover,
.colors-custom .widget ul li a:focus,
.colors-custom .widget ul li a:hover {
-webkit-box-shadow: inset 0 0 0 hsl( ' . $hue . ', ' . $saturation . ', 13% ), 0 3px 0 hsl( ' . $hue . ', ' . $saturation . ', 13% );
box-shadow: inset 0 0 0 hsl( ' . $hue . ', ' . $saturation . ' , 13% ), 0 3px 0 hsl( ' . $hue . ', ' . $saturation . ', 13% );
}
body.colors-custom,
.colors-custom button,
.colors-custom input,
.colors-custom select,
.colors-custom textarea,
.colors-custom h3,
.colors-custom h4,
.colors-custom h6,
.colors-custom label,
.colors-custom .entry-title a,
.colors-custom.twentyseventeen-front-page .panel-content .recent-posts article,
.colors-custom .entry-footer .cat-links a,
.colors-custom .entry-footer .tags-links a,
.colors-custom .format-quote blockquote,
.colors-custom .nav-title,
.colors-custom .comment-body,
.colors-custom .site-content .wp-playlist-light .wp-playlist-current-item .wp-playlist-item-album {
color: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
}
.colors-custom .social-navigation a:hover,
.colors-custom .social-navigation a:focus {
background: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
}
.colors-custom input[type="text"]:focus,
.colors-custom input[type="email"]:focus,
.colors-custom input[type="url"]:focus,
.colors-custom input[type="password"]:focus,
.colors-custom input[type="search"]:focus,
.colors-custom input[type="number"]:focus,
.colors-custom input[type="tel"]:focus,
.colors-custom input[type="range"]:focus,
.colors-custom input[type="date"]:focus,
.colors-custom input[type="month"]:focus,
.colors-custom input[type="week"]:focus,
.colors-custom input[type="time"]:focus,
.colors-custom input[type="datetime"]:focus,
.colors-custom input[type="datetime-local"]:focus,
.colors-custom input[type="color"]:focus,
.colors-custom textarea:focus,
.bypostauthor > .comment-body > .comment-meta > .comment-author .avatar {
border-color: hsl( ' . $hue . ', ' . $reduced_saturation . ', 20% ); /* base: #333; */
}
.colors-custom h2,
.colors-custom blockquote,
.colors-custom input[type="text"],
.colors-custom input[type="email"],
.colors-custom input[type="url"],
.colors-custom input[type="password"],
.colors-custom input[type="search"],
.colors-custom input[type="number"],
.colors-custom input[type="tel"],
.colors-custom input[type="range"],
.colors-custom input[type="date"],
.colors-custom input[type="month"],
.colors-custom input[type="week"],
.colors-custom input[type="time"],
.colors-custom input[type="datetime"],
.colors-custom input[type="datetime-local"],
.colors-custom input[type="color"],
.colors-custom textarea,
.colors-custom .site-description,
.colors-custom .entry-content blockquote.alignleft,
.colors-custom .entry-content blockquote.alignright,
.colors-custom .colors-custom .taxonomy-description,
.colors-custom .site-info a,
.colors-custom .wp-caption,
.colors-custom .gallery-caption {
color: hsl( ' . $hue . ', ' . $saturation . ', 40% ); /* base: #666; */
}
.colors-custom abbr,
.colors-custom acronym {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 40% ); /* base: #666; */
}
.colors-custom h5,
.colors-custom .entry-meta,
.colors-custom .entry-meta a,
.colors-custom.blog .entry-meta a.post-edit-link,
.colors-custom.archive .entry-meta a.post-edit-link,
.colors-custom.search .entry-meta a.post-edit-link,
.colors-custom .nav-subtitle,
.colors-custom .comment-metadata,
.colors-custom .comment-metadata a,
.colors-custom .no-comments,
.colors-custom .comment-awaiting-moderation,
.colors-custom .page-numbers.current,
.colors-custom .page-links .page-number,
.colors-custom .navigation-top .current-menu-item > a,
.colors-custom .navigation-top .current_page_item > a,
.colors-custom .main-navigation a:hover,
.colors-custom .site-content .wp-playlist-light .wp-playlist-current-item .wp-playlist-item-artist {
color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */
}
.colors-custom :not( .mejs-button ) > button:hover,
.colors-custom :not( .mejs-button ) > button:focus,
.colors-custom input[type="button"]:hover,
.colors-custom input[type="button"]:focus,
.colors-custom input[type="submit"]:hover,
.colors-custom input[type="submit"]:focus,
.colors-custom .entry-footer .edit-link a.post-edit-link:hover,
.colors-custom .entry-footer .edit-link a.post-edit-link:focus,
.colors-custom .social-navigation a,
.colors-custom .prev.page-numbers:focus,
.colors-custom .prev.page-numbers:hover,
.colors-custom .next.page-numbers:focus,
.colors-custom .next.page-numbers:hover,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus {
background: hsl( ' . esc_attr( $hue ) . ', ' . esc_attr( $saturation ) . ', 46% ); /* base: #767676; */
}
.colors-custom button.secondary:hover,
.colors-custom button.secondary:focus,
.colors-custom input[type="reset"]:hover,
.colors-custom input[type="reset"]:focus,
.colors-custom input[type="button"].secondary:hover,
.colors-custom input[type="button"].secondary:focus,
.colors-custom input[type="reset"].secondary:hover,
.colors-custom input[type="reset"].secondary:focus,
.colors-custom input[type="submit"].secondary:hover,
.colors-custom input[type="submit"].secondary:focus,
.colors-custom hr {
background: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
}
.colors-custom input[type="text"],
.colors-custom input[type="email"],
.colors-custom input[type="url"],
.colors-custom input[type="password"],
.colors-custom input[type="search"],
.colors-custom input[type="number"],
.colors-custom input[type="tel"],
.colors-custom input[type="range"],
.colors-custom input[type="date"],
.colors-custom input[type="month"],
.colors-custom input[type="week"],
.colors-custom input[type="time"],
.colors-custom input[type="datetime"],
.colors-custom input[type="datetime-local"],
.colors-custom input[type="color"],
.colors-custom textarea,
.colors-custom select,
.colors-custom fieldset,
.colors-custom .widget .tagcloud a:hover,
.colors-custom .widget .tagcloud a:focus,
.colors-custom .widget.widget_tag_cloud a:hover,
.colors-custom .widget.widget_tag_cloud a:focus,
.colors-custom .wp_widget_tag_cloud a:hover,
.colors-custom .wp_widget_tag_cloud a:focus {
border-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
}
.colors-custom thead th {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
}
.colors-custom .entry-footer .cat-links .icon,
.colors-custom .entry-footer .tags-links .icon {
color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
}
.colors-custom button.secondary,
.colors-custom input[type="reset"],
.colors-custom input[type="button"].secondary,
.colors-custom input[type="reset"].secondary,
.colors-custom input[type="submit"].secondary,
.colors-custom .prev.page-numbers,
.colors-custom .next.page-numbers {
background-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
}
.colors-custom .widget .tagcloud a,
.colors-custom .widget.widget_tag_cloud a,
.colors-custom .wp_widget_tag_cloud a {
border-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
}
.colors-custom.twentyseventeen-front-page article:not(.has-post-thumbnail):not(:first-child),
.colors-custom .widget ul li {
border-top-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
}
.colors-custom .widget ul li {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 87% ); /* base: #ddd; */
}
.colors-custom pre,
.colors-custom mark,
.colors-custom ins {
background: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
}
.colors-custom .navigation-top,
.colors-custom .main-navigation > div > ul,
.colors-custom .pagination,
.colors-custom .comments-pagination,
.colors-custom .entry-footer,
.colors-custom .site-footer {
border-top-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
}
.colors-custom .navigation-top,
.colors-custom .main-navigation li,
.colors-custom .entry-footer,
.colors-custom .single-featured-image-header,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item,
.colors-custom tr {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
}
.colors-custom .site-content .wp-playlist-light {
border-color: hsl( ' . $hue . ', ' . $saturation . ', 93% ); /* base: #eee; */
}
.colors-custom .site-header,
.colors-custom .single-featured-image-header {
background-color: hsl( ' . $hue . ', ' . $saturation . ', 98% ); /* base: #fafafa; */
}
.colors-custom button,
.colors-custom input[type="button"],
.colors-custom input[type="submit"],
.colors-custom .entry-footer .edit-link a.post-edit-link,
.colors-custom .social-navigation a,
.colors-custom .site-content .wp-playlist-light a.wp-playlist-caption:hover,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover a,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus a,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:hover,
.colors-custom .site-content .wp-playlist-light .wp-playlist-item:focus,
.colors-custom .prev.page-numbers:focus,
.colors-custom .prev.page-numbers:hover,
.colors-custom .next.page-numbers:focus,
.colors-custom .next.page-numbers:hover,
.colors-custom.has-header-image .site-title,
.colors-custom.has-header-video .site-title,
.colors-custom.has-header-image .site-title a,
.colors-custom.has-header-video .site-title a,
.colors-custom.has-header-image .site-description,
.colors-custom.has-header-video .site-description {
color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
}
body.colors-custom,
.colors-custom .navigation-top,
.colors-custom .main-navigation ul {
background: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
}
.colors-custom .widget ul li a,
.colors-custom .site-footer .widget-area ul li a {
-webkit-box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: rgba(255, 255, 255, 1); */
box-shadow: inset 0 -1px 0 hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: rgba(255, 255, 255, 1); */
}
.colors-custom .menu-toggle,
.colors-custom .menu-toggle:hover,
.colors-custom .menu-toggle:focus,
.colors-custom .menu .dropdown-toggle,
.colors-custom .menu-scroll-down,
.colors-custom .menu-scroll-down:hover,
.colors-custom .menu-scroll-down:focus {
background-color: transparent;
}
.colors-custom .widget .tagcloud a,
.colors-custom .widget .tagcloud a:focus,
.colors-custom .widget .tagcloud a:hover,
.colors-custom .widget.widget_tag_cloud a,
.colors-custom .widget.widget_tag_cloud a:focus,
.colors-custom .widget.widget_tag_cloud a:hover,
.colors-custom .wp_widget_tag_cloud a,
.colors-custom .wp_widget_tag_cloud a:focus,
.colors-custom .wp_widget_tag_cloud a:hover,
.colors-custom .entry-footer .edit-link a.post-edit-link:focus,
.colors-custom .entry-footer .edit-link a.post-edit-link:hover {
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
/* Reset non-customizable hover styling for links */
.colors-custom .entry-content a:hover,
.colors-custom .entry-content a:focus,
.colors-custom .entry-summary a:hover,
.colors-custom .entry-summary a:focus,
.colors-custom .comment-content a:focus,
.colors-custom .comment-content a:hover,
.colors-custom .widget a:hover,
.colors-custom .widget a:focus,
.colors-custom .site-footer .widget-area a:hover,
.colors-custom .site-footer .widget-area a:focus,
.colors-custom .posts-navigation a:hover,
.colors-custom .posts-navigation a:focus,
.colors-custom .widget_authors a:hover strong,
.colors-custom .widget_authors a:focus strong {
-webkit-box-shadow: inset 0 0 0 rgba(0, 0, 0, 0), 0 3px 0 rgba(0, 0, 0, 1);
box-shadow: inset 0 0 0 rgba(0, 0, 0, 0), 0 3px 0 rgba(0, 0, 0, 1);
}
.colors-custom .gallery-item a,
.colors-custom .gallery-item a:hover,
.colors-custom .gallery-item a:focus {
-webkit-box-shadow: none;
box-shadow: none;
}
@media screen and (min-width: 48em) {
.colors-custom .nav-links .nav-previous .nav-title .icon,
.colors-custom .nav-links .nav-next .nav-title .icon {
color: hsl( ' . $hue . ', ' . $saturation . ', 20% ); /* base: #222; */
}
.colors-custom .main-navigation li li:hover,
.colors-custom .main-navigation li li.focus {
background: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */
}
.colors-custom .navigation-top .menu-scroll-down {
color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */;
}
.colors-custom abbr[title] {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 46% ); /* base: #767676; */;
}
.colors-custom .main-navigation ul ul {
border-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
background: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
}
.colors-custom .main-navigation ul li.menu-item-has-children:before,
.colors-custom .main-navigation ul li.page_item_has_children:before {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 73% ); /* base: #bbb; */
}
.colors-custom .main-navigation ul li.menu-item-has-children:after,
.colors-custom .main-navigation ul li.page_item_has_children:after {
border-bottom-color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
}
.colors-custom .main-navigation li li.focus > a,
.colors-custom .main-navigation li li:focus > a,
.colors-custom .main-navigation li li:hover > a,
.colors-custom .main-navigation li li a:hover,
.colors-custom .main-navigation li li a:focus,
.colors-custom .main-navigation li li.current_page_item a:hover,
.colors-custom .main-navigation li li.current-menu-item a:hover,
.colors-custom .main-navigation li li.current_page_item a:focus,
.colors-custom .main-navigation li li.current-menu-item a:focus {
color: hsl( ' . $hue . ', ' . $saturation . ', 100% ); /* base: #fff; */
}
}
`

View File

@ -1,48 +0,0 @@
package twentyseventeen
import (
"fmt"
"github.com/fthvgb1/wp-go/app/theme/wp"
)
func customHeader(h *wp.Handle) (r string) {
themeMods := h.CommonThemeMods()
headerTextColor := themeMods.HeaderTextcolor
if headerTextColor == "" || headerTextColor == themeMods.ThemeSupport.CustomHeader.DefaultTextColor {
return
}
css := `
.site-title,
.site-description {
position: absolute;
clip: rect(1px, 1px, 1px, 1px);
}`
if headerTextColor != "blank" {
css = fmt.Sprintf(customHeaderCss, headerTextColor)
}
r = fmt.Sprintf(`<style id="twentyseventeen-custom-header-styles" type="text/css">%s</style>`, css)
return
}
var customHeaderCss = `
.site-title a,
.colors-dark .site-title a,
.colors-custom .site-title a,
body.has-header-image .site-title a,
body.has-header-video .site-title a,
body.has-header-image.colors-dark .site-title a,
body.has-header-video.colors-dark .site-title a,
body.has-header-image.colors-custom .site-title a,
body.has-header-video.colors-custom .site-title a,
.site-description,
.colors-dark .site-description,
.colors-custom .site-description,
body.has-header-image .site-description,
body.has-header-video .site-description,
body.has-header-image.colors-dark .site-description,
body.has-header-video.colors-dark .site-description,
body.has-header-image.colors-custom .site-description,
body.has-header-video.colors-custom .site-description {
color: #%s;
}
`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,16 +0,0 @@
{{template "layout/base" .}}
{{define "content"}}
<div class="site-content-contain">
<div id="content" class="site-content">
<div class="wrap">
<div id="primary" class="content-area">
<main id="main" class="site-main">
{{template "layout/empty"}}
</main>
</div>
</div>
</div>
</div>
{{end}}

View File

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

View File

@ -1,43 +0,0 @@
package twentyseventeen
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/wpconfig"
)
func pushScripts(h *wp.Handle) {
h.PushCacheGroupHeadScript(constraints.AllScene, "{theme}.head", 30, func(h *wp.Handle) string {
head := headScript
if "dark" == wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light") {
head = fmt.Sprintf("%s\n%s", headScript, ` <link rel="stylesheet" id="twentyseventeen-colors-dark-css" href="/wp-content/themes/twentyseventeen/assets/css/colors-dark.css?ver=20191025" media="all">`)
}
return head
})
h.PushGroupFooterScript(constraints.AllScene, "{theme}.footer", 20.005, footerScript)
}
var headScript = `<link rel='stylesheet' id='twentyseventeen-style-css' href='/wp-content/themes/twentyseventeen/style.css?ver=20221101' media='all' />
<link rel='stylesheet' id='twentyseventeen-block-style-css' href='/wp-content/themes/twentyseventeen/assets/css/blocks.css?ver=20220912' media='all' />
<!--[if lt IE 9]>
<link rel='stylesheet' id='twentyseventeen-ie8-css' href='/wp-content/themes/twentyseventeen/assets/css/ie8.css?ver=20161202' media='all' />
<![endif]-->
<!--[if lt IE 9]>
<script src='/wp-content/themes/twentyseventeen/assets/js/html5.js?ver=20161020' id='html5-js'></script>
<![endif]-->
<script src='/wp-includes/js/jquery/jquery.min.js?ver=3.6.0' id='jquery-core-js'></script>
<script src='/wp-includes/js/jquery/jquery-migrate.min.js?ver=3.3.2' id='jquery-migrate-js'></script>
<link rel="https://api.w.org/" href="/wp-json/" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="/xmlrpc.php?rsd" />
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="/wp-includes/wlwmanifest.xml" />
<meta name="generator" content="WordPress 6.1.1" />
<style>.recentcomments a{display:inline !important;padding:0 !important;margin:0 !important;}</style>`
var footerScript = `<script id="twentyseventeen-skip-link-focus-fix-js-extra">
var twentyseventeenScreenReaderText = {"quote":"<svg class=\"icon icon-quote-right\" aria-hidden=\"true\" role=\"img\"> <use href=\"#icon-quote-right\" xlink:href=\"#icon-quote-right\"><\/use> <\/svg>"};
</script>
<script src="/wp-content/themes/twentyseventeen/assets/js/skip-link-focus-fix.js?ver=20161114" id="twentyseventeen-skip-link-focus-fix-js"></script>
<script src="/wp-content/themes/twentyseventeen/assets/js/global.js?ver=20211130" id="twentyseventeen-global-js"></script>
<script src="/wp-content/themes/twentyseventeen/assets/js/jquery.scrollTo.js?ver=2.1.3" id="jquery-scrollto-js"></script>`

View File

@ -1,212 +0,0 @@
package twentyseventeen
import "github.com/fthvgb1/wp-go/app/wpconfig"
type themeSupport struct {
CustomLineHeight bool `json:"custom-line-height"`
StarterContent StarterContent `json:"starter-content"`
}
type Widgets struct {
Sidebar1 []string `json:"sidebar-1"`
Sidebar2 []string `json:"sidebar-2"`
Sidebar3 []string `json:"sidebar-3"`
}
type About struct {
Thumbnail string `json:"thumbnail"`
}
type Contact struct {
Thumbnail string `json:"thumbnail"`
}
type Blog struct {
Thumbnail string `json:"thumbnail"`
}
type HomepageSection struct {
Thumbnail string `json:"thumbnail"`
}
type ImageEspresso struct {
PostTitle string `json:"post_title"`
File string `json:"file"`
}
type ImageSandwich struct {
PostTitle string `json:"post_title"`
File string `json:"file"`
}
type ImageCoffee struct {
PostTitle string `json:"post_title"`
File string `json:"file"`
}
type Attachments struct {
ImageEspresso ImageEspresso `json:"image-espresso"`
ImageSandwich ImageSandwich `json:"image-sandwich"`
ImageCoffee ImageCoffee `json:"image-coffee"`
}
type Image struct {
PostTitle string `json:"post_title"`
File string `json:"file"`
}
type Options struct {
ShowOnFront string `json:"show_on_front"`
PageOnFront string `json:"page_on_front"`
PageForPosts string `json:"page_for_posts"`
}
type ThemeMods struct {
Panel1 string `json:"panel_1"`
Panel2 string `json:"panel_2"`
Panel3 string `json:"panel_3"`
Panel4 string `json:"panel_4"`
}
type Menus struct {
Name string `json:"name"`
Items []string `json:"items"`
}
type NavMenus struct {
Top Menus `json:"top"`
Social Menus `json:"social"`
}
type StarterContent struct {
Widgets Widgets `json:"widgets"`
Posts map[string]map[string]string `json:"posts"`
Attachments map[string]Image `json:"attachments"`
Options Options `json:"options"`
ThemeMods ThemeMods `json:"theme_mods"`
NavMenus NavMenus `json:"nav_menus"`
}
var themesupport = themeSupport{
CustomLineHeight: true,
StarterContent: StarterContent{
Widgets: Widgets{
Sidebar1: []string{"text_business_info", "search", "text_about"},
Sidebar2: []string{"text_business_info"},
Sidebar3: []string{"text_about", "search"},
},
Posts: map[string]map[string]string{
"0": {
"home": "home",
},
"about": {
"thumbnail": "{{image-sandwich}}",
},
"contact": {
"thumbnail": "{{image-espresso}}",
},
"blog": {
"thumbnail": "{{image-coffee}}",
},
"homepage-section": {
"thumbnail": "{{image-espresso}}",
},
},
Attachments: map[string]Image{
"image-espresso": {
PostTitle: "浓缩咖啡",
File: "assets/images/espresso.jpg",
},
"image-sandwich": {
PostTitle: "三明治",
File: "assets/images/sandwich.jpg",
},
"image-coffee": {
PostTitle: "咖啡",
File: "assets/images/coffee.jpg",
},
},
Options: Options{
ShowOnFront: "page",
PageOnFront: "{{home}}",
PageForPosts: "{{blog}}",
},
ThemeMods: ThemeMods{
Panel1: "{{homepage-section}}",
Panel2: "{{about}}",
Panel3: "{{blog}}",
Panel4: "{{contact}}",
},
NavMenus: NavMenus{
Top: Menus{
Name: "顶部菜单",
Items: []string{
"link_home",
"page_about",
"page_blog",
"page_contact",
},
},
Social: Menus{
Name: "社交网络链接菜单",
Items: []string{
"link_yelp",
"link_facebook",
"link_twitter",
"link_instagram",
"link_email",
},
},
},
},
}
var _ = func() struct{} {
v := wpconfig.ThemeSupport{
CoreBlockPatterns: true,
WidgetsBlockEditor: true,
AutomaticFeedLinks: true,
TitleTag: true,
PostThumbnails: true,
Menus: true,
HTML5: []string{
"comment-form",
"comment-list",
"gallery",
"caption",
"script",
"style",
"navigation-widgets",
},
PostFormats: []string{
"aside",
"image",
"video",
"quote",
"link",
"gallery",
"audio",
},
CustomLogo: wpconfig.CustomLogo{
Width: 250,
Height: 250,
FlexWidth: true,
FlexHeight: false,
HeaderText: "",
UnlinkHomepageLogo: false,
},
CustomizeSelectiveRefreshWidgets: true,
EditorStyle: true,
EditorStyles: true,
WpBlockStyles: true,
ResponsiveEmbeds: true,
CustomHeader: wpconfig.CustomHeader{
DefaultImage: "http://wp.test/wp-content/themes/twentyseventeen/assets/images/header.jpg",
RandomDefault: false,
Width: 2000,
Height: 1200,
FlexHeight: true,
FlexWidth: false,
DefaultTextColor: "",
HeaderText: true,
Uploads: true,
WpHeadCallback: "twentyseventeen_header_style",
AdminHeadCallback: "",
AdminPreviewCallback: "",
Video: true,
VideoActiveCallback: "is_front_page",
},
Widgets: true,
}
wpconfig.SetThemeSupport(ThemeName, v)
return struct{}{}
}()

View File

@ -1,229 +0,0 @@
package twentyseventeen
import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components"
"github.com/fthvgb1/wp-go/app/theme/wp/middleware"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/plugin/pagination"
"strings"
)
const ThemeName = "twentyseventeen"
var paginate = func() plugins.PageEle {
p := plugins.TwentyFifteenPagination()
p.PrevEle = `<a class="prev page-numbers" href="%s"><svg class="icon icon-arrow-left" aria-hidden="true" role="img"> <use href="#icon-arrow-left" xlink:href="#icon-arrow-left"></use> </svg>
<span class="screen-reader-text">上一页</span></a>`
p.NextEle = strings.Replace(p.NextEle, "下一页", `<span class="screen-reader-text">下一页</span>
<svg class="icon icon-arrow-right" aria-hidden="true" role="img"> <use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use>
</svg>`, 1)
commentPageEle = plugins.PaginationNav{
Currents: p.Current,
Prevs: p.Prev,
Nexts: p.Next,
Dotss: p.Dots,
Middles: p.Middle,
Urlss: plugins.TwentyFifteenCommentPagination().Urls,
}
return p
}()
var commentPageEle pagination.Render
func Hook(h *wp.Handle) {
wp.Run(h, configs)
}
func configs(h *wp.Handle) {
wp.InitPipe(h)
middleware.CommonMiddleware(h)
h.AddActionFilter("bodyClass", calClass)
h.PushCacheGroupHeadScript(constraints.AllScene, "colorScheme-customHeader", 10, colorScheme, customHeader)
components.WidgetArea(h)
pushScripts(h)
h.PushRender(constraints.AllStats, wp.NewHandleFn(calCustomHeader, 10.005, "calCustomHeader"))
wp.SetComponentsArgs(widgets.Widget, map[string]string{
"{$before_widget}": `<section id="%s" class="%s">`,
"{$after_widget}": `</section>`,
})
h.PushRender(constraints.AllStats,
wp.NewHandleFn(wp.PreTemplate, 70.005, "wp.PreTemplate"),
wp.NewHandleFn(errorsHandle, 80.005, "errorsHandle"),
)
videoHeader(h)
h.SetData("colophon", colophon)
setPaginationAndRender(h)
h.CommonComponents()
h.PushPostPlugin(postThumbnail)
wp.SetComponentsArgsForMap(widgets.Search, "{$form}", searchForm)
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 10.005, "wp.IndexRender"))
h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 10.005, "wp.DetailRender"))
h.PushDataHandler(constraints.Detail, wp.NewHandleFn(wp.Detail, 100.005, "wp.Detail"), wp.NewHandleFn(postThumb, 90.005, "{theme}.postThumb"))
wp.PushIndexHandler(constraints.PipeData, h, wp.NewHandleFn(wp.Index, 100.005, "wp.Index"))
h.PushDataHandler(constraints.AllScene, wp.NewHandleFn(wp.PreCodeAndStats, 90.005, "wp.PreCodeAndStats"))
}
func setPaginationAndRender(h *wp.Handle) {
h.PushHandler(constraints.PipeRender, constraints.Detail, wp.NewHandleFn(func(hh *wp.Handle) {
d := hh.GetDetailHandle()
d.CommentRender = commentFormat
d.CommentPageEle = commentPageEle
d.CommentStep = 2
}, 150, "setPaginationAndRender"))
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(func(hh *wp.Handle) {
i := hh.GetIndexHandle()
i.SetPageEle(paginate)
}, 150, "setPaginationAndRender"))
}
var searchForm = `<form role="search" method="get" class="search-form" action="/">
<label for="search-form-1">
<span class="screen-reader-text">{$label}</span>
</label>
<input type="search" id="search-form-1" class="search-field" placeholder="{$placeholder}…" value="{$value}" name="s">
<button type="submit" class="search-submit">
<svg class="icon icon-search" aria-hidden="true" role="img"> <use href="#icon-search" xlink:href="#icon-search"></use> </svg>
<span class="screen-reader-text">{$button}</span>
</button>
</form>`
func errorsHandle(h *wp.Handle) {
switch h.Stats {
case constraints.Error404, constraints.InternalErr, constraints.ParamError:
logs.IfError(h.Err(), "报错:")
h.SetTempl("twentyseventeen/posts/error.gohtml")
}
}
func postThumb(h *wp.Handle) {
d := h.GetDetailHandle()
if d.Post.Thumbnail.Path != "" {
img := wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "full", "", "thumbnail", "post-thumbnail")
img.Sizes = "100vw"
img.Srcset = fmt.Sprintf("%s %dw, %s", img.Path, img.Width, img.Srcset)
d.Post.Thumbnail = img
}
}
var commentFormat = comment{}
type comment struct {
plugins.CommonCommentFormat
}
var commentLi = plugins.CommonLi()
var respondFn = plugins.Responds(respondStr)
func (c comment) FormatLi(_ context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
return plugins.FormatLi(commentLi, m, respondFn, depth, maxDepth, page, isTls, isThreadComments, eo, parent)
}
var colophon = `<footer id="colophon" class="site-footer">
<div class="wrap">
<div class="site-info">
<a href="https://github.com/fthvgb1/wp-go" class="imprint">自豪地采用 wp-go</a>
</div>
</div>
</footer>`
var respondStr = `<a rel="nofollow" class="comment-reply-link"
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond"
data-replyto="回复给{{CommentAuthor}}"
aria-label="回复给{{CommentAuthor}}"><svg class="icon icon-mail-reply" aria-hidden="true" role="img"> <use href="#icon-mail-reply" xlink:href="#icon-mail-reply"></use> </svg>回复</a>`
func postThumbnail(h *wp.Handle, posts *models.Posts) {
if posts.Thumbnail.Path != "" {
posts.Thumbnail.Sizes = "(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px"
if h.Scene() == constraints.Detail {
posts.Thumbnail.Sizes = "100vw"
}
}
}
var header = reload.Vars(models.PostThumbnail{}, "twentyseventeen-headerImage")
func calCustomHeader(h *wp.Handle) {
h.SetData("HeaderImage", getHeaderImage(h))
}
func getHeaderImage(h *wp.Handle) (r models.PostThumbnail) {
img := header.Load()
if img.Path != "" {
return img
}
image, rand := h.GetCustomHeaderImg()
if image.Path != "" {
r = image
r.Sizes = "100vw"
if !rand {
header.Store(r)
}
return
}
r.Path = helper.CutUrlHost(h.CommonThemeMods().ThemeSupport.CustomHeader.DefaultImage)
r.Width = 2000
r.Height = 1200
header.Store(r)
return
}
func calClass(h *wp.Handle, s string, _ ...any) string {
class := strings.Split(s, " ")
themeMods := h.CommonThemeMods()
u := wpconfig.GetThemeModsVal(ThemeName, "header_image", themeMods.ThemeSupport.CustomHeader.DefaultImage)
if u != "" && u != "remove-header" {
class = append(class, "has-header-image")
}
if len(themeMods.SidebarsWidgets.Data.Sidebar1) > 0 {
class = append(class, "has-sidebar")
}
if themeMods.HeaderTextcolor == "blank" {
class = append(class, "title-tagline-hidden")
}
class = append(class, "hfeed")
class = append(class, str.Join("colors-", wpconfig.GetThemeModsVal(ThemeName, "colorscheme", "light")))
if h.Scene() == constraints.Archive {
if "one-column" == wpconfig.GetThemeModsVal(ThemeName, "page_layout", "") {
class = append(class, "page-one-column")
} else {
class = append(class, "page-two-column")
}
}
return strings.Join(class, " ")
}
func videoHeader(h *wp.Handle) {
h.AddActionFilter("videoSetting", videoPlay)
wp.CustomVideo(h, constraints.Home)
}
func videoPlay(h *wp.Handle, _ string, a ...any) string {
if len(a) < 1 {
return ""
}
v, ok := a[0].(*wp.VideoSetting)
if !ok {
return ""
}
img := getHeaderImage(h)
v.Width = img.Width
v.Height = img.Height
v.PosterUrl = img.Path
v.L10n.Play = `<span class="screen-reader-text">播放背景视频</span><svg class="icon icon-play" aria-hidden="true" role="img"> <use href="#icon-play" xlink:href="#icon-play"></use> </svg>`
v.L10n.Pause = `<span class="screen-reader-text">暂停背景视频</span><svg class="icon icon-pause" aria-hidden="true" role="img"> <use href="#icon-pause" xlink:href="#icon-pause"></use> </svg>`
return ""
}

View File

@ -1,131 +0,0 @@
package wp
import (
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"strconv"
"strings"
)
func bodyClass(h *Handle) func() string {
return func() string {
return h.BodyClass()
}
}
func (h *Handle) BodyClass() string {
var class []string
if constraints.Ok != h.Stats {
class = append(class, "error404")
}
switch h.scene {
case constraints.Home:
class = append(class, "home", "blog")
case constraints.Archive:
class = append(class, "archive", "date")
case constraints.Search:
s := "search-no-results"
if len(h.GetIndexHandle().Posts) > 0 {
s = "search-results"
}
class = append(class, "search", s)
case constraints.Category, constraints.Tag:
class = append(class, "archive", "category")
cat := h.GetIndexHandle().Param.Category
if cat == "" {
break
}
_, cate := slice.SearchFirst(cache.CategoriesTags(h.C, h.scene), func(my models.TermsMy) bool {
return my.Name == cat
})
if cate.Slug[0] != '%' {
class = append(class, str.Join("category-", cate.Slug))
}
class = append(class, str.Join("category-", number.IntToString(cate.Terms.TermId)))
case constraints.Author:
class = append(class, "archive", "author")
author := h.GetIndexHandle().Param.Author
user, _ := cache.GetUserByName(h.C, author)
class = append(class, str.Join("author-", number.IntToString(user.Id)))
if user.DisplayName[0] != '%' {
class = append(class, str.Join("author-", user.DisplayName))
}
case constraints.Detail:
class = append(class, "post-template-default", "single", "single-post")
class = append(class, str.Join("postid-", number.IntToString(h.GetDetailHandle().Post.Id)))
if len(h.themeMods.ThemeSupport.PostFormats) > 0 {
class = append(class, "single-format-standard")
}
}
if wpconfig.IsCustomBackground(h.theme) {
class = append(class, "custom-background")
}
if h.themeMods.CustomLogo > 0 || str.ToInteger(wpconfig.GetOption("site_logo"), 0) > 0 {
class = append(class, "wp-custom-logo")
}
if h.themeMods.ThemeSupport.ResponsiveEmbeds {
class = append(class, "wp-embed-responsive")
}
return h.DoActionFilter("bodyClass", strings.Join(class, " "))
}
func postClass(h *Handle) func(posts models.Posts) string {
return func(posts models.Posts) string {
return h.PostClass(posts)
}
}
func (h *Handle) PostClass(posts models.Posts) string {
var class []string
class = append(class, fmt.Sprintf("post-%d", posts.Id), posts.PostType,
str.Join("type-", posts.PostType), str.Join("status-", posts.PostStatus),
"hentry", "format-standard")
if h.CommonThemeMods().ThemeSupport.PostThumbnails && posts.Thumbnail.Path != "" {
class = append(class, "has-post-thumbnail")
}
if posts.PostPassword != "" {
if h.GetPassword() != posts.PostPassword {
class = append(class, "post-password-required")
} else {
class = append(class, "post-password-projected")
}
}
if h.scene == constraints.Home && h.IsStick(posts.Id) {
class = append(class, "sticky")
}
for _, id := range posts.TermIds {
term, ok := wpconfig.GetTermMy(id)
if !ok || term.Slug == "" {
continue
}
class = append(class, TermClass(term))
}
return h.DoActionFilter("postClass", strings.Join(class, " "))
}
func TermClass(term models.TermsMy) string {
termClass := term.Slug
if strings.Contains(term.Slug, "%") {
termClass = strconv.FormatUint(term.TermTaxonomy.TermId, 10)
}
switch term.Taxonomy {
case "post_tag":
return str.Join("tag-", termClass)
case "post_format":
return fmt.Sprintf("format-%s", strings.ReplaceAll(term.Slug, "post-format-", ""))
}
return str.Join(term.Taxonomy, "-", termClass)
}

View File

@ -1,126 +0,0 @@
package wp
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"time"
)
func RenderComment(ctx context.Context, page int, render plugins.CommentHtml, ids []uint64, timeout time.Duration, isTLS bool) (string, error) {
ca, _ := cachemanager.GetMapCache[uint64, models.Comments]("postCommentData")
children, _ := cachemanager.GetMapCache[uint64, []uint64]("commentChildren")
h := CommentHandle{
maxDepth: str.ToInteger(wpconfig.GetOption("thread_comments_depth"), 5),
depth: 1,
isTls: isTLS,
html: render,
order: wpconfig.GetOption("comment_order"),
ca: ca,
children: children,
threadComments: wpconfig.GetOption("thread_comments") == "1",
page: page,
}
return h.formatComments(ctx, ids, timeout)
}
type CommentHandle struct {
maxDepth int
depth int
isTls bool
html plugins.CommentHtml
order string
page int
ca *cache.MapCache[uint64, models.Comments]
children *cache.MapCache[uint64, []uint64]
threadComments bool
}
func (c CommentHandle) findGrandchildComments(ctx context.Context, timeout time.Duration, comments []models.Comments) ([]models.Comments, error) {
parentIds := slice.Map(comments, func(t models.Comments) uint64 {
return t.CommentId
})
children, err := c.children.GetCacheBatch(ctx, parentIds, timeout)
if err != nil {
return nil, err
}
rr := slice.FilterAndMap(children, func(t []uint64) ([]uint64, bool) {
return t, len(t) > 0
})
if len(rr) < 1 {
return comments, nil
}
ids := slice.Decompress(rr)
r, err := c.ca.GetCacheBatch(ctx, ids, timeout)
if err != nil {
return nil, err
}
rrr, err := c.findGrandchildComments(ctx, timeout, r)
if err != nil {
return nil, err
}
comments = append(comments, rrr...)
slice.Sort(comments, func(i, j models.Comments) bool {
return c.html.FloorOrder(i, j)
})
return comments, nil
}
func (c CommentHandle) formatComments(ctx context.Context, ids []uint64, timeout time.Duration) (html string, err error) {
comments, err := c.ca.GetCacheBatch(ctx, ids, timeout)
if err != nil {
return "", err
}
if c.depth > 1 && c.depth < c.maxDepth {
slice.Sort(comments, func(i, j models.Comments) bool {
return c.html.FloorOrder(i, j)
})
}
fixChildren := false
if c.depth >= c.maxDepth {
comments, err = c.findGrandchildComments(ctx, timeout, comments)
if err != nil {
return "", err
}
fixChildren = true
}
s := str.NewBuilder()
for i, comment := range comments {
eo := "even"
if (i+1)%2 == 0 {
eo = "odd"
}
parent := ""
fl := false
var children []uint64
if !fixChildren {
children, err = c.children.GetCache(ctx, comment.CommentId, timeout)
if err != nil {
return "", err
}
}
if c.threadComments && len(children) > 0 && c.depth < c.maxDepth+1 {
parent = "parent"
fl = true
}
s.WriteString(c.html.FormatLi(ctx, comment, c.depth, c.maxDepth, c.page, c.isTls, c.threadComments, eo, parent))
if fl {
c.depth++
ss, err := c.formatComments(ctx, children, timeout)
if err != nil {
return "", err
}
s.WriteString(`<ol class="children">`, ss, `</ol>`)
c.depth--
}
s.WriteString("</li><!-- #comment-## -->")
}
html = s.String()
return
}

View File

@ -1,256 +0,0 @@
package wp
import (
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
"strings"
)
var handleComponents = safety.NewMap[string, map[string][]Components[string]]()
var handleComponentHook = safety.NewMap[string, map[string][]func(Components[string]) (Components[string], bool)]()
var componentsArgs = safety.NewMap[string, any]()
var componentFilterFns = safety.NewMap[string, []func(*Handle, string, ...any) string]()
func (h *Handle) DeleteComponents(scene, componentKey, componentName string) {
v, ok := handleComponentHook.Load(scene)
if !ok {
v = make(map[string][]func(Components[string]) (Components[string], bool))
}
v[componentKey] = append(v[componentKey], func(c Components[string]) (Components[string], bool) {
return c, c.Name != componentName
})
handleComponentHook.Store(scene, v)
}
func (h *Handle) ReplaceComponents(scene, componentKey, componentName string, components Components[string]) {
v, ok := handleComponentHook.Load(scene)
if !ok {
v = make(map[string][]func(Components[string]) (Components[string], bool))
}
v[componentKey] = append(v[componentKey], func(c Components[string]) (Components[string], bool) {
if c.Name == componentName {
c = components
}
return c, true
})
handleComponentHook.Store(scene, v)
}
func (h *Handle) PushComponentHooks(scene, componentKey string, fn func(Components[string]) (Components[string], bool)) {
v, ok := handleComponentHook.Load(scene)
if !ok {
v = make(map[string][]func(Components[string]) (Components[string], bool))
}
v[componentKey] = append(v[componentKey], fn)
handleComponentHook.Store(scene, v)
}
var GetAndHookComponents = reload.BuildMapFnWithAnyParams[string]("calComponents", HookComponent)
func HookComponent(a ...any) []Components[string] {
componentKey := a[0].(string)
scene := a[1].(string)
mut := reload.GetGlobeMutex()
mut.Lock()
defer mut.Unlock()
components := GetComponents(scene, componentKey)
r := slice.FilterAndMap(components, func(component Components[string]) (Components[string], bool) {
keyHooks, ok := handleComponentHook.Load(scene)
if !ok {
return component, true
}
hooks := keyHooks[componentKey]
for _, fn := range hooks {
hookedComponent, ok := fn(component)
if !ok { // DeleteComponents fn
return hookedComponent, false
}
component = hookedComponent // ReplaceComponents fn
}
return component, true
})
slice.SimpleSort(r, slice.DESC, func(t Components[string]) float64 {
return t.Order
})
return r
}
func GetComponents(scene, key string) (r []Components[string]) {
sceneComponents, _ := handleComponents.Load(scene)
allSceneComponents, _ := handleComponents.Load(constraints.AllScene)
r = append(sceneComponents[key], allSceneComponents[key]...)
return
}
var CacheComponent = reload.BuildMapFnWithAnyParams[string]("cacheComponents", cacheComponentFn)
func cacheComponentFn(a ...any) string {
return a[0].(Components[string]).Fn(a[1].(*Handle))
}
func CalComponent(h *Handle) func(string) string {
return func(componentKey string) string {
cacheKey := str.Join("get-hook-Components-", h.scene, "-", componentKey)
hookedComponents := GetAndHookComponents(cacheKey, componentKey, h.scene)
var s = make([]string, 0, len(hookedComponents))
for _, component := range hookedComponents {
if component.Val != "" {
s = append(s, component.Val)
continue
}
if component.Fn != nil {
v := ""
if component.Cached {
key := str.Join(h.scene, "-", componentKey, "-", component.Name)
v = CacheComponent(key, component, h)
} else {
v = component.Fn(h)
}
if v != "" {
s = append(s, v)
}
}
}
return strings.Join(s, "\n")
}
}
func (h *Handle) PushComponents(scene, componentType string, components ...Components[string]) {
c, ok := handleComponents.Load(scene)
if !ok {
c = make(map[string][]Components[string])
}
c[componentType] = append(c[componentType], components...)
handleComponents.Store(scene, c)
}
func (h *Handle) PushGroupComponentStr(scene, componentType, name string, order float64, strs ...string) {
var component = Components[string]{
Val: strings.Join(slice.FilterAndMap(strs, func(t string) (string, bool) {
t = strings.Trim(t, " \n\r\t\v\x00")
if t == "" {
return "", false
}
return t, true
}), "\n"),
Order: order,
Name: name,
}
h.PushComponents(scene, componentType, component)
}
func (h *Handle) PushCacheGroupHeadScript(scene, name string, order float64, fns ...func(*Handle) string) {
h.PushGroupCacheComponentFn(scene, constraints.HeadScript, name, order, fns...)
}
func (h *Handle) PushFooterScript(scene string, components ...Components[string]) {
h.PushComponents(scene, constraints.FooterScript, components...)
}
func (h *Handle) PushGroupFooterScript(scene, name string, order float64, strs ...string) {
h.PushGroupComponentStr(scene, constraints.FooterScript, name, order, strs...)
}
func (h *Handle) PushCacheGroupFooterScript(scene, name string, order float64, fns ...func(*Handle) string) {
h.PushGroupCacheComponentFn(scene, constraints.FooterScript, name, order, fns...)
}
func (h *Handle) PushGroupCacheComponentFn(scene, componentType, name string, order float64, fns ...func(*Handle) string) {
h.PushComponents(scene, componentType, NewComponent(name, "", true, order, func(h *Handle) string {
return strings.Join(slice.Map(fns, func(t func(*Handle) string) string {
return t(h)
}), "\n")
}))
}
func NewComponent(name, val string, cached bool, order float64, fn func(handle *Handle) string) Components[string] {
return Components[string]{Fn: fn, Name: name, Cached: cached, Order: order, Val: val}
}
func (h *Handle) AddCacheComponent(scene, componentType, name string, order float64, fn func(*Handle) string) {
h.PushComponents(scene, componentType, NewComponent(name, "", true, order, fn))
}
func (h *Handle) PushHeadScript(scene string, components ...Components[string]) {
h.PushComponents(scene, constraints.HeadScript, components...)
}
func (h *Handle) PushGroupHeadScript(scene, name string, order float64, str ...string) {
h.PushGroupComponentStr(scene, constraints.HeadScript, name, order, str...)
}
func GetComponentsArgs[T any](k string, defaults T) T {
v, ok := componentsArgs.Load(k)
if ok {
vv, ok := v.(T)
if ok {
return vv
}
}
return defaults
}
func PushComponentsArgsForSlice[T any](name string, v ...T) {
val, ok := componentsArgs.Load(name)
if !ok {
var vv []T
vv = append(vv, v...)
componentsArgs.Store(name, vv)
return
}
vv, ok := val.([]T)
if ok {
vv = append(vv, v...)
componentsArgs.Store(name, vv)
}
}
func SetComponentsArgsForMap[K comparable, V any](name string, key K, v V) {
val, ok := componentsArgs.Load(name)
if !ok {
vv := make(map[K]V)
vv[key] = v
componentsArgs.Store(name, vv)
return
}
vv, ok := val.(map[K]V)
if ok {
vv[key] = v
componentsArgs.Store(name, vv)
}
}
func MergeComponentsArgsForMap[K comparable, V any](name string, m map[K]V) {
val, ok := componentsArgs.Load(name)
if !ok {
componentsArgs.Store(name, m)
return
}
vv, ok := val.(map[K]V)
if ok {
componentsArgs.Store(name, maps.Merge(vv, m))
}
}
func SetComponentsArgs(key string, value any) {
componentsArgs.Store(key, value)
}
func (h *Handle) GetComponentFilterFn(name string) ([]func(*Handle, string, ...any) string, bool) {
return componentFilterFns.Load(name)
}
func (h *Handle) AddActionFilter(name string, fns ...func(*Handle, string, ...any) string) {
v, _ := componentFilterFns.Load(name)
v = append(v, fns...)
componentFilterFns.Store(name, v)
}
func (h *Handle) DoActionFilter(name, s string, args ...any) string {
calls, ok := componentFilterFns.Load(name)
if ok {
return slice.Reduce(calls, func(fn func(*Handle, string, ...any) string, r string) string {
return fn(h, r, args...)
}, s)
}
return s
}

View File

@ -1,42 +0,0 @@
package components
import (
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components/block"
"github.com/fthvgb1/wp-go/app/wpconfig"
str "github.com/fthvgb1/wp-go/helper/strings"
"strings"
)
var blockFn = map[string]func(*wp.Handle, string, block.ParserBlock) (func() string, error){
"core/categories": block.Category,
}
func Block(id string) (func(*wp.Handle) string, string) {
content := wpconfig.GetPHPArrayVal("widget_block", "", str.ToInteger[int64](id, 0), "content")
if content == "" {
return nil, ""
}
var name string
v := block.ParseBlock(content)
if len(v.Output) > 0 {
name = v.Output[0].Name
}
return func(h *wp.Handle) string {
var out []string
for _, parserBlock := range v.Output {
fn, ok := blockFn[parserBlock.Name]
if ok {
s, err := fn(h, id, parserBlock)
if err != nil {
logs.Error(err, str.Join("parse block", parserBlock.Name, " fail "), parserBlock)
continue
}
out = append(out, s())
}
}
return strings.Join(out, "\n")
}, name
}

View File

@ -1,96 +0,0 @@
package block
import (
"github.com/dlclark/regexp2"
str "github.com/fthvgb1/wp-go/helper/strings"
)
type Block struct {
Name string
Attrs string
Len int
StartOffset int
Type string
}
type BockParser struct {
Document string
Offset int
Output []ParserBlock
}
type ParserBlock struct {
Name string
Attrs string
InnerBlocks string
InnerHtml string
InnerContent string
}
var block = regexp2.MustCompile(`<!--\s+(?<closer>/)?wp:(?<namespace>[a-z][a-z0-9_-]*\/)?(?<name>[a-z][a-z0-9_-]*)\s+(?<attrs>{(?:(?:[^}]+|}+(?=})|(?!}\s+\/?-->).)*)?}\s+)?(?<void>\/)?-->`, regexp2.IgnoreCase|regexp2.Singleline)
func ParseBlock(content string) (r BockParser) {
m, err := block.FindStringMatch(content)
if err != nil {
panic(err)
}
r.Document = content
for m != nil {
if m.GroupCount() < 1 {
continue
}
b := token(m.Groups())
bb := ParserBlock{}
bb.Name = b.Name
bb.Attrs = b.Attrs
r.Output = append(r.Output, bb)
m, _ = block.FindNextMatch(m)
}
return
}
func token(g []regexp2.Group) (b Block) {
if len(g) < 1 {
b.Type = "no-more-tokens"
return
}
var closer, name, void, nameSpace = "", "", "", ""
for i, group := range g {
v := group.String()
if v == "" {
continue
}
switch i {
case 0:
b.Len = group.Length
b.StartOffset = group.Index
case 1:
closer = v
case 2:
nameSpace = v
case 3:
name = v
case 4:
b.Attrs = v
case 5:
void = v
default:
continue
}
}
if nameSpace == "" {
nameSpace = "core/"
}
b.Name = str.Join(nameSpace, name)
if void != "" {
b.Type = "void-block"
return
}
if closer != "" {
b.Type = "block-closer"
return
}
b.Type = "block-opener"
return b
}

View File

@ -1,25 +0,0 @@
package block
import "testing"
func TestParseBlock(t *testing.T) {
type args struct {
s string
}
tests := []struct {
name string
args args
}{
{
name: "t1",
args: args{
s: `<!-- wp:categories {"showPostCounts":true,"showEmpty":true} /-->`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ParseBlock(tt.args.s)
})
}
}

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