Compare commits
32 Commits
9c0d18a6d0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 996e8abc9d | |||
| 1a99e53158 | |||
| 6b7adcb07a | |||
| 352666f059 | |||
| 4b29709791 | |||
| ed9a47c5ad | |||
| 4b4b39b82d | |||
| 8c1a3dd00d | |||
| 6670a4a256 | |||
| a3a1f37c72 | |||
| 60f9fdf116 | |||
| 97d31651c6 | |||
| 941fee1f70 | |||
| 8679dadd19 | |||
| fa6af8bf9c | |||
| d53620bcc6 | |||
| fcc8163cfe | |||
| a1264857d0 | |||
| 0fdcae3c0c | |||
| 2b7fdf7b9f | |||
| dbd62b3156 | |||
| ad3a4afc14 | |||
| b55551d4c7 | |||
| 0f49d5f017 | |||
| 47f42fe2b0 | |||
| 7964879495 | |||
| ddd12b69c2 | |||
| f04ed0b242 | |||
| 080c41d458 | |||
| ce45736f28 | |||
| 31c541e1c2 | |||
| f4597906c0 |
@@ -76,6 +76,8 @@ content目录下的一级目录代表一个分类,如果一级目录下有子
|
|||||||
- [x] 8.添加评论支持(在配置里开启,所有评论都会储存在仓库的Issues)
|
- [x] 8.添加评论支持(在配置里开启,所有评论都会储存在仓库的Issues)
|
||||||
- [x] 9.支持网易云音乐
|
- [x] 9.支持网易云音乐
|
||||||
- [x] 10.支持自定义切换主题
|
- [x] 10.支持自定义切换主题
|
||||||
|
- [x] 11.说说支持
|
||||||
|
- [x] 12.影集支持
|
||||||
|
|
||||||
> 后续尽善尽美之后,我可能会提供其他漂亮的主题皮肤,也欢迎大家参与进来。
|
> 后续尽善尽美之后,我可能会提供其他漂亮的主题皮肤,也欢迎大家参与进来。
|
||||||
|
|
||||||
@@ -88,8 +90,14 @@ content目录下的一级目录代表一个分类,如果一级目录下有子
|
|||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
|
### V3.3
|
||||||
|
* 加入影集界面
|
||||||
|
* 加入说说界面
|
||||||
|
|
||||||
### V3.2
|
### V3.2
|
||||||
|
* 加入搜索功能
|
||||||
* 加入Tag搜索和展示
|
* 加入Tag搜索和展示
|
||||||
|
* 加入Gitalk评论支持(可选)
|
||||||
|
|
||||||
### V3.1
|
### V3.1
|
||||||
* 去掉标题的.MD后缀
|
* 去掉标题的.MD后缀
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func InitApi() {
|
||||||
|
BilibiliInit()
|
||||||
|
http.HandleFunc(Mindustry.Url, GetMindustryInfo)
|
||||||
|
http.HandleFunc(Memos.Url, GetMemosJson)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blog/utils"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Mindustry = Api{
|
||||||
|
Mode: "GET",
|
||||||
|
Url: "/api/mdt",
|
||||||
|
Args: []Args{{Name: "host", Required: true, Description: "服务器地址"}},
|
||||||
|
SampleResponse: "{\n \"host\": \"p4.simpfun.cn\",\n \"port\": 8952,\n \"status\": \"Online\",\n \"name\": \"[#00ff00]镜影若滴の低配备用服\",\n \"maps\": \"未知\",\n \"players\": 0,\n \"version\": 146,\n \"wave\": 1,\n \"vertype\": \"official\",\n \"gamemode\": {\n \"name\": \"生存\",\n \"id\": 0\n },\n \"description\": \"[#00ff00]低配但稳定的备用服,[#ffaaff]欢迎加入QQ群:726525226\",\n \"modename\": \"\",\n \"limit\": 0,\n \"ping\": 40\n}",
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMindustryInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
info utils.ServerInfo
|
||||||
|
)
|
||||||
|
if err = r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "参数解析错误", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
host := r.Form.Get("host")
|
||||||
|
if host != "" {
|
||||||
|
info, err = utils.GetServerInfo(host)
|
||||||
|
//if err != nil {
|
||||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
//}
|
||||||
|
// 如果需要返回JSON数据给客户端,可以使用以下代码
|
||||||
|
w.Header().Set("Content-Type", "application/json;charset=utf-8")
|
||||||
|
err = json.NewEncoder(w).Encode(info)
|
||||||
|
} else {
|
||||||
|
Mindustry.ErrorInfoView(w, "host为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "无法解析服务器数据", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
+231
@@ -0,0 +1,231 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VideoData struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Ttl int `json:"ttl"`
|
||||||
|
Data struct {
|
||||||
|
Bvid string `json:"bvid"`
|
||||||
|
Aid int `json:"aid"`
|
||||||
|
Videos int `json:"videos"`
|
||||||
|
Tid int `json:"tid"`
|
||||||
|
Tname string `json:"tname"`
|
||||||
|
Copyright int `json:"copyright"`
|
||||||
|
Pic string `json:"pic"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Pubdate int `json:"pubdate"`
|
||||||
|
Ctime int `json:"ctime"`
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
DescV2 []struct {
|
||||||
|
RawText string `json:"raw_text"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
BizId int `json:"biz_id"`
|
||||||
|
} `json:"desc_v2"`
|
||||||
|
State int `json:"state"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
MissionId int `json:"mission_id"`
|
||||||
|
Rights struct {
|
||||||
|
Bp int `json:"bp"`
|
||||||
|
Elec int `json:"elec"`
|
||||||
|
Download int `json:"download"`
|
||||||
|
Movie int `json:"movie"`
|
||||||
|
Pay int `json:"pay"`
|
||||||
|
Hd5 int `json:"hd5"`
|
||||||
|
NoReprint int `json:"no_reprint"`
|
||||||
|
Autoplay int `json:"autoplay"`
|
||||||
|
UgcPay int `json:"ugc_pay"`
|
||||||
|
IsCooperation int `json:"is_cooperation"`
|
||||||
|
UgcPayPreview int `json:"ugc_pay_preview"`
|
||||||
|
NoBackground int `json:"no_background"`
|
||||||
|
CleanMode int `json:"clean_mode"`
|
||||||
|
IsSteinGate int `json:"is_stein_gate"`
|
||||||
|
Is360 int `json:"is_360"`
|
||||||
|
NoShare int `json:"no_share"`
|
||||||
|
ArcPay int `json:"arc_pay"`
|
||||||
|
FreeWatch int `json:"free_watch"`
|
||||||
|
} `json:"rights"`
|
||||||
|
Owner struct {
|
||||||
|
Mid int `json:"mid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Face string `json:"face"`
|
||||||
|
} `json:"owner"`
|
||||||
|
Stat struct {
|
||||||
|
Aid int `json:"aid"`
|
||||||
|
View int `json:"view"`
|
||||||
|
Danmaku int `json:"danmaku"`
|
||||||
|
Reply int `json:"reply"`
|
||||||
|
Favorite int `json:"favorite"`
|
||||||
|
Coin int `json:"coin"`
|
||||||
|
Share int `json:"share"`
|
||||||
|
NowRank int `json:"now_rank"`
|
||||||
|
HisRank int `json:"his_rank"`
|
||||||
|
Like int `json:"like"`
|
||||||
|
Dislike int `json:"dislike"`
|
||||||
|
Evaluation string `json:"evaluation"`
|
||||||
|
Vt int `json:"vt"`
|
||||||
|
} `json:"stat"`
|
||||||
|
ArgueInfo struct {
|
||||||
|
ArgueMsg string `json:"argue_msg"`
|
||||||
|
ArgueType int `json:"argue_type"`
|
||||||
|
ArgueLink string `json:"argue_link"`
|
||||||
|
} `json:"argue_info"`
|
||||||
|
Dynamic string `json:"dynamic"`
|
||||||
|
Cid int `json:"cid"`
|
||||||
|
Dimension struct {
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Rotate int `json:"rotate"`
|
||||||
|
} `json:"dimension"`
|
||||||
|
Premiere interface{} `json:"premiere"`
|
||||||
|
TeenageMode int `json:"teenage_mode"`
|
||||||
|
IsChargeableSeason bool `json:"is_chargeable_season"`
|
||||||
|
IsStory bool `json:"is_story"`
|
||||||
|
IsUpowerExclusive bool `json:"is_upower_exclusive"`
|
||||||
|
IsUpowerPlay bool `json:"is_upower_play"`
|
||||||
|
IsUpowerPreview bool `json:"is_upower_preview"`
|
||||||
|
EnableVt int `json:"enable_vt"`
|
||||||
|
VtDisplay string `json:"vt_display"`
|
||||||
|
NoCache bool `json:"no_cache"`
|
||||||
|
Pages []struct {
|
||||||
|
Cid int `json:"cid"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
From string `json:"from"`
|
||||||
|
Part string `json:"part"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
Vid string `json:"vid"`
|
||||||
|
Weblink string `json:"weblink"`
|
||||||
|
Dimension struct {
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Rotate int `json:"rotate"`
|
||||||
|
} `json:"dimension"`
|
||||||
|
FirstFrame string `json:"first_frame"`
|
||||||
|
} `json:"pages"`
|
||||||
|
Subtitle struct {
|
||||||
|
AllowSubmit bool `json:"allow_submit"`
|
||||||
|
List []struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Lan string `json:"lan"`
|
||||||
|
LanDoc string `json:"lan_doc"`
|
||||||
|
IsLock bool `json:"is_lock"`
|
||||||
|
SubtitleUrl string `json:"subtitle_url"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
IdStr string `json:"id_str"`
|
||||||
|
AiType int `json:"ai_type"`
|
||||||
|
AiStatus int `json:"ai_status"`
|
||||||
|
Author struct {
|
||||||
|
Mid int `json:"mid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Sex string `json:"sex"`
|
||||||
|
Face string `json:"face"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
Rank int `json:"rank"`
|
||||||
|
Birthday int `json:"birthday"`
|
||||||
|
IsFakeAccount int `json:"is_fake_account"`
|
||||||
|
IsDeleted int `json:"is_deleted"`
|
||||||
|
InRegAudit int `json:"in_reg_audit"`
|
||||||
|
IsSeniorMember int `json:"is_senior_member"`
|
||||||
|
} `json:"author"`
|
||||||
|
} `json:"list"`
|
||||||
|
} `json:"subtitle"`
|
||||||
|
Label struct {
|
||||||
|
Type int `json:"type"`
|
||||||
|
} `json:"label"`
|
||||||
|
IsSeasonDisplay bool `json:"is_season_display"`
|
||||||
|
UserGarb struct {
|
||||||
|
UrlImageAniCut string `json:"url_image_ani_cut"`
|
||||||
|
} `json:"user_garb"`
|
||||||
|
HonorReply struct {
|
||||||
|
Honor []struct {
|
||||||
|
Aid int `json:"aid"`
|
||||||
|
Type int `json:"type"`
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
WeeklyRecommendNum int `json:"weekly_recommend_num"`
|
||||||
|
} `json:"honor"`
|
||||||
|
} `json:"honor_reply"`
|
||||||
|
LikeIcon string `json:"like_icon"`
|
||||||
|
NeedJumpBv bool `json:"need_jump_bv"`
|
||||||
|
DisableShowUpInfo bool `json:"disable_show_up_info"`
|
||||||
|
IsStoryPlay int `json:"is_story_play"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
type VideoInfo struct {
|
||||||
|
Owner string `json:"owner"`
|
||||||
|
Face string `json:"face"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
Pic string `json:"pic"`
|
||||||
|
Coin int `json:"coin"`
|
||||||
|
Danmaku int `json:"danmaku"`
|
||||||
|
Favorite int `json:"favorite"`
|
||||||
|
Like int `json:"like"`
|
||||||
|
Reply int `json:"reply"`
|
||||||
|
Share int `json:"share"`
|
||||||
|
View int `json:"view"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func secondsToTimeString(seconds int) string {
|
||||||
|
duration := time.Duration(seconds) * time.Second
|
||||||
|
|
||||||
|
if duration.Hours() < 1 {
|
||||||
|
return fmt.Sprintf("%02d:%02d", int(duration.Minutes()), int(duration.Seconds())%60)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%02d:%02d:%02d", int(duration.Hours()), int(duration.Minutes())%60, int(duration.Seconds())%60)
|
||||||
|
}
|
||||||
|
func GetVideoData(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "参数解析错误", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
bv_id := r.Form.Get("bvid")
|
||||||
|
resp, err := http.Get("https://api.bilibili.com/x/web-interface/view?bvid=" + bv_id)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "无法获取视频数据", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var video VideoData
|
||||||
|
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&video) // 使用json.NewDecoder解码JSON数据
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "无法解析视频数据", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
imageurl := "https://images.weserv.nl/?url="
|
||||||
|
info := &VideoInfo{
|
||||||
|
Owner: video.Data.Owner.Name,
|
||||||
|
Face: imageurl + video.Data.Owner.Face,
|
||||||
|
Title: video.Data.Title,
|
||||||
|
Desc: video.Data.Desc,
|
||||||
|
Duration: secondsToTimeString(video.Data.Duration),
|
||||||
|
Pic: imageurl + video.Data.Pic,
|
||||||
|
Coin: video.Data.Stat.Coin,
|
||||||
|
Danmaku: video.Data.Stat.Danmaku,
|
||||||
|
Favorite: video.Data.Stat.Favorite,
|
||||||
|
Like: video.Data.Stat.Like,
|
||||||
|
Reply: video.Data.Stat.Reply,
|
||||||
|
Share: video.Data.Stat.Share,
|
||||||
|
View: video.Data.Stat.View,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果需要返回JSON数据给客户端,可以使用以下代码
|
||||||
|
w.Header().Set("Content-Type", "application/json;charset=utf-8")
|
||||||
|
err = json.NewEncoder(w).Encode(info)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "无法解析视频数据", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BilibiliInit() {
|
||||||
|
http.HandleFunc("/api/bilibili", GetVideoData)
|
||||||
|
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blog/config"
|
||||||
|
"blog/models"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Api struct {
|
||||||
|
Mode string
|
||||||
|
Url string
|
||||||
|
Args []Args
|
||||||
|
SampleResponse string
|
||||||
|
}
|
||||||
|
type Args struct {
|
||||||
|
Name string
|
||||||
|
Required bool
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiViewBuild(api Api, err interface{}) map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"Api": api,
|
||||||
|
"Err": err,
|
||||||
|
"Config": config.Cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Api) ErrorInfoView(w io.Writer, err interface{}) {
|
||||||
|
models.Api.Info.WriteData(w, ApiViewBuild(a, err))
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blog/config"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemoInfo struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
RowStatus string `json:"rowStatus"`
|
||||||
|
CreatorId int `json:"creatorId"`
|
||||||
|
CreatedTs int `json:"createdTs"`
|
||||||
|
UpdatedTs int `json:"updatedTs"`
|
||||||
|
DisplayTs int `json:"displayTs"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
Pinned bool `json:"pinned"`
|
||||||
|
CreatorName string `json:"creatorName"`
|
||||||
|
CreatorUsername string `json:"creatorUsername"`
|
||||||
|
ResourceList []interface{} `json:"resourceList"`
|
||||||
|
RelationList []interface{} `json:"relationList"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var Memos = Api{
|
||||||
|
Url: "/api/memos",
|
||||||
|
Mode: "GET",
|
||||||
|
SampleResponse: "[{\n \"bookSourceName\": \"69书吧自制\",\n \"bookSourceType\": 0,\n \"bookSourceUrl\": \"https://www.69shu.cc\",\n \"customOrder\": 45,\n \"enabled\": true,\n \"enabledCookieJar\": true,\n \"enabledExplore\": true,\n \"lastUpdateTime\": 1713454345818,\n \"respondTime\": 6224,\n \"ruleBookInfo\": {\n \"author\": \"h1@text##(.*) / (.*)##$2\",\n \"coverUrl\": \"img@src\",\n \"intro\": \".bookinfo_intro@text\",\n \"kind\": \".nav-mbx > a:nth-child(3)@text\",\n \"lastChapter\": \".update > a@text\",\n \"name\": \"h1@text##(.*) / (.*)##$1\"\n },\n \"ruleContent\": {\n \"content\": \"id.htmlContent@html##.*最快更新.*\"\n },\n \"ruleExplore\": {},\n \"ruleReview\": {},\n \"ruleSearch\": {\n \"author\": \"td.2@text\",\n \"bookList\": \".grid@tbody@tr!0\",\n \"bookUrl\": \"td.0@a@href\",\n \"checkKeyWord\": \"我的\",\n \"lastChapter\": \"td.1@text\",\n \"name\": \"td.0@text\",\n \"wordCount\": \"td.3@text\"\n },\n \"ruleToc\": {\n \"chapterList\": \"class.chapterlist.1@tag.li!0\",\n \"chapterName\": \"a.0@text\",\n \"chapterUrl\": \"a.0@href\"\n },\n \"searchUrl\": \"/modules/article/search.php?searchkey={{key}},{\\n\\\"charset\\\": \\\"gbk\\\"}\",\n \"weight\": 0\n}\n]",
|
||||||
|
Args: []Args{{
|
||||||
|
Name: "id",
|
||||||
|
Required: true,
|
||||||
|
Description: "有json代码块的文章id",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMemosJson(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "参数解析错误", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
id := r.Form.Get("id")
|
||||||
|
if id != "" {
|
||||||
|
resp, err := http.Get(config.Cfg.MemosURL + "/api/v1/memo/" + id)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "无法获取文章数据", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var info MemoInfo
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&info) // 使用json.NewDecoder解码JSON数据
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "无法解析文章数据", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
compileRegex := regexp.MustCompile("(?s)```json\n(.*?)```")
|
||||||
|
matchArr := compileRegex.FindStringSubmatch(info.Content)
|
||||||
|
if len(matchArr) > 0 {
|
||||||
|
source := make([]byte, 4096)
|
||||||
|
source = []byte(fmt.Sprintf("[%v]", matchArr[len(matchArr)-1]))
|
||||||
|
w.Header().Add("Content-Type", "application/octet-stream;charset=utf-8")
|
||||||
|
w.Header().Add("Content-Disposition", "attachment;filename="+info.Name+".json")
|
||||||
|
_, err = w.Write(source)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "无法解析源数据", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Memos.ErrorInfoView(w, "无法解析页面请求"+id)
|
||||||
|
} else {
|
||||||
|
Memos.ErrorInfoView(w, "id为空")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"blog/api"
|
||||||
"blog/config"
|
"blog/config"
|
||||||
"blog/models"
|
"blog/models"
|
||||||
"blog/routes"
|
"blog/routes"
|
||||||
@@ -16,6 +17,7 @@ func init() {
|
|||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
routes.InitRoute()
|
routes.InitRoute()
|
||||||
|
api.InitApi()
|
||||||
fmt.Printf("Version:v%v \n", config.Cfg.Version)
|
fmt.Printf("Version:v%v \n", config.Cfg.Version)
|
||||||
fmt.Printf("ListenAndServe On Port %v \n", config.Cfg.Port)
|
fmt.Printf("ListenAndServe On Port %v \n", config.Cfg.Port)
|
||||||
fmt.Printf("Dashboard On Path %v \n", config.Cfg.Dashboard)
|
fmt.Printf("Dashboard On Path %v \n", config.Cfg.Dashboard)
|
||||||
|
|||||||
@@ -7,6 +7,11 @@
|
|||||||
"webHookSecret": "jixieshi",
|
"webHookSecret": "jixieshi",
|
||||||
"categoryDisplayQuantity": 6,
|
"categoryDisplayQuantity": 6,
|
||||||
"utterancesRepo": "jixishi/blog_docs",
|
"utterancesRepo": "jixishi/blog_docs",
|
||||||
|
"gitalk_clientID": "2c40e5de269c2c61dc91",
|
||||||
|
"gitalk_clientSecret": "d976941995470662ec039028fe37c0466bd27027",
|
||||||
|
"gitalk_owner": "jixishi",
|
||||||
|
"gitalk_repo": "",
|
||||||
|
"gitalk_repo": "",
|
||||||
"timeLayout": "2006.01.02 15:04",
|
"timeLayout": "2006.01.02 15:04",
|
||||||
"siteName": "JiXieShi's Blog",
|
"siteName": "JiXieShi's Blog",
|
||||||
"htmlKeywords": "forest blog,Golang,ARM,BE6,前端,硬件",
|
"htmlKeywords": "forest blog,Golang,ARM,BE6,前端,硬件",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
userConfig
|
userConfig
|
||||||
systemConfig
|
systemConfig
|
||||||
|
memosConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type memosConfig struct {
|
||||||
|
MemosUser string `json:"memos_user"`
|
||||||
|
MemosURL string `json:"memos_url"`
|
||||||
|
MemosAlbumTag string `json:"memos_album_tag"`
|
||||||
|
MemosTalkTag string `json:"memos_talk_tag"`
|
||||||
|
MemosReaderTag string `json:"memos_reader_tag"`
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ type userConfig struct {
|
|||||||
|
|
||||||
Author string `json:"author"`
|
Author string `json:"author"`
|
||||||
|
|
||||||
|
Qq string `json:"qq"`
|
||||||
|
|
||||||
Icp string `json:"icp"`
|
Icp string `json:"icp"`
|
||||||
|
|
||||||
TimeLayout string `json:"timeLayout"`
|
TimeLayout string `json:"timeLayout"`
|
||||||
@@ -17,6 +19,14 @@ type userConfig struct {
|
|||||||
|
|
||||||
UtterancesRepo string `json:"utterancesRepo"`
|
UtterancesRepo string `json:"utterancesRepo"`
|
||||||
|
|
||||||
|
Gitalk_ClientID string `json:"gitalk_clientID"`
|
||||||
|
|
||||||
|
Gitalk_ClientSecret string `json:"gitalk_clientSecret"`
|
||||||
|
|
||||||
|
Gitalk_Repo string `json:"gitalk_repo"`
|
||||||
|
|
||||||
|
Gitalk_Owner string `json:"gitalk_owner"`
|
||||||
|
|
||||||
PageSize int `json:"pageSize"`
|
PageSize int `json:"pageSize"`
|
||||||
|
|
||||||
DescriptionLen int `json:"descriptionLen"`
|
DescriptionLen int `json:"descriptionLen"`
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func Article(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
path := models.ArticleShortUrlMap[key]
|
path := models.ArticleShortUrlMap[key]
|
||||||
|
|
||||||
articleDetail, err := models.ReadArticleDetail(path)
|
articleDetail, err := models.ReadArticleDetail(path, key)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
articleTemplate.WriteError(w, err)
|
articleTemplate.WriteError(w, err)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func ExtraNav(w http.ResponseWriter, r *http.Request) {
|
|||||||
name := r.Form.Get("name")
|
name := r.Form.Get("name")
|
||||||
for _, nav := range models.Navigation {
|
for _, nav := range models.Navigation {
|
||||||
if nav.Title == name {
|
if nav.Title == name {
|
||||||
articleDetail, err := models.ReadArticleDetail(nav.Path)
|
articleDetail, err := models.ReadArticleDetail(nav.Path, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
extraNavTemplate.WriteError(w, err)
|
extraNavTemplate.WriteError(w, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blog/config"
|
||||||
|
"blog/models"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Memos(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "参数解析错误", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
fun := r.Form.Get("name")
|
||||||
|
switch fun {
|
||||||
|
case "talk":
|
||||||
|
Template := models.Template.Talk
|
||||||
|
//qq := "https://q1.qlogo.cn/g?b=qq&nk=" + config.Cfg.Qq + "&s=5"
|
||||||
|
Template.WriteData(w, models.BuildViewData("Talk", map[string]interface{}{
|
||||||
|
"url": config.Cfg.MemosURL,
|
||||||
|
"user": config.Cfg.MemosUser,
|
||||||
|
"tag": config.Cfg.MemosTalkTag,
|
||||||
|
"author": config.Cfg.Author,
|
||||||
|
"qq": config.Cfg.Qq,
|
||||||
|
}))
|
||||||
|
case "reader":
|
||||||
|
Template := models.Template.Reader
|
||||||
|
//qq := "https://q1.qlogo.cn/g?b=qq&nk=" + config.Cfg.Qq + "&s=5"
|
||||||
|
Template.WriteData(w, models.BuildViewData("Reader", map[string]interface{}{
|
||||||
|
"url": config.Cfg.MemosURL,
|
||||||
|
"user": config.Cfg.MemosUser,
|
||||||
|
"tag": config.Cfg.MemosReaderTag,
|
||||||
|
"author": config.Cfg.Author,
|
||||||
|
"qq": config.Cfg.Qq,
|
||||||
|
}))
|
||||||
|
case "album":
|
||||||
|
Template := models.Template.Album
|
||||||
|
tag := strings.Split(config.Cfg.MemosAlbumTag, ",")
|
||||||
|
Template.WriteData(w, models.BuildViewData("Album", map[string]interface{}{
|
||||||
|
"url": config.Cfg.MemosURL,
|
||||||
|
"user": config.Cfg.MemosUser,
|
||||||
|
"image": tag[0],
|
||||||
|
"video": tag[1],
|
||||||
|
}))
|
||||||
|
default:
|
||||||
|
http.Error(w, "无法解析页面请求"+fun, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+15
-6
@@ -139,44 +139,52 @@ func RecursiveReadArticles(dir string) (Articles, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReadArticle(path string) (Article, error) {
|
func ReadArticle(path string) (Article, error) {
|
||||||
article, _, err := readMarkdown(path)
|
article, _, err := readMarkdown(path, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return article, err
|
return article, err
|
||||||
}
|
}
|
||||||
return article, nil
|
return article, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadArticleDetail(path string) (ArticleDetail, error) {
|
func ReadArticleDetail(path, key string) (ArticleDetail, error) {
|
||||||
_, articleDetail, err := readMarkdown(path)
|
_, articleDetail, err := readMarkdown(path, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return articleDetail, err
|
return articleDetail, err
|
||||||
}
|
}
|
||||||
return articleDetail, nil
|
return articleDetail, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readMarkdown(path string) (Article, ArticleDetail, error) {
|
// readMarkdown 读取 Markdown 文件内容
|
||||||
|
func readMarkdown(path, key string) (Article, ArticleDetail, error) {
|
||||||
var article Article
|
var article Article
|
||||||
var articleDetail ArticleDetail
|
var articleDetail ArticleDetail
|
||||||
mdFile, err := os.Stat(path)
|
|
||||||
|
|
||||||
|
// 获取文件信息
|
||||||
|
mdFile, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return article, articleDetail, err
|
return article, articleDetail, err
|
||||||
}
|
}
|
||||||
if mdFile.IsDir() {
|
if mdFile.IsDir() {
|
||||||
return article, articleDetail, errors.New("this path is Dir")
|
return article, articleDetail, errors.New("this path is Dir")
|
||||||
}
|
}
|
||||||
markdown, err := ioutil.ReadFile(path)
|
|
||||||
|
|
||||||
|
// 读取 Markdown 文件内容
|
||||||
|
markdown, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return article, articleDetail, err
|
return article, articleDetail, err
|
||||||
}
|
}
|
||||||
markdown = bytes.TrimSpace(markdown)
|
markdown = bytes.TrimSpace(markdown)
|
||||||
|
|
||||||
|
// 设置文章属性
|
||||||
|
if key != "" {
|
||||||
|
article.ShortUrl = key
|
||||||
|
}
|
||||||
article.Path = path
|
article.Path = path
|
||||||
article.Category = GetCategoryName(path)
|
article.Category = GetCategoryName(path)
|
||||||
article.Title = strings.TrimSuffix(strings.ToUpper(mdFile.Name()), ".MD")
|
article.Title = strings.TrimSuffix(strings.ToUpper(mdFile.Name()), ".MD")
|
||||||
article.Date = Time(mdFile.ModTime())
|
article.Date = Time(mdFile.ModTime())
|
||||||
|
|
||||||
|
// 处理 开头JSON 格式的文章信息
|
||||||
if !bytes.HasPrefix(markdown, []byte("```json")) {
|
if !bytes.HasPrefix(markdown, []byte("```json")) {
|
||||||
article.Description = cropDesc(markdown)
|
article.Description = cropDesc(markdown)
|
||||||
articleDetail.Article = article
|
articleDetail.Article = article
|
||||||
@@ -189,6 +197,7 @@ func readMarkdown(path string) (Article, ArticleDetail, error) {
|
|||||||
|
|
||||||
article.Description = cropDesc(markdownArrInfo[1])
|
article.Description = cropDesc(markdownArrInfo[1])
|
||||||
|
|
||||||
|
// 解析 JSON 内容
|
||||||
if err := json.Unmarshal(bytes.TrimSpace(markdownArrInfo[0]), &article); err != nil {
|
if err := json.Unmarshal(bytes.TrimSpace(markdownArrInfo[0]), &article); err != nil {
|
||||||
article.Title = "文章[" + article.Title + "]解析 JSON 出错,请检查。"
|
article.Title = "文章[" + article.Title + "]解析 JSON 出错,请检查。"
|
||||||
article.Description = err.Error()
|
article.Description = err.Error()
|
||||||
|
|||||||
+10
-2
@@ -25,13 +25,14 @@ func GetCategoryName(path string) string {
|
|||||||
return categoryName
|
return categoryName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GroupByCategory 按类别对文章进行分组
|
||||||
func GroupByCategory(articles *Articles, articleQuantity int) Categories {
|
func GroupByCategory(articles *Articles, articleQuantity int) Categories {
|
||||||
|
|
||||||
var categories Categories
|
var categories Categories
|
||||||
categoryMap := make(map[string]Articles)
|
categoryMap := make(map[string]Articles)
|
||||||
|
|
||||||
|
// 根据类别将文章分组
|
||||||
for _, article := range *articles {
|
for _, article := range *articles {
|
||||||
|
|
||||||
_, existedCategory := categoryMap[article.Category]
|
_, existedCategory := categoryMap[article.Category]
|
||||||
if existedCategory {
|
if existedCategory {
|
||||||
categoryMap[article.Category] = append(categoryMap[article.Category], article)
|
categoryMap[article.Category] = append(categoryMap[article.Category], article)
|
||||||
@@ -39,10 +40,13 @@ func GroupByCategory(articles *Articles, articleQuantity int) Categories {
|
|||||||
categoryMap[article.Category] = Articles{article}
|
categoryMap[article.Category] = Articles{article}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 遍历类别映射,构建分类信息
|
||||||
for categoryName, articles := range categoryMap {
|
for categoryName, articles := range categoryMap {
|
||||||
articleLen := len(articles)
|
articleLen := len(articles)
|
||||||
|
|
||||||
var articleList Articles
|
var articleList Articles
|
||||||
|
|
||||||
|
// 根据指定数量截取文章列表
|
||||||
if articleQuantity <= 0 {
|
if articleQuantity <= 0 {
|
||||||
articleList = articles
|
articleList = articles
|
||||||
} else {
|
} else {
|
||||||
@@ -52,12 +56,16 @@ func GroupByCategory(articles *Articles, articleQuantity int) Categories {
|
|||||||
articleList = articles[0:articleQuantity]
|
articleList = articles[0:articleQuantity]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加分类信息到结果集
|
||||||
categories = append(categories, Category{
|
categories = append(categories, Category{
|
||||||
Name: categoryName,
|
Name: categoryName,
|
||||||
Quantity: articleLen,
|
Quantity: articleLen,
|
||||||
Articles: articleList,
|
Articles: articleList,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 对分类结果集进行排序
|
||||||
sort.Sort(categories)
|
sort.Sort(categories)
|
||||||
return categories
|
return categories
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ var Navigation Navs
|
|||||||
var ArticleList Articles
|
var ArticleList Articles
|
||||||
var ArticleShortUrlMap map[string]string //用来保证文章 shortUrl 唯一和快速定位文章
|
var ArticleShortUrlMap map[string]string //用来保证文章 shortUrl 唯一和快速定位文章
|
||||||
var Template HtmlTemplate
|
var Template HtmlTemplate
|
||||||
|
var Api ApiTemplate
|
||||||
|
|
||||||
func CompiledContent() {
|
func CompiledContent() {
|
||||||
config.Initial() //克隆或者更新文档库
|
config.Initial() //克隆或者更新文档库
|
||||||
@@ -35,6 +36,16 @@ func CompiledContent() {
|
|||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
//加载Api信息模板
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
Api, err = initApiTemplate(config.Cfg.ThemesDir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
//文章
|
//文章
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -11,17 +11,20 @@ type Nav struct {
|
|||||||
}
|
}
|
||||||
type Navs []Nav
|
type Navs []Nav
|
||||||
|
|
||||||
|
// initExtraNav 初始化额外导航栏
|
||||||
func initExtraNav(dir string) (Navs, error) {
|
func initExtraNav(dir string) (Navs, error) {
|
||||||
|
|
||||||
var navigation Navs
|
var navigation Navs
|
||||||
var extraNav Articles
|
var extraNav Articles
|
||||||
|
|
||||||
|
// 递归读取文章
|
||||||
extraNav, err := RecursiveReadArticles(dir)
|
extraNav, err := RecursiveReadArticles(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return navigation, err
|
return navigation, err
|
||||||
}
|
}
|
||||||
sort.Sort(extraNav)
|
sort.Sort(extraNav)
|
||||||
|
|
||||||
|
// 将文章标题格式化为大写开头的标题,并添加到导航栏中
|
||||||
for _, article := range extraNav {
|
for _, article := range extraNav {
|
||||||
title := strings.Title(strings.ToLower(article.Title))
|
title := strings.Title(strings.ToLower(article.Title))
|
||||||
navigation = append(navigation, Nav{Title: title, Path: article.Path})
|
navigation = append(navigation, Nav{Title: title, Path: article.Path})
|
||||||
|
|||||||
+92
-22
@@ -12,6 +12,9 @@ type TemplatePointer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HtmlTemplate struct {
|
type HtmlTemplate struct {
|
||||||
|
Reader TemplatePointer
|
||||||
|
Talk TemplatePointer
|
||||||
|
Album TemplatePointer
|
||||||
Article TemplatePointer
|
Article TemplatePointer
|
||||||
Categories TemplatePointer
|
Categories TemplatePointer
|
||||||
Dashboard TemplatePointer
|
Dashboard TemplatePointer
|
||||||
@@ -19,6 +22,11 @@ type HtmlTemplate struct {
|
|||||||
Index TemplatePointer
|
Index TemplatePointer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ApiTemplate struct {
|
||||||
|
Info TemplatePointer
|
||||||
|
Mindustry TemplatePointer
|
||||||
|
}
|
||||||
|
|
||||||
func (t TemplatePointer) WriteData(w io.Writer, data interface{}) {
|
func (t TemplatePointer) WriteData(w io.Writer, data interface{}) {
|
||||||
|
|
||||||
err := t.Execute(w, data)
|
err := t.Execute(w, data)
|
||||||
@@ -35,34 +43,54 @@ func (t TemplatePointer) WriteError(w io.Writer, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildViewData 构建视图数据
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// title: 页面标题
|
||||||
|
// data: 页面数据
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
//
|
||||||
|
// map[string]interface{}: 包含标题、数据、网站配置和导航栏信息的映射
|
||||||
func BuildViewData(title string, data interface{}) map[string]interface{} {
|
func BuildViewData(title string, data interface{}) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"Title": title,
|
"Title": title, // 页面标题
|
||||||
"Data": data,
|
"Data": data, // 页面数据
|
||||||
"Config": config.Cfg,
|
"Config": config.Cfg, // 网站配置
|
||||||
"Navs": Navigation,
|
"Navs": Navigation, // 导航栏数据
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initHtmlTemplate 初始化 HTML 模板
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// viewDir: 视图文件目录路径
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
//
|
||||||
|
// HtmlTemplate: HTML 模板对象
|
||||||
|
// error: 错误信息(如果有)
|
||||||
func initHtmlTemplate(viewDir string) (HtmlTemplate, error) {
|
func initHtmlTemplate(viewDir string) (HtmlTemplate, error) {
|
||||||
var htmlTemplate HtmlTemplate
|
|
||||||
|
|
||||||
tp, err := readHtmlTemplate(
|
tp, err := readHtmlTemplate(
|
||||||
[]string{"index", "extraNav", "dashboard", "categories", "article"},
|
[]string{"index", "extraNav", "dashboard", "categories", "article", "album", "talk", "reader"},
|
||||||
viewDir)
|
viewDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return htmlTemplate, err
|
return HtmlTemplate{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlTemplate.Index = tp[0]
|
return HtmlTemplate{
|
||||||
htmlTemplate.ExtraNav = tp[1]
|
Index: tp[0],
|
||||||
htmlTemplate.Dashboard = tp[2]
|
ExtraNav: tp[1],
|
||||||
htmlTemplate.Categories = tp[3]
|
Dashboard: tp[2],
|
||||||
htmlTemplate.Article = tp[4]
|
Categories: tp[3],
|
||||||
|
Article: tp[4],
|
||||||
return htmlTemplate, nil
|
Album: tp[5],
|
||||||
|
Talk: tp[6],
|
||||||
|
Reader: tp[7],
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SpreadDigit(n int) []int {
|
func SpreadDigit(n int) []int {
|
||||||
var r []int
|
var r []int
|
||||||
for i := 1; i <= n; i++ {
|
for i := 1; i <= n; i++ {
|
||||||
@@ -71,21 +99,63 @@ func SpreadDigit(n int) []int {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readHtmlTemplate 读取 HTML 模板文件
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// htmlFileName: HTML 文件名列表
|
||||||
|
// viewDir: 视图文件目录路径
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
//
|
||||||
|
// []TemplatePointer: HTML 模板指针列表
|
||||||
|
// error: 错误信息(如果有)
|
||||||
func readHtmlTemplate(htmlFileName []string, viewDir string) ([]TemplatePointer, error) {
|
func readHtmlTemplate(htmlFileName []string, viewDir string) ([]TemplatePointer, error) {
|
||||||
var htmlTemplate []TemplatePointer
|
var htmlTemplate []TemplatePointer
|
||||||
|
|
||||||
head := viewDir + "/layouts/head.html"
|
head := viewDir + "/layouts/head.gohtml"
|
||||||
footer := viewDir + "/layouts/footer.html"
|
nav := viewDir + "/layouts/nav.gohtml"
|
||||||
|
footer := viewDir + "/layouts/footer.gohtml"
|
||||||
|
reviews := viewDir + "/layouts/reviews.gohtml"
|
||||||
|
|
||||||
for _, name := range htmlFileName {
|
for _, name := range htmlFileName {
|
||||||
|
tp, err := template.New(name+".gohtml").
|
||||||
tp, err := template.New(name+".html").
|
|
||||||
Funcs(template.FuncMap{"SpreadDigit": SpreadDigit}).
|
Funcs(template.FuncMap{"SpreadDigit": SpreadDigit}).
|
||||||
ParseFiles(viewDir+"/"+name+".html", head, footer)
|
ParseFiles(viewDir+"/"+name+".gohtml", head, nav, footer, reviews)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return htmlTemplate, err
|
return nil, err
|
||||||
}
|
}
|
||||||
htmlTemplate = append(htmlTemplate, TemplatePointer{tp})
|
htmlTemplate = append(htmlTemplate, TemplatePointer{tp})
|
||||||
}
|
}
|
||||||
|
|
||||||
return htmlTemplate, nil
|
return htmlTemplate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initApiTemplate(viewDir string) (ApiTemplate, error) {
|
||||||
|
tp, err := readApiTemplate(
|
||||||
|
[]string{"info", "mdt"},
|
||||||
|
viewDir+"/api")
|
||||||
|
if err != nil {
|
||||||
|
return ApiTemplate{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiTemplate{
|
||||||
|
Info: tp[0],
|
||||||
|
Mindustry: tp[1],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func readApiTemplate(htmlFileName []string, viewDir string) ([]TemplatePointer, error) {
|
||||||
|
var apiTemplate []TemplatePointer
|
||||||
|
head := viewDir + "/head.gohtml"
|
||||||
|
footer := viewDir + "/footer.gohtml"
|
||||||
|
for _, name := range htmlFileName {
|
||||||
|
tp, err := template.New(name+".gohtml").
|
||||||
|
Funcs(template.FuncMap{"SpreadDigit": SpreadDigit}).
|
||||||
|
ParseFiles(viewDir+"/"+name+".gohtml", head, footer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
apiTemplate = append(apiTemplate, TemplatePointer{tp})
|
||||||
|
}
|
||||||
|
return apiTemplate, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ func InitRoute() {
|
|||||||
http.HandleFunc("/categories", controller.Category)
|
http.HandleFunc("/categories", controller.Category)
|
||||||
http.HandleFunc("/article", controller.Article)
|
http.HandleFunc("/article", controller.Article)
|
||||||
http.HandleFunc("/extra-nav", controller.ExtraNav)
|
http.HandleFunc("/extra-nav", controller.ExtraNav)
|
||||||
|
http.HandleFunc("/memos", controller.Memos)
|
||||||
|
|
||||||
http.HandleFunc(config.Cfg.GitHookUrl, controller.GithubHook)
|
http.HandleFunc(config.Cfg.GitHookUrl, controller.GithubHook)
|
||||||
http.HandleFunc(config.Cfg.Dashboard, controller.Dashboard)
|
http.HandleFunc(config.Cfg.Dashboard, controller.Dashboard)
|
||||||
|
|||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||||
|
<!-- <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/viewerjs/1.10.1/viewer.min.css">-->
|
||||||
|
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/viewerjs/1.10.1/viewer.min.js"></script>-->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h3>[XiuRen秀人网]第8473期小逗逗写真</h3>
|
||||||
|
|
||||||
|
<div id="box"><img alt="秀人集.com_[XiuRen秀人网]No.8473_模特小逗逗性感白色OL服饰露黑色内衣配超薄肉丝迷人诱惑写真84P" title="秀人集.com_[XiuRen秀人网]No.8473_模特小逗逗性感白色OL服饰露黑色内衣配超薄肉丝迷人诱惑写真84P" src="/uploadfile/202405/14/17115035362.webp">
|
||||||
|
<img alt="秀人集.com_[XiuRen秀人网]No.8473_模特小逗逗性感白色OL服饰露黑色内衣配超薄肉丝迷人诱惑写真84P" title="秀人集.com_[XiuRen秀人网]No.8473_模特小逗逗性感白色OL服饰露黑色内衣配超薄肉丝迷人诱惑写真84P" src="/uploadfile/202405/14/3C115035217.webp">
|
||||||
|
<img alt="秀人集.com_[XiuRen秀人网]No.8473_模特小逗逗性感白色OL服饰露黑色内衣配超薄肉丝迷人诱惑写真84P" title="秀人集.com_[XiuRen秀人网]No.8473_模特小逗逗性感白色OL服饰露黑色内衣配超薄肉丝迷人诱惑写真84P" src="/uploadfile/202405/14/3C115035258.webp"></div>
|
||||||
|
<div id="loads">加载中 </div>
|
||||||
|
<script>
|
||||||
|
var box = document.getElementById("box");
|
||||||
|
var loads = document.getElementById("loads");
|
||||||
|
// var io; document.querySelector("img").onload = () => {
|
||||||
|
// new Viewer(box, {
|
||||||
|
// //设置图片地址来源
|
||||||
|
// url: 'src',
|
||||||
|
// //是否显示图片标题(true/false)
|
||||||
|
// title: false,
|
||||||
|
// //设置播放间隔(单位毫秒,1秒=1000毫秒)
|
||||||
|
// interval: 3000
|
||||||
|
// });
|
||||||
|
// loads.id = 'load';
|
||||||
|
var i = 1;
|
||||||
|
var page = 28;
|
||||||
|
var url = "https://www.xiurenb.net/XiuRen/15302_";
|
||||||
|
// io = new IntersectionObserver(entries => {
|
||||||
|
// if (entries[0].intersectionRatio <= 0) return;
|
||||||
|
for (i; i < page + 1; i++) {
|
||||||
|
fetchHtml(i, page, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// });
|
||||||
|
// io.observe(load);
|
||||||
|
// }
|
||||||
|
function fetchHtml(i, page, url) {
|
||||||
|
if (i != page) {
|
||||||
|
nurl = url + i + '.html';
|
||||||
|
fetch(nurl).then(res => res.text()).then(text => {
|
||||||
|
box.innerHTML +=
|
||||||
|
'<h5>' + (++i) + '->' + (++page) + '</h5>';
|
||||||
|
console.log(text)
|
||||||
|
new DOMParser().parseFromString(text, "text/html").querySelectorAll("p img").forEach(item => box.innerHTML += item.outerHTML.replace('<img', '<img onload="box.viewer.update()"'))
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
loads.innerHTML = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-cn">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<link rel="shortcut icon" href="public/img/favicon.ico" type="image/x-icon">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<title>{{ .Title }} - {{ .Config.SiteName }}</title>
|
||||||
|
</head>
|
||||||
|
<body class="theme{{ .Config.ThemeColor }}">
|
||||||
|
<header>
|
||||||
|
</header>
|
||||||
|
<div class="post-warp">
|
||||||
|
<div className="sub-title">- tall -</div>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
#bilibili {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bilibili .loading {
|
||||||
|
/*vertical-align: middle;*/
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--primary, #673ab7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#bilibili .loading img {
|
||||||
|
height: 60px;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box {
|
||||||
|
display: flex;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid #e0e3ed;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--primary, #673ab7) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
transition: 0.3s;
|
||||||
|
maxWidth768()
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box:hover {
|
||||||
|
border-color: #4976f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_cover {
|
||||||
|
width: 234px;
|
||||||
|
position: relative;
|
||||||
|
maxWidth768()
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_cover img {
|
||||||
|
width: 100%;
|
||||||
|
filter: none;
|
||||||
|
margin: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_cover .play_icon {
|
||||||
|
position: absolute;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
opacity: 0.8;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_cover span {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
right: 5px;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 1px 3px #7a7a7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info {
|
||||||
|
padding: 10px 10px 10px 18px;
|
||||||
|
line-height: 1;
|
||||||
|
width: calc(100% - 200px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
maxWidth768()
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .desc {
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 2px 0 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
maxWidth768()
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .stat {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .stat svg {
|
||||||
|
margin-right: 3px;
|
||||||
|
font-size: 18px;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .stat svg path {
|
||||||
|
fill: var(--primary, #673ab7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .stat span {
|
||||||
|
margin-right: 10px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .owner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .owner .tip {
|
||||||
|
color: #f69;
|
||||||
|
border: 1px solid;
|
||||||
|
padding: 3px 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .owner img {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
object-fit: cover;
|
||||||
|
margin: 0 5px 0 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='light'] .bilibili_box .bilibili_info .stat svg, [data-theme='dark'] .bilibili_cover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="bilibili">
|
||||||
|
<div class='loading'><img src="/public/img/loading.svg" alt="加载中...">
|
||||||
|
<a>加载中...</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let playIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.67735 4.2798C5.98983 4.1725 7.85812 4.0625 10 4.0625C12.1421 4.0625 14.0105 4.17252 15.323 4.27983C16.2216 4.3533 16.9184 5.04049 16.9989 5.9318C17.0962 7.00837 17.1875 8.43614 17.1875 10C17.1875 11.5639 17.0962 12.9916 16.9989 14.0682C16.9184 14.9595 16.2216 15.6467 15.323 15.7202C14.0105 15.8275 12.1421 15.9375 10 15.9375C7.85812 15.9375 5.98983 15.8275 4.67735 15.7202C3.77861 15.6467 3.08174 14.9593 3.00119 14.0678C2.90388 12.9908 2.8125 11.5627 2.8125 10C2.8125 8.43727 2.90388 7.00924 3.00119 5.93221C3.08174 5.04067 3.77861 4.35327 4.67735 4.2798ZM10 2.8125C7.81674 2.8125 5.9136 2.92456 4.5755 3.03395C3.07738 3.15643 1.8921 4.31616 1.75626 5.81973C1.65651 6.92379 1.5625 8.39058 1.5625 10C1.5625 11.6094 1.65651 13.0762 1.75626 14.1803C1.8921 15.6838 3.07738 16.8436 4.5755 16.966C5.9136 17.0754 7.81674 17.1875 10 17.1875C12.1835 17.1875 14.0868 17.0754 15.4249 16.966C16.9228 16.8436 18.108 15.6841 18.2438 14.1807C18.3435 13.077 18.4375 11.6105 18.4375 10C18.4375 8.38948 18.3435 6.92296 18.2438 5.81931C18.108 4.31588 16.9228 3.15645 15.4249 3.03398C14.0868 2.92458 12.1835 2.8125 10 2.8125ZM12.1876 10.722C12.7431 10.4013 12.7431 9.59941 12.1876 9.27866L9.06133 7.47373C8.50577 7.15298 7.81133 7.55392 7.81133 8.19542V11.8053C7.81133 12.4468 8.50577 12.8477 9.06133 12.527L12.1876 10.722Z" fill="#9499A0"/></svg>`
|
||||||
|
let likeIcon = `<svg width="36" height="36" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M9.77234 30.8573V11.7471H7.54573C5.50932 11.7471 3.85742 13.3931 3.85742 15.425V27.1794C3.85742 29.2112 5.50932 30.8573 7.54573 30.8573H9.77234ZM11.9902 30.8573V11.7054C14.9897 10.627 16.6942 7.8853 17.1055 3.33591C17.2666 1.55463 18.9633 0.814421 20.5803 1.59505C22.1847 2.36964 23.243 4.32583 23.243 6.93947C23.243 8.50265 23.0478 10.1054 22.6582 11.7471H29.7324C31.7739 11.7471 33.4289 13.402 33.4289 15.4435C33.4289 15.7416 33.3928 16.0386 33.3215 16.328L30.9883 25.7957C30.2558 28.7683 27.5894 30.8573 24.528 30.8573H11.9911H11.9902Z"></path></svg>`
|
||||||
|
let coinIcon = `<svg width="28" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" class="icon" style="fill:;"><path fill-rule="evenodd" clip-rule="evenodd" d="M14.045 25.5454C7.69377 25.5454 2.54504 20.3967 2.54504 14.0454C2.54504 7.69413 7.69377 2.54541 14.045 2.54541C20.3963 2.54541 25.545 7.69413 25.545 14.0454C25.545 17.0954 24.3334 20.0205 22.1768 22.1771C20.0201 24.3338 17.095 25.5454 14.045 25.5454ZM9.66202 6.81624H18.2761C18.825 6.81624 19.27 7.22183 19.27 7.72216C19.27 8.22248 18.825 8.62807 18.2761 8.62807H14.95V10.2903C17.989 10.4444 20.3766 12.9487 20.3855 15.9916V17.1995C20.3854 17.6997 19.9799 18.1052 19.4796 18.1052C18.9793 18.1052 18.5738 17.6997 18.5737 17.1995V15.9916C18.5667 13.9478 16.9882 12.2535 14.95 12.1022V20.5574C14.95 21.0577 14.5444 21.4633 14.0441 21.4633C13.5437 21.4633 13.1382 21.0577 13.1382 20.5574V12.1022C11.1 12.2535 9.52148 13.9478 9.51448 15.9916V17.1995C9.5144 17.6997 9.10883 18.1052 8.60856 18.1052C8.1083 18.1052 7.70273 17.6997 7.70265 17.1995V15.9916C7.71158 12.9487 10.0992 10.4444 13.1382 10.2903V8.62807H9.66202C9.11309 8.62807 8.66809 8.22248 8.66809 7.72216C8.66809 7.22183 9.11309 6.81624 9.66202 6.81624Z"></path></svg>`
|
||||||
|
function Formatv(item) {
|
||||||
|
let ids,
|
||||||
|
content = item.content,
|
||||||
|
bilibilis = content.match(/{\s*bilibili\s*(.*)\s*}/g);
|
||||||
|
if (bilibilis) {
|
||||||
|
ids = bilibilis.map(item => item.replace(/{\s*bilibili\s*(.*)\s*}/, '$1').replace(/.*video\/([^\/]*).*/, '$1').trim());
|
||||||
|
}
|
||||||
|
content = content.replace(/#(.*?)\s/g, '').replace(/{.*}/g, '').replace(/\!\[(.*?)\]\((.*?)\)/g, '').replace(/```/g, '')
|
||||||
|
let text = content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]').trim();
|
||||||
|
return {content, ids, text}
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = 'https://notes.starss.cc'
|
||||||
|
fetch(url + '/api/v1/memo?creatorId=1&tag=视频').then(res => res.json()).then(async data => {
|
||||||
|
let items = [], html = ''
|
||||||
|
data.forEach(item => {
|
||||||
|
items.push(Formatv(item))
|
||||||
|
})
|
||||||
|
for (const item of items) {
|
||||||
|
html += `<div class="bilibili_item"><span>${item.text}</span>`
|
||||||
|
|
||||||
|
for (const id of item.ids) {
|
||||||
|
await fetch(`https://api.320.ink/api/b?id=${id}`).then(res => res.json()).then(data => {
|
||||||
|
console.log(data)
|
||||||
|
html += `<a href="https://www.bilibili.com/video/${id}/" class="bilibili_box" id="${id}">
|
||||||
|
<div class="bilibili_cover">
|
||||||
|
<img src="https://s1.hdslb.com/bfs/static/player/img/play.svg" class="play_icon no-lazyload">
|
||||||
|
<img src="${data.pic + '&h=300'}" class="no-lazyload">
|
||||||
|
<span>${data.duration}</span>
|
||||||
|
</div>
|
||||||
|
<div class="bilibili_info">
|
||||||
|
<div class="title">${data.title}</div>
|
||||||
|
<div class="desc">${data.desc}</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span>${playIcon}${data.view}</span>
|
||||||
|
<span>${likeIcon}${data.like}</span>
|
||||||
|
<span>${coinIcon}${data.coin}</span>
|
||||||
|
</div>
|
||||||
|
<div class="owner">
|
||||||
|
<span class="tip">视频</span>
|
||||||
|
<img src="${data.face + '&h=100'}" class="no-lazyload">
|
||||||
|
<span>${data.owner}</span>
|
||||||
|
</div>
|
||||||
|
</div></a>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
html += `</div>`
|
||||||
|
document.getElementById('bilibili').innerHTML = html
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
{{template "header" .}}
|
||||||
|
<div class="sub-title">- {{ .Title }} -</div>
|
||||||
|
<div class="gallery-photos page">
|
||||||
|
<img src="https://cdn.jsdelivr.net/gh/jkjoy/14e/img/loading.svg" style="margin:auto" alt="">
|
||||||
|
</div>
|
||||||
|
<link rel="stylesheet" href="https://cdn.staticfile.org/fancyapps-ui/4.0.27/fancybox.min.css" media="print" onload="this.media='all'">
|
||||||
|
<script src="https://cdn.staticfile.org/fancyapps-ui/4.0.27/fancybox.umd.min.js"></script>
|
||||||
|
<style>
|
||||||
|
/* 页面初始化 */
|
||||||
|
.page-title{display: none;}
|
||||||
|
.page-top-card {border-radius: 12px;}
|
||||||
|
/* 页面初始化结束 */
|
||||||
|
#article-container a img {margin: 0; border-radius:0;}
|
||||||
|
.gallery-photos{width:100%;margin-top: 10px;}
|
||||||
|
.gallery-photo{min-height:5rem;width:24.97%;padding:4px;position: relative;}
|
||||||
|
.gallery-photo a{display:block;overflow: hidden;}
|
||||||
|
.gallery-photo img{display: block;width:100%;animation: fadeIn 1s;cursor: pointer;transition: all .4s ease-in-out !important;}
|
||||||
|
.gallery-photo span.photo-title,.gallery-photo span.photo-time{max-width: calc(100% - 7px);position:absolute;line-height:1.8;left:4px;font-size:14px;background: rgba(0, 0, 0, 0.3);padding:0px 8px;color: #fff;animation: fadeIn 1s;}
|
||||||
|
.gallery-photo span.photo-title{bottom:4px;}
|
||||||
|
.gallery-photo span.photo-time{top:4px;}
|
||||||
|
.gallery-photo:hover img{transform: scale(1.1);}
|
||||||
|
@media screen and (max-width: 1100px) {
|
||||||
|
.gallery-photo{width:33.3%;}
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 900px) {.page-top-card {margin: 0;}}
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.gallery-photo{width:49.9%;padding:3px}
|
||||||
|
.gallery-photo span.photo-time{display:none}
|
||||||
|
.page-top-card {border-radius: 8px;}
|
||||||
|
.gallery-photo span.photo-title{left:3px;bottom:3px;}
|
||||||
|
}
|
||||||
|
@keyframes fadeIn{0% {opacity: 0;}100%{opacity: 1;}}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
fetch('{{ .Data.url }}/api/v1/memo?creatorId={{ .Data.user }}&tag={{ .Data.tag }}').then(res => res.json()).then(data => {
|
||||||
|
let html = '',
|
||||||
|
imgs = []
|
||||||
|
data.forEach(item => {
|
||||||
|
let ls = item.content.match(/\!\[.*?\]\(.*?\)/g)
|
||||||
|
if (ls) imgs = imgs.concat(ls)
|
||||||
|
if (item.resourceList.length) {
|
||||||
|
item.resourceList.forEach(t => {
|
||||||
|
if (t.externalLink) imgs.push(``)
|
||||||
|
else imgs.push(``)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (imgs) imgs.forEach(item => {
|
||||||
|
let img = item.replace(/!\[.*?\]\((.*?)\)/g, '$1'),
|
||||||
|
time, title, tat = item.replace(/!\[(.*?)\]\(.*?\)/g, '$1')
|
||||||
|
if (tat.indexOf(' ') != -1) {
|
||||||
|
time = tat.split(' ')[0]
|
||||||
|
title = tat.split(' ')[1]
|
||||||
|
} else title = tat
|
||||||
|
|
||||||
|
html += `<div class="gallery-photo"><a href="${img}" data-fancybox="gallery" class="fancybox" data-thumb="${img}"><img class="no-lazyload photo-img" loading='lazy' decoding="async" src="${img}"></a>`
|
||||||
|
title ? html += `<span class="photo-title">${title}</span>` : ''
|
||||||
|
time ? html += `<span class="photo-time">${time}</span>` : ''
|
||||||
|
html += `</div>`
|
||||||
|
})
|
||||||
|
console.log(Fancybox);
|
||||||
|
document.querySelector('.gallery-photos.page').innerHTML = html
|
||||||
|
imgStatus.watch('.photo-img', () => { waterfall('.gallery-photos') })
|
||||||
|
window.Lately && Lately.init({ target: '.photo-time' })
|
||||||
|
}).catch()
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
#bilibili{margin-top:1rem}#bilibili .loading{display:flex;align-items:center;justify-content:center;flex-direction:column;margin:0 auto;width:100px;height:100px;border-radius:8px;background-color:var(--primary,#673ab7)}#bilibili .loading img{height:60px;width:60px}.bilibili_item{display:flex;flex-direction:column;padding:20px;margin-bottom:5px;color:var(--primary,#673ab7) !important;text-decoration:none !important;transition:0.3s;border-radius:10px;border:1px solid #e0e3ed}.bilibili_item:hover{border-color:#4976f5}.bilibili_box{display:flex;border:1px solid #e0e3ed;border-radius:10px;overflow:hidden;color:var(--primary,#673ab7) !important;text-decoration:none !important;transition:0.3s;maxWidth768()}.bilibili_box:hover{border-color:rgb(from hotpink r g b)}.bilibili_box .bilibili_cover{width:234px;position:relative;maxWidth768()}.bilibili_box .bilibili_cover img{width:100%;filter:none;margin:0 !important;border-radius:0 !important}.bilibili_box .bilibili_cover .play_icon{position:absolute;width:45px;height:45px;opacity:0.8;top:50%;left:50%;transform:translate(-50%,-50%)}.bilibili_box .bilibili_cover span{position:absolute;bottom:0px;right:5px;color:#fff;text-shadow:0 1px 3px #7a7a7a}.bilibili_box .bilibili_info{padding:10px 10px 10px 18px;line-height:1;width:calc(100% - 200px);display:flex;flex-direction:column;justify-content:space-around;maxWidth768()}.bilibili_box .bilibili_info .title{font-size:1.2rem;font-weight:bold;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.5;color:rgb(from var(--primary,#000000) calc(1 - r) calc(1 - g) calc(1 - b))}.bilibili_box .bilibili_info .desc{font-size:15px;margin:2px 0 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:rgb(from var(--primary,#000000) calc(1 - r) calc(1 - g) calc(1 - b));maxWidth768()}.bilibili_box .bilibili_info .stat{font-size:15px}.bilibili_box .bilibili_info .stat svg{margin-right:3px;font-size:18px;width:1em;height:1em}.bilibili_box .bilibili_info .stat svg path{fill:rgb(from hotpink r g b)}.bilibili_box .bilibili_info .stat span{margin-right:10px;display:inline-flex;align-items:center}.bilibili_box .bilibili_info .owner{display:flex;align-items:center;line-height:1;font-size:15px}.bilibili_box .bilibili_info .owner .tip{color:#f69;border:1px solid;padding:3px 6px;font-size:12px;border-radius:5px;margin-right:10px}.bilibili_box .bilibili_info .owner img{width:22px;height:22px;border-radius:50% !important;object-fit:cover;margin:0 5px 0 0 !important}[data-theme='light'] .bilibili_box .bilibili_info .stat svg,[data-theme='dark'] .bilibili_cover{opacity:0.8}
|
||||||
|
</style>
|
||||||
|
<div id="bilibili">
|
||||||
|
<div class='loading'><img src="/public/img/loading.svg" alt="加载中...">
|
||||||
|
<a>加载中...</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let playIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.67735 4.2798C5.98983 4.1725 7.85812 4.0625 10 4.0625C12.1421 4.0625 14.0105 4.17252 15.323 4.27983C16.2216 4.3533 16.9184 5.04049 16.9989 5.9318C17.0962 7.00837 17.1875 8.43614 17.1875 10C17.1875 11.5639 17.0962 12.9916 16.9989 14.0682C16.9184 14.9595 16.2216 15.6467 15.323 15.7202C14.0105 15.8275 12.1421 15.9375 10 15.9375C7.85812 15.9375 5.98983 15.8275 4.67735 15.7202C3.77861 15.6467 3.08174 14.9593 3.00119 14.0678C2.90388 12.9908 2.8125 11.5627 2.8125 10C2.8125 8.43727 2.90388 7.00924 3.00119 5.93221C3.08174 5.04067 3.77861 4.35327 4.67735 4.2798ZM10 2.8125C7.81674 2.8125 5.9136 2.92456 4.5755 3.03395C3.07738 3.15643 1.8921 4.31616 1.75626 5.81973C1.65651 6.92379 1.5625 8.39058 1.5625 10C1.5625 11.6094 1.65651 13.0762 1.75626 14.1803C1.8921 15.6838 3.07738 16.8436 4.5755 16.966C5.9136 17.0754 7.81674 17.1875 10 17.1875C12.1835 17.1875 14.0868 17.0754 15.4249 16.966C16.9228 16.8436 18.108 15.6841 18.2438 14.1807C18.3435 13.077 18.4375 11.6105 18.4375 10C18.4375 8.38948 18.3435 6.92296 18.2438 5.81931C18.108 4.31588 16.9228 3.15645 15.4249 3.03398C14.0868 2.92458 12.1835 2.8125 10 2.8125ZM12.1876 10.722C12.7431 10.4013 12.7431 9.59941 12.1876 9.27866L9.06133 7.47373C8.50577 7.15298 7.81133 7.55392 7.81133 8.19542V11.8053C7.81133 12.4468 8.50577 12.8477 9.06133 12.527L12.1876 10.722Z" fill="#9499A0"/></svg>';
|
||||||
|
let likeIcon = '<svg width="36" height="36" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M9.77234 30.8573V11.7471H7.54573C5.50932 11.7471 3.85742 13.3931 3.85742 15.425V27.1794C3.85742 29.2112 5.50932 30.8573 7.54573 30.8573H9.77234ZM11.9902 30.8573V11.7054C14.9897 10.627 16.6942 7.8853 17.1055 3.33591C17.2666 1.55463 18.9633 0.814421 20.5803 1.59505C22.1847 2.36964 23.243 4.32583 23.243 6.93947C23.243 8.50265 23.0478 10.1054 22.6582 11.7471H29.7324C31.7739 11.7471 33.4289 13.402 33.4289 15.4435C33.4289 15.7416 33.3928 16.0386 33.3215 16.328L30.9883 25.7957C30.2558 28.7683 27.5894 30.8573 24.528 30.8573H11.9911H11.9902Z"></path></svg>';
|
||||||
|
let coinIcon = '<svg width="28" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" class="icon" style="fill:;"><path fill-rule="evenodd" clip-rule="evenodd" d="M14.045 25.5454C7.69377 25.5454 2.54504 20.3967 2.54504 14.0454C2.54504 7.69413 7.69377 2.54541 14.045 2.54541C20.3963 2.54541 25.545 7.69413 25.545 14.0454C25.545 17.0954 24.3334 20.0205 22.1768 22.1771C20.0201 24.3338 17.095 25.5454 14.045 25.5454ZM9.66202 6.81624H18.2761C18.825 6.81624 19.27 7.22183 19.27 7.72216C19.27 8.22248 18.825 8.62807 18.2761 8.62807H14.95V10.2903C17.989 10.4444 20.3766 12.9487 20.3855 15.9916V17.1995C20.3854 17.6997 19.9799 18.1052 19.4796 18.1052C18.9793 18.1052 18.5738 17.6997 18.5737 17.1995V15.9916C18.5667 13.9478 16.9882 12.2535 14.95 12.1022V20.5574C14.95 21.0577 14.5444 21.4633 14.0441 21.4633C13.5437 21.4633 13.1382 21.0577 13.1382 20.5574V12.1022C11.1 12.2535 9.52148 13.9478 9.51448 15.9916V17.1995C9.5144 17.6997 9.10883 18.1052 8.60856 18.1052C8.1083 18.1052 7.70273 17.6997 7.70265 17.1995V15.9916C7.71158 12.9487 10.0992 10.4444 13.1382 10.2903V8.62807H9.66202C9.11309 8.62807 8.66809 8.22248 8.66809 7.72216C8.66809 7.22183 9.11309 6.81624 9.66202 6.81624Z"></path></svg>';
|
||||||
|
|
||||||
|
function Formatv(item) {
|
||||||
|
let ids,
|
||||||
|
content = item.content,
|
||||||
|
bilibilis = content.match(/{\s*bilibili\s*(.*)\s*}/g);
|
||||||
|
if (bilibilis) {
|
||||||
|
ids = bilibilis.map(item => item.replace(/{\s*bilibili\s*(.*)\s*}/, '$1').replace(/.*video\/([^\/]*).*/, '$1').trim());
|
||||||
|
}
|
||||||
|
content = content.replace(/#(.*?)\s/g, '').replace(/{.*}/g, '').replace(/\!\[(.*?)\]\((.*?)\)/g, '').replace(/```/g,'')
|
||||||
|
let text = content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]').trim();
|
||||||
|
return {content, ids, text}
|
||||||
|
}
|
||||||
|
fetch('{{ .Data.url }}/api/v1/memo?creatorId={{ .Data.user }}&tag={{ .Data.video }}').then(res => res.json()).then(async data => {
|
||||||
|
let items = [], html = ''
|
||||||
|
data.forEach(item => {
|
||||||
|
items.push(Formatv(item))
|
||||||
|
})
|
||||||
|
html+='<span class="dividingline">Videos</span>'
|
||||||
|
for (const item of items) {
|
||||||
|
html += `<div class="bilibili_item"><span>${item.text}</span>`
|
||||||
|
for (const id of item.ids) {
|
||||||
|
await fetch(`/api/bilibili?bvid=${id}`).then(res => res.json()).then(data => {
|
||||||
|
html += `<a href="https://www.bilibili.com/video/${id}/" class="bilibili_box" id="${id}">
|
||||||
|
<div class="bilibili_cover">
|
||||||
|
<img src="https://s1.hdslb.com/bfs/static/player/img/play.svg" class="play_icon no-lazyload">
|
||||||
|
<img src="${data.pic + '&h=300'}" class="no-lazyload">
|
||||||
|
<span>${data.duration}</span>
|
||||||
|
</div>
|
||||||
|
<div class="bilibili_info">
|
||||||
|
<div class="title">${data.title}</div>
|
||||||
|
<div class="desc">${data.desc}</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span>${playIcon}${data.view}</span>
|
||||||
|
<span>${likeIcon}${data.like}</span>
|
||||||
|
<span>${coinIcon}${data.coin}</span>
|
||||||
|
</div>
|
||||||
|
<div class="owner">
|
||||||
|
<span class="tip">视频</span>
|
||||||
|
<img src="${data.face + '&h=100'}" class="no-lazyload">
|
||||||
|
<span>${data.owner}</span>
|
||||||
|
</div>
|
||||||
|
</div></a>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
html += `</div>`
|
||||||
|
document.getElementById('bilibili').innerHTML = html
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/gh/jkjoy/14e/js/waterfall.min.js"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/gh/jkjoy/14e/js/imgStatus.min.js"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/gh/jkjoy/14e/js/lately.min.js"></script>
|
||||||
|
{{template "footer" .}}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{{define "footer"}}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{ end }}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{{define "header"}}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-cn">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<link rel="shortcut icon" href="public/img/favicon.ico" type="image/x-icon">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<title>Api - Starss</title>
|
||||||
|
<style>:root{--primary: {{ .Config.ThemeColor }}}</style>
|
||||||
|
<!-- 引入 layui.css -->
|
||||||
|
<link href="//unpkg.com/layui@2.9.10/dist/css/layui.css" rel="stylesheet">
|
||||||
|
<!-- 引入 layui.js -->
|
||||||
|
<script src="//unpkg.com/layui@2.9.10/dist/layui.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="layui-container">
|
||||||
|
{{ end }}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{{template "header" .}}
|
||||||
|
<div class="layui-body">
|
||||||
|
<!-- 内容主体区域 -->
|
||||||
|
<div style="padding: 15px;">
|
||||||
|
{{if ne .Err ""}}
|
||||||
|
<blockquote class="layui-elem-quote layui-text" style="border-left-color:#ff5722">
|
||||||
|
<span class="layui-badge layui-bg-red">ERR</span>
|
||||||
|
{{ .Err }}
|
||||||
|
</blockquote>
|
||||||
|
<hr class="layui-border-blue">
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
|
<blockquote class="layui-elem-quote layui-text">
|
||||||
|
<span class="layui-badge layui-bg-green">{{ .Api.Mode }}</span>
|
||||||
|
{{ .Api.Url }}
|
||||||
|
</blockquote>
|
||||||
|
<div class="layui-collapse" lay-filter="filter-collapse" lay-accordion>
|
||||||
|
<div class="layui-colla-item">
|
||||||
|
<div class="layui-colla-title">请求参数</div>
|
||||||
|
<div class="layui-colla-content">
|
||||||
|
<table lay-filter="parse-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th lay-data="{field:'name', width:150}">参数名</th>
|
||||||
|
<th lay-data="{field:'required', width:50}">必填</th>
|
||||||
|
<th lay-data="{field:'description', minWidth: 180}">介绍</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .Api.Args }}
|
||||||
|
<tr>
|
||||||
|
<td>{{ .Name }}</td>
|
||||||
|
<td>{{ if .Required }}√ {{ else }}× {{ end }}</td>
|
||||||
|
<td>{{ .Description }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="layui-border-orange">
|
||||||
|
<div class="layui-card layui-panel">
|
||||||
|
<div class="layui-card-header">
|
||||||
|
响应示例
|
||||||
|
</div>
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<pre class="layui-code code-resp" lay-options="{lang:'json'}">{{ .Api.SampleResponse }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
layui.use(function () {
|
||||||
|
var table = layui.table;
|
||||||
|
var element = layui.element;
|
||||||
|
element.on('collapse(filter-collapse)', function (data) {
|
||||||
|
if (data.show) {
|
||||||
|
table.init('parse-table', {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layui.code({
|
||||||
|
elem: '.code-resp'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{template "footer" .}}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Title</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -32,7 +32,10 @@
|
|||||||
{{ if ne .Data.MusicId "" }}
|
{{ if ne .Data.MusicId "" }}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/meting@1.2.0/dist/Meting.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/meting@1.2.0/dist/Meting.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/gh/kaygb/kaygb@master/js/v3.js"></script>
|
<script>
|
||||||
|
var windowWidth = $(window).width();
|
||||||
|
var meting_api = 'https://api.injahow.cn/meting/?server=:server&type=:type&id=:id';
|
||||||
|
</script>
|
||||||
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
|
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/gh/kaygb/kaygb@master/layer/layer.js"></script>
|
<script src="https://cdn.jsdelivr.net/gh/kaygb/kaygb@master/layer/layer.js"></script>
|
||||||
<div id="aplayer" class="aplayer" data-order="random" data-id="{{ .Data.MusicId }}" data-server="netease"
|
<div id="aplayer" class="aplayer" data-order="random" data-id="{{ .Data.MusicId }}" data-server="netease"
|
||||||
@@ -44,12 +47,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('article').innerHTML = marked({{ .Data.Body }});
|
document.getElementById('article').innerHTML = marked({{ .Data.Body }});
|
||||||
</script>
|
</script>
|
||||||
<script src="/public/js/prism.js"></script>
|
<script src="/public/js/prism.js"></script>
|
||||||
{{if ne .Config.UtterancesRepo ""}}
|
{{template "reviews" .}}
|
||||||
<script src="https://utteranc.es/client.js" repo="{{ .Config.UtterancesRepo }}" issue-term="[{{ .Data.Title }}]"
|
|
||||||
theme="github-light" crossorigin="anonymous" async>
|
|
||||||
</script>
|
|
||||||
{{end}}
|
|
||||||
{{template "footer" .}}
|
{{template "footer" .}}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<p>Action:</p>
|
<p>Action:</p>
|
||||||
<div class="item-content">
|
<div class="item-content">
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<a href="{{ .Config.DashboardEntrance }}?action=updateArticle">更新文章</a>
|
<a href="{{ .Config.Dashboard }}?action=updateArticle">更新文章</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ if .Data.msg }}
|
{{ if .Data.msg }}
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<span class="action-tip">提示:更新文章会执行git pull命令,和你的仓库网络有关,等待时间可能会稍长。</span>
|
<span class="action-tip">提示:更新文章会执行git pull命令,和你的仓库网络有关,等待时间可能会稍长。</span>
|
||||||
<script>
|
<script>
|
||||||
function selectColor(index) {
|
function selectColor(index) {
|
||||||
window.location.href = '{{ .Config.DashboardEntrance }}?theme=' + index
|
window.location.href = '{{ .Config.Dashboard }}?theme=' + index
|
||||||
}
|
}
|
||||||
function searchArticle() {
|
function searchArticle() {
|
||||||
var searchKey = document.getElementById('search-input').value;
|
var searchKey = document.getElementById('search-input').value;
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
{{define "footer"}}
|
{{define "footer"}}
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
|
{{if ne .Config.Icp ""}}
|
||||||
<span>{{ .Config.Icp }}</span><span class="footer-divider"> | </span>
|
<span>{{ .Config.Icp }}</span><span class="footer-divider"> | </span>
|
||||||
|
{{ end }}
|
||||||
<span>
|
<span>
|
||||||
© 2018 - <script>document.write(new Date().getFullYear())</script> {{ .Config.Author }}
|
© 2018 - <script>document.write(new Date().getFullYear())</script> {{ .Config.Author }}
|
||||||
Powered By <a href="{{ .Config.AppRepository }}" target="_blank"> {{ .Config.AppName }} </a>
|
Powered By <a href="{{ .Config.AppRepository }}" target="_blank"> {{ .Config.AppName }} </a>
|
||||||
</span>
|
</span>
|
||||||
<span id="busuanzi_container_site_pv" style='display:none'>
|
<span id="busuanzi_container_site_pv" style='display:none'>
|
||||||
总访问量<span id="busuanzi_value_site_pv"></span>次
|
当月访问量<span id="busuanzi_value_site_pv"></span>次
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
@@ -10,9 +10,12 @@
|
|||||||
<meta name="description" content="{{ .Config.HtmlDescription }}" />
|
<meta name="description" content="{{ .Config.HtmlDescription }}" />
|
||||||
<title>{{ .Title }} - {{ .Config.SiteName }}</title>
|
<title>{{ .Title }} - {{ .Config.SiteName }}</title>
|
||||||
<style>:root{--primary: {{ .Config.ThemeColor }}}</style>
|
<style>:root{--primary: {{ .Config.ThemeColor }}}</style>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
|
||||||
<link rel="stylesheet" href="/public/css/app.css">
|
<link rel="stylesheet" href="/public/css/app.css">
|
||||||
<link href="/public/css/prism.css" rel="stylesheet" />
|
<link href="/public/css/prism.css" rel="stylesheet" />
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.css">
|
|
||||||
<script src="/public/js/marked.min.js"></script>
|
<script src="/public/js/marked.min.js"></script>
|
||||||
<script async src="https://umami.starss.cc/script.js" data-website-id="594ff1a5-e8ce-47b1-8558-88cb3f9d7704"></script>
|
<script async src="https://umami.starss.cc/script.js" data-website-id="594ff1a5-e8ce-47b1-8558-88cb3f9d7704"></script>
|
||||||
<script async src="https://busuanzi.icodeq.com/busuanzi.pure.mini.js"></script>
|
<script async src="https://busuanzi.icodeq.com/busuanzi.pure.mini.js"></script>
|
||||||
@@ -40,31 +43,11 @@
|
|||||||
if(key)return paramsData.hasOwnProperty(key) ? paramsData[key] : null;
|
if(key)return paramsData.hasOwnProperty(key) ? paramsData[key] : null;
|
||||||
return paramsData;
|
return paramsData;
|
||||||
}
|
}
|
||||||
function searchArticle() {
|
|
||||||
var searchKey = document.getElementById('search-input').value;
|
|
||||||
searchKey = searchKey.replace(/^\s+|\s+$/g,"")
|
|
||||||
if("" === searchKey){return}
|
|
||||||
|
|
||||||
window.location.href = '/?search=' + searchKey
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="theme{{ .Config.ThemeColor }}">
|
<body class="theme{{ .Config.ThemeColor }}">
|
||||||
<nav class="head">
|
<header>
|
||||||
<div class="container head-content">
|
{{template "nav" .}}
|
||||||
<div class="logo">{{ .Config.SiteName }}</div>
|
</header>
|
||||||
<div class="search-box">
|
|
||||||
<input id="search-input" class="search-input" type="text" placeholder="搜索...">
|
|
||||||
<img onclick="searchArticle()" class="search-icon" src="/public/img/search.svg" alt="">
|
|
||||||
</div>
|
|
||||||
<div class="nav">
|
|
||||||
<a href="/blog">Blog</a>
|
|
||||||
<a href="/categories">Categories</a>
|
|
||||||
{{range $nav := .Navs }}
|
|
||||||
<a href="/extra-nav?name={{ $nav.Title }}">{{ $nav.Title }}</a>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<div class="post-warp">
|
<div class="post-warp">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
{{define "nav"}}
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: var(--primary,#673ab7);">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="/blog" style="font-size: 120%">{{ .Config.SiteName }}</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarMain" aria-controls="navbarMain" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarMain">
|
||||||
|
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" aria-current="page" href="/blog">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/categories">Categories</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
More
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
|
{{if ne .Config.MemosAlbumTag ""}}
|
||||||
|
<li><a class="dropdown-item" href="/memos?name=album">Album</a></li>
|
||||||
|
{{ end }}
|
||||||
|
{{if ne .Config.MemosTalkTag ""}}
|
||||||
|
<li><a class="dropdown-item" href="/memos?name=talk">Talk</a></li>
|
||||||
|
{{ end }}
|
||||||
|
{{if ne .Config.MemosReaderTag ""}}
|
||||||
|
<li><a class="dropdown-item" href="/memos?name=reader">Reader</a></li>
|
||||||
|
{{ end }}
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
{{range $nav := .Navs }}
|
||||||
|
<li><a class="dropdown-item" href="/extra-nav?name={{ $nav.Title }}">{{ $nav.Title }}</a></li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<!-- <li class="nav-item">-->
|
||||||
|
<!-- <a class="nav-link disabled">Disabled</a>-->
|
||||||
|
<!-- </li>-->
|
||||||
|
</ul>
|
||||||
|
<form class="d-flex">
|
||||||
|
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
|
||||||
|
<button class="btn btn-outline-success" type="submit"><img src="/public/img/search.svg" alt="Search" style=""></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<script>
|
||||||
|
document.querySelector('form').addEventListener('submit', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const searchQuery = document.querySelector('.form-control').value.trim();
|
||||||
|
|
||||||
|
if (searchQuery) {
|
||||||
|
const searchUrl = `/?search=${encodeURIComponent(searchQuery)}`;
|
||||||
|
|
||||||
|
window.location.href = searchUrl;
|
||||||
|
} else {
|
||||||
|
console.log('请输入搜索关键词');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const btn = document.querySelector('.btn-outline-success');
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutationsList, observer) => {
|
||||||
|
for (let mutation of mutationsList) {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
|
||||||
|
btn.style.borderColor= 'invert(100%)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(btn, { attributes: true });
|
||||||
|
</script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
{{ end }}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{{define "reviews"}}
|
||||||
|
{{if ne .Config.UtterancesRepo ""}}
|
||||||
|
<script src="https://utteranc.es/client.js" repo="{{ .Config.UtterancesRepo }}" issue-term="[{{ .Data.Title }}]"
|
||||||
|
theme="github-light" crossorigin="anonymous" async>
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if ne .Config.Gitalk_Repo ""}}
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>
|
||||||
|
<div id="gitalk-container"></div>
|
||||||
|
<script>
|
||||||
|
var gitalk = new Gitalk({
|
||||||
|
title: "{{ .Data.Title }}",
|
||||||
|
clientID: "{{ .Config.Gitalk_ClientID }}",
|
||||||
|
clientSecret: "{{ .Config.Gitalk_ClientSecret }}",
|
||||||
|
repo: "{{ .Config.Gitalk_Repo }}",
|
||||||
|
owner: "{{ .Config.Gitalk_Owner }}",
|
||||||
|
admin: "[{{ .Config.Gitalk_Owner }}]",
|
||||||
|
id: "{{ .Data.ShortUrl }}", // Ensure uniqueness and length less than 50
|
||||||
|
distractionFreeMode: false // Facebook-like distraction free mode
|
||||||
|
})
|
||||||
|
gitalk.render('gitalk-container')
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
|
{{ end }}
|
||||||
@@ -41,7 +41,6 @@ a{
|
|||||||
}
|
}
|
||||||
|
|
||||||
a:hover{
|
a:hover{
|
||||||
color: #673ab7;
|
|
||||||
color: var(--primary,#673ab7);
|
color: var(--primary,#673ab7);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -61,7 +60,6 @@ blockquote::before{
|
|||||||
content: '“';
|
content: '“';
|
||||||
font-family: fantasy;
|
font-family: fantasy;
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
color: #673ab7;
|
|
||||||
color: var(--primary,#673ab7);
|
color: var(--primary,#673ab7);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,35 +78,6 @@ blockquote::before{
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.container {
|
|
||||||
width: auto;
|
|
||||||
max-width: 1200px;
|
|
||||||
text-align: center;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.head{
|
|
||||||
width: 100%;
|
|
||||||
height: 64px;
|
|
||||||
}
|
|
||||||
.head-content{
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
@media screen and (max-width: 860px){
|
|
||||||
.head-content{
|
|
||||||
padding-top: 10px;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav a{
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-title {
|
.sub-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -248,7 +217,6 @@ hr:after{
|
|||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
.pagination li a:hover,.pagination .active a{
|
.pagination li a:hover,.pagination .active a{
|
||||||
border-bottom: 3px solid #673ab7;
|
|
||||||
border-bottom: 3px solid var(--primary,#673ab7);
|
border-bottom: 3px solid var(--primary,#673ab7);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,8 +251,6 @@ hr:after{
|
|||||||
padding: 4px 20px;
|
padding: 4px 20px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #673ab7;
|
|
||||||
border: 2px solid #673ab7;
|
|
||||||
background: var(--primary,#673ab7);
|
background: var(--primary,#673ab7);
|
||||||
border: 2px solid var(--primary,#673ab7);
|
border: 2px solid var(--primary,#673ab7);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -305,7 +271,6 @@ hr:after{
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
.articles li .title:hover{
|
.articles li .title:hover{
|
||||||
color: #673ab7;
|
|
||||||
color: var(--primary,#673ab7);
|
color: var(--primary,#673ab7);
|
||||||
}
|
}
|
||||||
.article-info{
|
.article-info{
|
||||||
@@ -369,19 +334,19 @@ hr:after{
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.search-box{
|
.search-box{
|
||||||
height: 40px;
|
height: 36px;
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 2px;
|
border-radius: 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border: 1px solid #673ab7;
|
border: 2px solid var(--primary,#673ab7);
|
||||||
border: 1px solid var(--primary,#673ab7);
|
|
||||||
width: 320px;
|
width: 320px;
|
||||||
}
|
}
|
||||||
.search-input{
|
.search-input{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
border: none;
|
border: none;
|
||||||
|
border-radius: 16px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
@@ -434,7 +399,6 @@ hr:after{
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
opacity: .8;
|
opacity: .8;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background-color: #673ab7;
|
|
||||||
background-color: var(--primary,#673ab7);
|
background-color: var(--primary,#673ab7);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
@@ -444,7 +408,6 @@ hr:after{
|
|||||||
}
|
}
|
||||||
.action-tip{
|
.action-tip{
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #673ab7;
|
|
||||||
color: var(--primary,#673ab7);
|
color: var(--primary,#673ab7);
|
||||||
}
|
}
|
||||||
.action-msg{
|
.action-msg{
|
||||||
@@ -463,7 +426,6 @@ hr:after{
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
min-width: 288px;
|
min-width: 288px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: #673ab7;
|
|
||||||
background-color: var(--primary,#673ab7);
|
background-color: var(--primary,#673ab7);
|
||||||
box-shadow: 0 3px 5px -1px rgba(0,0,0,0.2), 0 6px 10px 0 rgba(0,0,0,0.14), 0 1px 18px 0 rgba(0,0,0,0.12);
|
box-shadow: 0 3px 5px -1px rgba(0,0,0,0.2), 0 6px 10px 0 rgba(0,0,0,0.14), 0 1px 18px 0 rgba(0,0,0,0.12);
|
||||||
}
|
}
|
||||||
@@ -475,3 +437,32 @@ hr:after{
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-outline-success {
|
||||||
|
border: 2px solid var(--primary, #673ab7);
|
||||||
|
filter: invert(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-success img{
|
||||||
|
width:26px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dividingline{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--primary, #673ab7);
|
||||||
|
}
|
||||||
|
.dividingline::before,.dividingline::after{
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
height: 1px;
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
.dividingline::before{
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.dividingline::after{
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
|
||||||
|
<!-- Todo: add easing -->
|
||||||
|
<svg width="57" height="57" viewBox="0 0 57 57" xmlns="http://www.w3.org/2000/svg" stroke="#fff">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<g transform="translate(1 1)" stroke-width="2">
|
||||||
|
<circle cx="5" cy="50" r="5">
|
||||||
|
<animate attributeName="cy"
|
||||||
|
begin="0s" dur="2.2s"
|
||||||
|
values="50;5;50;50"
|
||||||
|
calcMode="linear"
|
||||||
|
repeatCount="indefinite" />
|
||||||
|
<animate attributeName="cx"
|
||||||
|
begin="0s" dur="2.2s"
|
||||||
|
values="5;27;49;5"
|
||||||
|
calcMode="linear"
|
||||||
|
repeatCount="indefinite" />
|
||||||
|
</circle>
|
||||||
|
<circle cx="27" cy="5" r="5">
|
||||||
|
<animate attributeName="cy"
|
||||||
|
begin="0s" dur="2.2s"
|
||||||
|
from="5" to="5"
|
||||||
|
values="5;50;50;5"
|
||||||
|
calcMode="linear"
|
||||||
|
repeatCount="indefinite" />
|
||||||
|
<animate attributeName="cx"
|
||||||
|
begin="0s" dur="2.2s"
|
||||||
|
from="27" to="27"
|
||||||
|
values="27;49;5;27"
|
||||||
|
calcMode="linear"
|
||||||
|
repeatCount="indefinite" />
|
||||||
|
</circle>
|
||||||
|
<circle cx="49" cy="50" r="5">
|
||||||
|
<animate attributeName="cy"
|
||||||
|
begin="0s" dur="2.2s"
|
||||||
|
values="50;50;5;50"
|
||||||
|
calcMode="linear"
|
||||||
|
repeatCount="indefinite" />
|
||||||
|
<animate attributeName="cx"
|
||||||
|
from="49" to="49"
|
||||||
|
begin="0s" dur="2.2s"
|
||||||
|
values="49;5;27;49"
|
||||||
|
calcMode="linear"
|
||||||
|
repeatCount="indefinite" />
|
||||||
|
</circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,287 @@
|
|||||||
|
{{template "header" .}}
|
||||||
|
<div class="sub-title">- {{ .Title }} -</div>
|
||||||
|
<style>
|
||||||
|
div#page {
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme=dark] #twikoo .tk-content, #twikoo .tk-content {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_item, .tk-expand, .tk-comments-container > .tk-comment, .tk-submit:nth-child(1) {
|
||||||
|
border: 1px solid #e0e3ed;
|
||||||
|
box-shadow: 0 5px 10px rgb(189 189 189 / 10%);
|
||||||
|
transition: all .3s ease-in-out;
|
||||||
|
border-radius: 12px
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_item:hover, .tk-comments-container > .tk-comment:hover, .tk-submit:nth-child(1):hover {
|
||||||
|
border-color: var(--primary, #673ab7)
|
||||||
|
}
|
||||||
|
|
||||||
|
.tk-submit {
|
||||||
|
padding: 20px 10px 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.tk-comments-container > .tk-comment {
|
||||||
|
padding: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
#talk {
|
||||||
|
margin-top: 1rem
|
||||||
|
}
|
||||||
|
|
||||||
|
#talk .loading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--primary, #673ab7)
|
||||||
|
}
|
||||||
|
|
||||||
|
#talk .loading img {
|
||||||
|
height: 60px;
|
||||||
|
width: 60px
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
margin: 0 !important;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_bottom, .talk_meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 1.5
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_bottom {
|
||||||
|
justify-content: space-between
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
span.talk_nick {
|
||||||
|
color: #6dbdc3;
|
||||||
|
font-size: 1.2rem
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
span.talk_date {
|
||||||
|
opacity: .6
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_content {
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-top: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.zone_imgbox a {
|
||||||
|
display: block;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: var(--w);
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone_imgbox img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0 !important;
|
||||||
|
object-fit: cover
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_bottom {
|
||||||
|
opacity: .9
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_bottom .icon {
|
||||||
|
color: var(--primary, #673ab7);
|
||||||
|
float: right;
|
||||||
|
transition: all .3s
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_bottom .icon:hover {
|
||||||
|
color: #49b1f5
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_content > a {
|
||||||
|
margin: 0 3px;
|
||||||
|
color: #ff7d73 !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.talk_content > a:hover {
|
||||||
|
text-decoration: none !important;
|
||||||
|
color: #ff5143 !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.limit {
|
||||||
|
transition: all .3s ease-in-out;
|
||||||
|
color: rgba(76, 73, 72, 0.6);
|
||||||
|
display: none;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.limit button {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 9pt;
|
||||||
|
background: var(--primary, #673ab7);
|
||||||
|
border: 1px solid lightgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme=dark] .limit {
|
||||||
|
color: rgba(255, 255, 255, 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 900px) {
|
||||||
|
.zone_imgbox {
|
||||||
|
--w: calc(33% - 5px)
|
||||||
|
}
|
||||||
|
|
||||||
|
#talk {
|
||||||
|
margin: 10px 3px 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#post-comment {
|
||||||
|
margin: 0 3px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.zone_imgbox {
|
||||||
|
gap: 6px
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone_imgbox {
|
||||||
|
--w: calc(50% - 3px)
|
||||||
|
}
|
||||||
|
|
||||||
|
span.talk_date {
|
||||||
|
font-size: 14px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadBtn {
|
||||||
|
display: flex;
|
||||||
|
background: var(--primary, #673ab7);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin: 2px 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12pt;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 2px 2px 0 rgb(0 40 56 / 15%);
|
||||||
|
font-weight: bold;
|
||||||
|
transition: box-shadow 300ms ease-in;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="talk">
|
||||||
|
<div class='loading'><img src="/public/img/loading.svg" alt="加载中...">
|
||||||
|
<a>加载中...</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='limit'><button onclick='loadMemo(-1)'>上一页</button> <button onclick='loadMemo(1)'>下一页</button></div>
|
||||||
|
<script>
|
||||||
|
function Format(item) {
|
||||||
|
let date = getTime(new Date(item.createdTs * 1000).toString()),
|
||||||
|
content = item.content,
|
||||||
|
id = item.id,
|
||||||
|
name = item.name,
|
||||||
|
codes = content.match(/```(.*)```/igs);
|
||||||
|
content = content.replace(/#(.*?)\s/g, '').replace(/{.*}/g, '').replace(/\!\[(.*?)\]\((.*?)\)/g, '').replace(/```(.*)```/igs, '')
|
||||||
|
let text = `<a href="{{ .Data.url }}/m/${name}">原文地址</a>`;
|
||||||
|
if (codes) codes.forEach(item => {
|
||||||
|
content += `<div style="display: flex;"><a type="button" class="downloadBtn" href="legado://import/auto?src=${window.location.origin}/api/memos?id=${id}">一键导入</a>`
|
||||||
|
content += ` <a type="button" class="downloadBtn" href="${window.location.origin}/api/memos?id=${id}">下载文件</a></div>`
|
||||||
|
// content += marked(item);
|
||||||
|
})
|
||||||
|
return {content, date, text}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTime(time) {
|
||||||
|
var nol = function (h) {
|
||||||
|
return h > 9 ? h : '0' + h
|
||||||
|
}
|
||||||
|
var now = new Date()
|
||||||
|
var yesterday = new Date(now.getTime() - (1000 * 60 * 60 * 24)).toLocaleDateString()
|
||||||
|
var twoDaysAgo = new Date(now.getTime() - 2 * (1000 * 60 * 60 * 24)).toLocaleDateString()
|
||||||
|
let d = new Date(time),
|
||||||
|
ls = [d.getFullYear(), nol(d.getMonth() + 1), nol(d.getDate()), nol(d.getHours()), nol(d.getMinutes()), nol(d.getSeconds())],
|
||||||
|
day = d.toLocaleDateString()
|
||||||
|
if (day === now.toLocaleDateString()) {
|
||||||
|
return '今天 ' + ls[3] + ':' + ls[4]
|
||||||
|
} else if (day === yesterday) {
|
||||||
|
return '昨天 ' + ls[3] + ':' + ls[4]
|
||||||
|
} else if (day === twoDaysAgo) {
|
||||||
|
return '前天 ' + ls[3] + ':' + ls[4]
|
||||||
|
} else {
|
||||||
|
if (now.getFullYear() == ls[0]) return ls[1] + '月' + ls[2] + '日 ' + ls[3] + ':' + ls[4]
|
||||||
|
else return ls[0] + '年' + ls[1] + '月' + ls[2] + '日 ' + ls[3] + ':' + ls[4]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = 0;
|
||||||
|
let limit =30;
|
||||||
|
function loadMemo(p) {
|
||||||
|
offset += p*limit;
|
||||||
|
if (offset < 1) {
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
fetch('{{ .Data.url }}/api/v1/memo?creatorId={{ .Data.user }}&tag={{ .Data.tag }}&limit='+limit+'&offset=' + offset)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
let items = [], html = '';
|
||||||
|
data.forEach(item => {
|
||||||
|
items.push(Format(item));
|
||||||
|
});
|
||||||
|
if (items.length === limit) {
|
||||||
|
document.querySelector('.limit').style.display = 'flex';
|
||||||
|
}
|
||||||
|
items.forEach(item => {
|
||||||
|
html += '<div class="talk_item"><div class="talk_meta">';
|
||||||
|
html += "<img class=\"no-lightbox no-lazyload avatar\" src=\"https://q1.qlogo.cn/g?b=qq&nk={{ .Data.qq }}&s=5\">";
|
||||||
|
html += `<div class="info"><span class="talk_nick">{{ .Data.author }}</span><span class="talk_date">${item.date}</span>`;
|
||||||
|
html += `</div></div><div class="talk_content">${item.content}</div>${item.text}</div>`;
|
||||||
|
});
|
||||||
|
if(items.length!==0){
|
||||||
|
document.getElementById('talk').innerHTML = '';
|
||||||
|
document.getElementById('talk').insertAdjacentHTML('afterbegin', html);
|
||||||
|
waterfall('#talk');
|
||||||
|
}else {
|
||||||
|
document.getElementById('talk').innerHTML = '<span>无数据</span>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
loadMemo(0);
|
||||||
|
</script>
|
||||||
|
<script src="/public/js/prism.js"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/gh/jkjoy/14e/js/waterfall.min.js"></script>
|
||||||
|
{{template "footer" .}}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
{{template "header" .}}
|
||||||
|
<div class="sub-title">- {{ .Title }} -</div>
|
||||||
|
<style>
|
||||||
|
div#page{background:none;border:0;padding:0}[data-theme=dark] #twikoo .tk-content,#twikoo .tk-content{padding:0;background:transparent}.talk_item,.tk-expand,.tk-comments-container>.tk-comment,.tk-submit:nth-child(1){border:1px solid #e0e3ed;box-shadow:0 5px 10px rgb(189 189 189 / 10%);transition:all .3s ease-in-out;border-radius:12px}.talk_item:hover,.tk-comments-container>.tk-comment:hover,.tk-submit:nth-child(1):hover{border-color:var(--primary,#673ab7)}.tk-submit{padding:20px 10px 0}.tk-comments-container>.tk-comment{padding:15px}#talk{margin-top:1rem}#talk .loading{display:flex;align-items:center;justify-content:center;flex-direction:column;margin:0 auto;width:100px;height:100px;border-radius:8px;background-color:var(--primary,#673ab7)}#talk .loading img{height:60px;width:60px}.talk_item{display:flex;flex-direction:column;padding:20px;margin-bottom:15px}.avatar{margin:0 !important;width:60px;height:60px;border-radius:10px}.talk_bottom,.talk_meta{display:flex;align-items:center;width:100%;line-height:1.5}.talk_bottom{justify-content:space-between}.info{display:flex;flex-direction:column;margin-left:10px}span.talk_nick{color:#6dbdc3;font-size:1.2rem}svg.is-badge.icon{width:15px;margin-left:5px;padding-top:3px}span.talk_date{opacity:.6}.talk_content{line-height:1.5;margin-top:10px}.zone_imgbox{display:flex;flex-wrap:wrap;--w:calc(25% - 8px);gap:10px;margin-top:5px}.zone_imgbox a{display:block;border-radius:12px;width:var(--w);aspect-ratio:1/1;position:relative}.zone_imgbox img{width:100%;height:100%;margin:0 !important;object-fit:cover}.talk_bottom{opacity:.9}.talk_bottom .icon{color:var(--primary,#673ab7);float:right;transition:all .3s}.talk_bottom .icon:hover{color:#49b1f5}span.talk_tag{font-size:14px}.talk_content>a{margin:0 3px;color:#ff7d73 !important}.talk_content>a:hover{text-decoration:none !important;color:#ff5143 !important}.limit{transition:all .3s ease-in-out;color:rgba(76,73,72,0.6)}[data-theme=dark] .limit{color:rgba(255,255,255,0.5)}.limit{display:none;text-align:center;margin-top:20px;color:var(--primary,#673ab7)}@media screen and (max-width:900px){.zone_imgbox{--w:calc(33% - 5px)}#talk{margin:10px 3px 0}#post-comment{margin:0 3px}}@media screen and (max-width:768px){.zone_imgbox{gap:6px}.zone_imgbox{--w:calc(50% - 3px)}span.talk_date{font-size:14px}}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="talk">
|
||||||
|
<div class='loading'><img src="/public/img/loading.svg" alt="加载中...">
|
||||||
|
<a>加载中...</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="limit">- 只展示最近30条说说 -</div>
|
||||||
|
<script>
|
||||||
|
function Format(item) {
|
||||||
|
let date = getTime(new Date(item.createdTs * 1000).toString()),
|
||||||
|
content = item.content,
|
||||||
|
imgs = content.match(/!\[.*\]\(.*?\)/g),
|
||||||
|
musics = content.match(/{\s*music\s*(.*)\s*}/g),
|
||||||
|
videos = content.match(/{\s*bilibili\s*(.*)\s*}/g);
|
||||||
|
codes = content.match(/```(.*)```/igs);
|
||||||
|
if (imgs) imgs = imgs.map(item => {
|
||||||
|
return item.replace(/!\[.*\]\((.*?)\)/, '$1')
|
||||||
|
})
|
||||||
|
if (item.resourceList.length) {
|
||||||
|
if (!imgs) imgs = []
|
||||||
|
item.resourceList.forEach(t => {
|
||||||
|
if (t.externalLink) imgs.push(t.externalLink)
|
||||||
|
else imgs.push(`${url}/o/r/${t.id}/${t.publicId}/${t.filename}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
content = content.replace(/#(.*?)\s/g, '').replace(/{.*}/g, '').replace(/\!\[(.*?)\]\((.*?)\)/g, '').replace(/```(.*)```/igs, '')
|
||||||
|
let text = content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]').trim();
|
||||||
|
content = content.replace(/\[(.*?)\]\((.*?)\)/g, `<a href="$2">@$1</a>`);
|
||||||
|
if (imgs) {
|
||||||
|
content += `<div class="zone_imgbox">`
|
||||||
|
imgs.forEach(e => content += `<a href="${e}" data-fancybox="gallery" class="fancybox" data-thumb="${e}"><img class="no-lazyload talk-img" src="${e}"></a>`)
|
||||||
|
content += '</div>'
|
||||||
|
}
|
||||||
|
if (musics) musics.forEach(item => {
|
||||||
|
content += `<meting-js auto="${item.replace(/{\s*music\s*(.*)\s*}/, '$1')}" theme="var(--leonus-main)" preload="metadata"></meting-js>`
|
||||||
|
})
|
||||||
|
if (videos) videos.forEach(item => {
|
||||||
|
content += `<div style="position: relative; padding: 30% 45%;margin-top: 10px;margin-bottom: 10px"><iframe style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;" src="//player.bilibili.com/player.html?autoplay=0&bvid=${item.replace(/{\s*bilibili\s*(.*)\s*}/, '$1').replace(/.*video\/([^\/]*).*/, '$1')}" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe></div>`
|
||||||
|
})
|
||||||
|
if (codes) codes.forEach(item => {
|
||||||
|
content +=marked(item);
|
||||||
|
})
|
||||||
|
return {content, date, text}
|
||||||
|
}
|
||||||
|
function getTime(time) {
|
||||||
|
var nol = function (h) {
|
||||||
|
return h > 9 ? h : '0' + h
|
||||||
|
}
|
||||||
|
var now = new Date()
|
||||||
|
var yesterday = new Date(now.getTime() - (1000 * 60 * 60 * 24)).toLocaleDateString()
|
||||||
|
var twoDaysAgo = new Date(now.getTime() - 2 * (1000 * 60 * 60 * 24)).toLocaleDateString()
|
||||||
|
let d = new Date(time),
|
||||||
|
ls = [d.getFullYear(), nol(d.getMonth() + 1), nol(d.getDate()), nol(d.getHours()), nol(d.getMinutes()), nol(d.getSeconds())],
|
||||||
|
day = d.toLocaleDateString()
|
||||||
|
if (day === now.toLocaleDateString()) {
|
||||||
|
return '今天 ' + ls[3] + ':' + ls[4]
|
||||||
|
} else if (day === yesterday) {
|
||||||
|
return '昨天 ' + ls[3] + ':' + ls[4]
|
||||||
|
} else if (day === twoDaysAgo) {
|
||||||
|
return '前天 ' + ls[3] + ':' + ls[4]
|
||||||
|
} else {
|
||||||
|
if (now.getFullYear() == ls[0]) return ls[1] + '月' + ls[2] + '日 ' + ls[3] + ':' + ls[4]
|
||||||
|
else return ls[0] + '年' + ls[1] + '月' + ls[2] + '日 ' + ls[3] + ':' + ls[4]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('{{ .Data.url }}/api/v1/memo?creatorId={{ .Data.user }}&tag={{ .Data.tag }}&limit=30').then(res => res.json()).then(data => {
|
||||||
|
let items = [], html = ''
|
||||||
|
data.forEach(item => {
|
||||||
|
items.push(Format(item))
|
||||||
|
})
|
||||||
|
if (items.length == 30) document.querySelector('.limit').style.display = 'block'
|
||||||
|
items.forEach(item => {
|
||||||
|
html += '<div class="talk_item"><div class="talk_meta">';
|
||||||
|
html += "<img class=\"no-lightbox no-lazyload avatar\" src=\"https://q1.qlogo.cn/g?b=qq&nk={{ .Data.qq }}&s=5\">";
|
||||||
|
html += `<div class="info"><span class="talk_nick">{{ .Data.author }}</span><span class="talk_date">${item.date}</span>`;
|
||||||
|
html += `</div></div><div class="talk_content">${item.content}</div></div>`;
|
||||||
|
})
|
||||||
|
document.getElementById('talk').innerHTML = html;
|
||||||
|
imgStatus.watch('.talk-img', () => { waterfall('#talk')});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/gh/jkjoy/14e/js/waterfall.min.js"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/gh/jkjoy/14e/js/imgStatus.min.js"></script>
|
||||||
|
{{template "footer" .}}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
{{define "nav"}}
|
||||||
|
<style>
|
||||||
|
|
||||||
|
#bilibili {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bilibili .loading {
|
||||||
|
/*vertical-align: middle;*/
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--primary, #673ab7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#bilibili .loading img {
|
||||||
|
height: 60px;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box {
|
||||||
|
display: flex;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border: 1px solid #e0e3ed;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--primary, #673ab7) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
transition: 0.3s;
|
||||||
|
maxWidth768()
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box:hover {
|
||||||
|
border-color: #4976f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_cover {
|
||||||
|
width: 234px;
|
||||||
|
position: relative;
|
||||||
|
maxWidth768()
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_cover img {
|
||||||
|
width: 100%;
|
||||||
|
filter: none;
|
||||||
|
margin: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_cover .play_icon {
|
||||||
|
position: absolute;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
opacity: 0.8;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_cover span {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
right: 5px;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 1px 3px #7a7a7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info {
|
||||||
|
padding: 10px 10px 10px 18px;
|
||||||
|
line-height: 1;
|
||||||
|
width: calc(100% - 200px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
maxWidth768()
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .desc {
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 2px 0 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
maxWidth768()
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .stat {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .stat svg {
|
||||||
|
margin-right: 3px;
|
||||||
|
font-size: 18px;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .stat svg path {
|
||||||
|
fill: var(--primary, #673ab7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .stat span {
|
||||||
|
margin-right: 10px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .owner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .owner .tip {
|
||||||
|
color: #f69;
|
||||||
|
border: 1px solid;
|
||||||
|
padding: 3px 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bilibili_box .bilibili_info .owner img {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
object-fit: cover;
|
||||||
|
margin: 0 5px 0 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme='light'] .bilibili_box .bilibili_info .stat svg, [data-theme='dark'] .bilibili_cover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="bilibili">
|
||||||
|
<div class='loading'><img src="/public/img/loading.svg" alt="加载中...">
|
||||||
|
<a>加载中...</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let playIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.67735 4.2798C5.98983 4.1725 7.85812 4.0625 10 4.0625C12.1421 4.0625 14.0105 4.17252 15.323 4.27983C16.2216 4.3533 16.9184 5.04049 16.9989 5.9318C17.0962 7.00837 17.1875 8.43614 17.1875 10C17.1875 11.5639 17.0962 12.9916 16.9989 14.0682C16.9184 14.9595 16.2216 15.6467 15.323 15.7202C14.0105 15.8275 12.1421 15.9375 10 15.9375C7.85812 15.9375 5.98983 15.8275 4.67735 15.7202C3.77861 15.6467 3.08174 14.9593 3.00119 14.0678C2.90388 12.9908 2.8125 11.5627 2.8125 10C2.8125 8.43727 2.90388 7.00924 3.00119 5.93221C3.08174 5.04067 3.77861 4.35327 4.67735 4.2798ZM10 2.8125C7.81674 2.8125 5.9136 2.92456 4.5755 3.03395C3.07738 3.15643 1.8921 4.31616 1.75626 5.81973C1.65651 6.92379 1.5625 8.39058 1.5625 10C1.5625 11.6094 1.65651 13.0762 1.75626 14.1803C1.8921 15.6838 3.07738 16.8436 4.5755 16.966C5.9136 17.0754 7.81674 17.1875 10 17.1875C12.1835 17.1875 14.0868 17.0754 15.4249 16.966C16.9228 16.8436 18.108 15.6841 18.2438 14.1807C18.3435 13.077 18.4375 11.6105 18.4375 10C18.4375 8.38948 18.3435 6.92296 18.2438 5.81931C18.108 4.31588 16.9228 3.15645 15.4249 3.03398C14.0868 2.92458 12.1835 2.8125 10 2.8125ZM12.1876 10.722C12.7431 10.4013 12.7431 9.59941 12.1876 9.27866L9.06133 7.47373C8.50577 7.15298 7.81133 7.55392 7.81133 8.19542V11.8053C7.81133 12.4468 8.50577 12.8477 9.06133 12.527L12.1876 10.722Z" fill="#9499A0"/></svg>`
|
||||||
|
let likeIcon = `<svg width="36" height="36" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg" class="icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M9.77234 30.8573V11.7471H7.54573C5.50932 11.7471 3.85742 13.3931 3.85742 15.425V27.1794C3.85742 29.2112 5.50932 30.8573 7.54573 30.8573H9.77234ZM11.9902 30.8573V11.7054C14.9897 10.627 16.6942 7.8853 17.1055 3.33591C17.2666 1.55463 18.9633 0.814421 20.5803 1.59505C22.1847 2.36964 23.243 4.32583 23.243 6.93947C23.243 8.50265 23.0478 10.1054 22.6582 11.7471H29.7324C31.7739 11.7471 33.4289 13.402 33.4289 15.4435C33.4289 15.7416 33.3928 16.0386 33.3215 16.328L30.9883 25.7957C30.2558 28.7683 27.5894 30.8573 24.528 30.8573H11.9911H11.9902Z"></path></svg>`
|
||||||
|
let coinIcon = `<svg width="28" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg" class="icon" style="fill:;"><path fill-rule="evenodd" clip-rule="evenodd" d="M14.045 25.5454C7.69377 25.5454 2.54504 20.3967 2.54504 14.0454C2.54504 7.69413 7.69377 2.54541 14.045 2.54541C20.3963 2.54541 25.545 7.69413 25.545 14.0454C25.545 17.0954 24.3334 20.0205 22.1768 22.1771C20.0201 24.3338 17.095 25.5454 14.045 25.5454ZM9.66202 6.81624H18.2761C18.825 6.81624 19.27 7.22183 19.27 7.72216C19.27 8.22248 18.825 8.62807 18.2761 8.62807H14.95V10.2903C17.989 10.4444 20.3766 12.9487 20.3855 15.9916V17.1995C20.3854 17.6997 19.9799 18.1052 19.4796 18.1052C18.9793 18.1052 18.5738 17.6997 18.5737 17.1995V15.9916C18.5667 13.9478 16.9882 12.2535 14.95 12.1022V20.5574C14.95 21.0577 14.5444 21.4633 14.0441 21.4633C13.5437 21.4633 13.1382 21.0577 13.1382 20.5574V12.1022C11.1 12.2535 9.52148 13.9478 9.51448 15.9916V17.1995C9.5144 17.6997 9.10883 18.1052 8.60856 18.1052C8.1083 18.1052 7.70273 17.6997 7.70265 17.1995V15.9916C7.71158 12.9487 10.0992 10.4444 13.1382 10.2903V8.62807H9.66202C9.11309 8.62807 8.66809 8.22248 8.66809 7.72216C8.66809 7.22183 9.11309 6.81624 9.66202 6.81624Z"></path></svg>`
|
||||||
|
function Formatv(item) {
|
||||||
|
let ids,
|
||||||
|
content = item.content,
|
||||||
|
bilibilis = content.match(/{\s*bilibili\s*(.*)\s*}/g);
|
||||||
|
if (bilibilis) {
|
||||||
|
ids = bilibilis.map(item => item.replace(/{\s*bilibili\s*(.*)\s*}/, '$1').replace(/.*video\/([^\/]*).*/, '$1').trim());
|
||||||
|
}
|
||||||
|
content = content.replace(/#(.*?)\s/g, '').replace(/{.*}/g, '').replace(/\!\[(.*?)\]\((.*?)\)/g, '').replace(/```/g, '')
|
||||||
|
let text = content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]').trim();
|
||||||
|
return {content, ids, text}
|
||||||
|
}
|
||||||
|
fetch('{{ .Data.url }}/api/v1/memo?creatorId={{ .Data.user }}&tag={{ .Data.video }}').then(res => res.json()).then(async data => {
|
||||||
|
let items = [], html = ''
|
||||||
|
data.forEach(item => {
|
||||||
|
items.push(Formatv(item))
|
||||||
|
})
|
||||||
|
for (const item of items) {
|
||||||
|
html += `<div class="bilibili_item"><span>${item.text}</span>`
|
||||||
|
|
||||||
|
for (const id of item.ids) {
|
||||||
|
await fetch(`/api/bilibili?bvid=${id}`).then(res => res.json()).then(data => {
|
||||||
|
console.log(data)
|
||||||
|
html += `<a href="https://www.bilibili.com/video/${id}/" class="bilibili_box" id="${id}">
|
||||||
|
<div class="bilibili_cover">
|
||||||
|
<img src="https://s1.hdslb.com/bfs/static/player/img/play.svg" class="play_icon no-lazyload">
|
||||||
|
<img src="${data.pic + '&h=300'}" class="no-lazyload">
|
||||||
|
<span>${data.duration}</span>
|
||||||
|
</div>
|
||||||
|
<div class="bilibili_info">
|
||||||
|
<div class="title">${data.title}</div>
|
||||||
|
<div class="desc">${data.desc}</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span>${playIcon}${data.view}</span>
|
||||||
|
<span>${likeIcon}${data.like}</span>
|
||||||
|
<span>${coinIcon}${data.coin}</span>
|
||||||
|
</div>
|
||||||
|
<div class="owner">
|
||||||
|
<span class="tip">视频</span>
|
||||||
|
<img src="${data.face + '&h=100'}" class="no-lazyload">
|
||||||
|
<span>${data.owner}</span>
|
||||||
|
</div>
|
||||||
|
</div></a>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
html += `</div>`
|
||||||
|
document.getElementById('bilibili').innerHTML = html
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GamemodeId int
|
||||||
|
|
||||||
|
const (
|
||||||
|
survival GamemodeId = iota
|
||||||
|
sandbox
|
||||||
|
attack
|
||||||
|
pvp
|
||||||
|
editor
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gamemode struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Id GamemodeId `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerInfo struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Maps string `json:"maps"`
|
||||||
|
Players int `json:"players"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
Wave int `json:"wave"`
|
||||||
|
Vertype string `json:"vertype"`
|
||||||
|
Gamemode Gamemode `json:"gamemode"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Modename string `json:"modename"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Ping int `json:"ping"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InfoBuffer struct {
|
||||||
|
*bytes.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GamemodeId) Name() string {
|
||||||
|
switch g {
|
||||||
|
case survival:
|
||||||
|
return "生存"
|
||||||
|
case sandbox:
|
||||||
|
return "沙盒"
|
||||||
|
case attack:
|
||||||
|
return "进攻"
|
||||||
|
case pvp:
|
||||||
|
return "PVP"
|
||||||
|
case editor:
|
||||||
|
return "编辑"
|
||||||
|
default:
|
||||||
|
return "Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InfoBuffer) New(b []byte) {
|
||||||
|
r.Reader = bytes.NewReader(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InfoBuffer) readString() string {
|
||||||
|
l, _ := r.ReadByte()
|
||||||
|
buf := make([]byte, l)
|
||||||
|
r.Read(buf)
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InfoBuffer) getInt() int {
|
||||||
|
var t int32
|
||||||
|
binary.Read(r, binary.BigEndian, &t)
|
||||||
|
return int(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InfoBuffer) get() byte {
|
||||||
|
b, _ := r.ReadByte()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectServer(host string) (buf InfoBuffer, ping int64, err error) {
|
||||||
|
socket, err := net.Dial("udp", host)
|
||||||
|
if err != nil {
|
||||||
|
return InfoBuffer{}, -1, err
|
||||||
|
}
|
||||||
|
defer socket.Close()
|
||||||
|
startTime := time.Now()
|
||||||
|
_, err = socket.Write([]byte{0xFE, 0x01})
|
||||||
|
if err != nil {
|
||||||
|
return InfoBuffer{}, -1, err
|
||||||
|
}
|
||||||
|
data := make([]byte, 500)
|
||||||
|
socket.SetReadDeadline(time.Now().Add(2 * time.Second))
|
||||||
|
_, err = socket.Read(data)
|
||||||
|
if err != nil {
|
||||||
|
return InfoBuffer{}, -1, err
|
||||||
|
}
|
||||||
|
ping = time.Since(startTime).Milliseconds()
|
||||||
|
buf.New(data)
|
||||||
|
return buf, ping, nil
|
||||||
|
}
|
||||||
|
func GetServerInfo(host string) (ServerInfo, error) {
|
||||||
|
var info ServerInfo
|
||||||
|
ip := strings.Split(host, ":")
|
||||||
|
if len(ip) == 1 {
|
||||||
|
info.Host = host
|
||||||
|
info.Port = 6567
|
||||||
|
} else {
|
||||||
|
info.Host = ip[0]
|
||||||
|
port, err := strconv.Atoi(ip[1])
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
info.Port = port
|
||||||
|
}
|
||||||
|
add := fmt.Sprintf("%s:%d", info.Host, info.Port)
|
||||||
|
buf, ping, err := connectServer(add)
|
||||||
|
if err != nil {
|
||||||
|
info.Status = "Offline"
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
info.Ping = int(ping)
|
||||||
|
info.Status = "Online"
|
||||||
|
info.Name = buf.readString()
|
||||||
|
info.Maps = buf.readString()
|
||||||
|
info.Players = buf.getInt()
|
||||||
|
info.Wave = buf.getInt()
|
||||||
|
info.Version = buf.getInt()
|
||||||
|
info.Vertype = buf.readString()
|
||||||
|
info.Gamemode.Id = GamemodeId(buf.get())
|
||||||
|
info.Gamemode.Name = info.Gamemode.Id.Name()
|
||||||
|
info.Limit = buf.getInt()
|
||||||
|
info.Description = buf.readString()
|
||||||
|
info.Modename = buf.readString()
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
@@ -4,10 +4,15 @@
|
|||||||
- "pageSize": 首页每一页的文章数量,
|
- "pageSize": 首页每一页的文章数量,
|
||||||
- "descriptionLen": 文章没有配置description字段时,默认取文章内容多少个字作为描述,
|
- "descriptionLen": 文章没有配置description字段时,默认取文章内容多少个字作为描述,
|
||||||
- "author": 博客作者,网站底部展示,
|
- "author": 博客作者,网站底部展示,
|
||||||
|
- "qq": 作者的QQ号,在说说中显示头像
|
||||||
- "icp": 网站的备案号,
|
- "icp": 网站的备案号,
|
||||||
- "webHookSecret": 博客文章更新勾子的密钥,这里要和你在仓库设置的密钥一样,
|
- "webHookSecret": 博客文章更新勾子的密钥,这里要和你在仓库设置的密钥一样,
|
||||||
- "categoryDisplayQuantity": 在分类页面下,每个分类下最多展示多少篇文章,
|
- "categoryDisplayQuantity": 在分类页面下,每个分类下最多展示多少篇文章,
|
||||||
- "utterancesRepo": 是否开启utterances评论,留空没有评论,否则填写评论存储的仓库name/repo,
|
- "utterancesRepo": 是否开启utterances评论,留空没有评论,否则填写评论存储的仓库name/repo,
|
||||||
|
- "gitalk_repo": 是否开启Gitalks评论,留空没有评论,否则填写评论存储的仓库repo,
|
||||||
|
- "gitalk_clientID": 应用id,
|
||||||
|
- "gitalk_clientSecret": 应用授权密钥,
|
||||||
|
- "gitalk_owner": 仓库所有者,
|
||||||
- "timeLayout": 解析时间的格式,保持和你文章里面的date字段一样,除非了解Golang的时间解析,否则不要修改,
|
- "timeLayout": 解析时间的格式,保持和你文章里面的date字段一样,除非了解Golang的时间解析,否则不要修改,
|
||||||
- "siteName": 网站的名字,
|
- "siteName": 网站的名字,
|
||||||
- "documentGitUrl": 你文章的git地址,应用会把文章克隆在当前目录下,必须公开并且以.git结尾,
|
- "documentGitUrl": 你文章的git地址,应用会把文章克隆在当前目录下,必须公开并且以.git结尾,
|
||||||
@@ -17,6 +22,10 @@
|
|||||||
- "themeColor": 博客的主题颜色,
|
- "themeColor": 博客的主题颜色,
|
||||||
- "dashboardEntrance": 网站仪表盘的访问路径,留空使用/admin,
|
- "dashboardEntrance": 网站仪表盘的访问路径,留空使用/admin,
|
||||||
- "themeOption": 网站可选择的主题颜色
|
- "themeOption": 网站可选择的主题颜色
|
||||||
|
- "memos_url": 说说和影集的memos网站地址不带后面的/
|
||||||
|
- "memos_user": 用户名称或者id
|
||||||
|
- "memos_album_tag": 影集所使用的标签
|
||||||
|
- "memos_talk_tag": 说说所使用的标签
|
||||||
|
|
||||||
|
|
||||||
## MD 文章支持的字段
|
## MD 文章支持的字段
|
||||||
|
|||||||
Reference in New Issue
Block a user