Compare commits

...

32 Commits

Author SHA1 Message Date
JiXieShi 996e8abc9d UP 2024-06-08 20:05:02 +08:00
JiXieShi 1a99e53158 UP 2024-06-05 19:11:35 +08:00
JiXieShi 6b7adcb07a UP MDTApi 2024-06-03 18:40:21 +08:00
JiXieShi 352666f059 UP MDTApi 2024-06-03 16:15:23 +08:00
JiXieShi 4b29709791 UP MDTApi 2024-05-27 19:00:33 +08:00
JiXieShi ed9a47c5ad UP Reader 2024-05-26 17:15:16 +08:00
JiXieShi 4b4b39b82d UP Talk Code 2024-05-22 18:19:12 +08:00
JiXieShi 8c1a3dd00d UP Talk Code 2024-05-22 18:12:01 +08:00
JiXieShi 6670a4a256 UP Api 2024-05-22 16:30:01 +08:00
JiXieShi a3a1f37c72 Fix img 2024-05-15 17:59:49 +08:00
JiXieShi 60f9fdf116 Fix img 2024-05-15 17:31:37 +08:00
JiXieShi 97d31651c6 Fix img 2024-05-15 16:53:43 +08:00
JiXieShi 941fee1f70 Fix img 2024-05-15 16:48:22 +08:00
JiXieShi 8679dadd19 Fix img 2024-05-15 16:43:25 +08:00
JiXieShi fa6af8bf9c Fix img 2024-05-15 16:37:12 +08:00
JiXieShi d53620bcc6 Fix img 2024-05-15 16:25:55 +08:00
JiXieShi fcc8163cfe Fix img 2024-05-15 16:23:11 +08:00
JiXieShi a1264857d0 Fix img 2024-05-15 16:16:49 +08:00
JiXieShi 0fdcae3c0c Fix img 2024-05-15 16:15:01 +08:00
JiXieShi 2b7fdf7b9f Fix img 2024-05-15 16:01:29 +08:00
JiXieShi dbd62b3156 Fix img加载1 2024-05-07 22:00:29 +08:00
JiXieShi ad3a4afc14 Fix img加载 2024-05-07 21:58:17 +08:00
JiXieShi b55551d4c7 Fix img加载 2024-05-07 21:49:23 +08:00
JiXieShi 0f49d5f017 Fix img加载 2024-05-07 21:46:06 +08:00
JiXieShi 47f42fe2b0 UP 视频模块 2024-05-07 21:33:51 +08:00
JiXieShi 7964879495 UP 2024-05-04 21:17:34 +08:00
JiXieShi ddd12b69c2 UP 手机访问 2024-05-04 21:14:10 +08:00
JiXieShi f04ed0b242 UP 2024-05-03 22:42:59 +08:00
JiXieShi 080c41d458 UP svg 2024-05-03 22:14:21 +08:00
JiXieShi ce45736f28 UP Help 2024-05-03 18:45:50 +08:00
JiXieShi 31c541e1c2 增加说说 影集支持模块 2024-05-03 18:34:32 +08:00
JiXieShi f4597906c0 更新评论模块 2024-03-28 19:45:25 +08:00
43 changed files with 2073 additions and 109 deletions
+8
View File
@@ -76,6 +76,8 @@ content目录下的一级目录代表一个分类,如果一级目录下有子
- [x] 8.添加评论支持(在配置里开启,所有评论都会储存在仓库的Issues)
- [x] 9.支持网易云音乐
- [x] 10.支持自定义切换主题
- [x] 11.说说支持
- [x] 12.影集支持
> 后续尽善尽美之后,我可能会提供其他漂亮的主题皮肤,也欢迎大家参与进来。
@@ -88,8 +90,14 @@ content目录下的一级目录代表一个分类,如果一级目录下有子
## 更新日志
### V3.3
* 加入影集界面
* 加入说说界面
### V3.2
* 加入搜索功能
* 加入Tag搜索和展示
* 加入Gitalk评论支持(可选)
### V3.1
* 去掉标题的.MD后缀
+9
View File
@@ -0,0 +1,9 @@
package api
import "net/http"
func InitApi() {
BilibiliInit()
http.HandleFunc(Mindustry.Url, GetMindustryInfo)
http.HandleFunc(Memos.Url, GetMemosJson)
}
+40
View File
@@ -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
View File
@@ -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
View File
@@ -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))
}
+76
View File
@@ -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
}
+2
View File
@@ -1,6 +1,7 @@
package main
import (
"blog/api"
"blog/config"
"blog/models"
"blog/routes"
@@ -16,6 +17,7 @@ func init() {
func main() {
routes.InitRoute()
api.InitApi()
fmt.Printf("Versionv%v \n", config.Cfg.Version)
fmt.Printf("ListenAndServe On Port %v \n", config.Cfg.Port)
fmt.Printf("Dashboard On Path %v \n", config.Cfg.Dashboard)
+6 -1
View File
@@ -7,6 +7,11 @@
"webHookSecret": "jixieshi",
"categoryDisplayQuantity": 6,
"utterancesRepo": "jixishi/blog_docs",
"gitalk_clientID": "2c40e5de269c2c61dc91",
"gitalk_clientSecret": "d976941995470662ec039028fe37c0466bd27027",
"gitalk_owner": "jixishi",
"gitalk_repo": "",
"gitalk_repo": "",
"timeLayout": "2006.01.02 15:04",
"siteName": "JiXieShi's Blog",
"htmlKeywords": "forest blog,Golang,ARM,BE6,前端,硬件",
@@ -16,4 +21,4 @@
"themeColor": "#2196f3",
"dashboard": "/adminjxs",
"themeOption": ["#673ab7","#f44336","#9c27b0","#2196f3","#607d8b","#795548"]
}
}
+1
View File
@@ -14,6 +14,7 @@ import (
type Config struct {
userConfig
systemConfig
memosConfig
}
//
+9
View File
@@ -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"`
}
+10
View File
@@ -5,6 +5,8 @@ type userConfig struct {
Author string `json:"author"`
Qq string `json:"qq"`
Icp string `json:"icp"`
TimeLayout string `json:"timeLayout"`
@@ -17,6 +19,14 @@ type userConfig struct {
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"`
DescriptionLen int `json:"descriptionLen"`
+1 -1
View File
@@ -15,7 +15,7 @@ func Article(w http.ResponseWriter, r *http.Request) {
path := models.ArticleShortUrlMap[key]
articleDetail, err := models.ReadArticleDetail(path)
articleDetail, err := models.ReadArticleDetail(path, key)
if err != nil {
articleTemplate.WriteError(w, err)
+1 -1
View File
@@ -15,7 +15,7 @@ func ExtraNav(w http.ResponseWriter, r *http.Request) {
name := r.Form.Get("name")
for _, nav := range models.Navigation {
if nav.Title == name {
articleDetail, err := models.ReadArticleDetail(nav.Path)
articleDetail, err := models.ReadArticleDetail(nav.Path, "")
if err != nil {
extraNavTemplate.WriteError(w, err)
}
+49
View File
@@ -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
View File
@@ -139,44 +139,52 @@ func RecursiveReadArticles(dir string) (Articles, error) {
}
func ReadArticle(path string) (Article, error) {
article, _, err := readMarkdown(path)
article, _, err := readMarkdown(path, "")
if err != nil {
return article, err
}
return article, nil
}
func ReadArticleDetail(path string) (ArticleDetail, error) {
_, articleDetail, err := readMarkdown(path)
func ReadArticleDetail(path, key string) (ArticleDetail, error) {
_, articleDetail, err := readMarkdown(path, key)
if err != nil {
return articleDetail, err
}
return articleDetail, nil
}
func readMarkdown(path string) (Article, ArticleDetail, error) {
// readMarkdown 读取 Markdown 文件内容
func readMarkdown(path, key string) (Article, ArticleDetail, error) {
var article Article
var articleDetail ArticleDetail
mdFile, err := os.Stat(path)
// 获取文件信息
mdFile, err := os.Stat(path)
if err != nil {
return article, articleDetail, err
}
if mdFile.IsDir() {
return article, articleDetail, errors.New("this path is Dir")
}
markdown, err := ioutil.ReadFile(path)
// 读取 Markdown 文件内容
markdown, err := ioutil.ReadFile(path)
if err != nil {
return article, articleDetail, err
}
markdown = bytes.TrimSpace(markdown)
// 设置文章属性
if key != "" {
article.ShortUrl = key
}
article.Path = path
article.Category = GetCategoryName(path)
article.Title = strings.TrimSuffix(strings.ToUpper(mdFile.Name()), ".MD")
article.Date = Time(mdFile.ModTime())
// 处理 开头JSON 格式的文章信息
if !bytes.HasPrefix(markdown, []byte("```json")) {
article.Description = cropDesc(markdown)
articleDetail.Article = article
@@ -189,6 +197,7 @@ func readMarkdown(path string) (Article, ArticleDetail, error) {
article.Description = cropDesc(markdownArrInfo[1])
// 解析 JSON 内容
if err := json.Unmarshal(bytes.TrimSpace(markdownArrInfo[0]), &article); err != nil {
article.Title = "文章[" + article.Title + "]解析 JSON 出错,请检查。"
article.Description = err.Error()
+10 -2
View File
@@ -25,13 +25,14 @@ func GetCategoryName(path string) string {
return categoryName
}
// GroupByCategory 按类别对文章进行分组
func GroupByCategory(articles *Articles, articleQuantity int) Categories {
var categories Categories
categoryMap := make(map[string]Articles)
// 根据类别将文章分组
for _, article := range *articles {
_, existedCategory := categoryMap[article.Category]
if existedCategory {
categoryMap[article.Category] = append(categoryMap[article.Category], article)
@@ -39,10 +40,13 @@ func GroupByCategory(articles *Articles, articleQuantity int) Categories {
categoryMap[article.Category] = Articles{article}
}
}
// 遍历类别映射,构建分类信息
for categoryName, articles := range categoryMap {
articleLen := len(articles)
var articleList Articles
// 根据指定数量截取文章列表
if articleQuantity <= 0 {
articleList = articles
} else {
@@ -52,12 +56,16 @@ func GroupByCategory(articles *Articles, articleQuantity int) Categories {
articleList = articles[0:articleQuantity]
}
}
// 添加分类信息到结果集
categories = append(categories, Category{
Name: categoryName,
Quantity: articleLen,
Articles: articleList,
})
}
// 对分类结果集进行排序
sort.Sort(categories)
return categories
}
+11
View File
@@ -9,6 +9,7 @@ var Navigation Navs
var ArticleList Articles
var ArticleShortUrlMap map[string]string //用来保证文章 shortUrl 唯一和快速定位文章
var Template HtmlTemplate
var Api ApiTemplate
func CompiledContent() {
config.Initial() //克隆或者更新文档库
@@ -35,6 +36,16 @@ func CompiledContent() {
wg.Done()
}()
//加载Api信息模板
wg.Add(1)
go func() {
Api, err = initApiTemplate(config.Cfg.ThemesDir)
if err != nil {
panic(err)
}
wg.Done()
}()
//文章
wg.Add(1)
go func() {
+3
View File
@@ -11,17 +11,20 @@ type Nav struct {
}
type Navs []Nav
// initExtraNav 初始化额外导航栏
func initExtraNav(dir string) (Navs, error) {
var navigation Navs
var extraNav Articles
// 递归读取文章
extraNav, err := RecursiveReadArticles(dir)
if err != nil {
return navigation, err
}
sort.Sort(extraNav)
// 将文章标题格式化为大写开头的标题,并添加到导航栏中
for _, article := range extraNav {
title := strings.Title(strings.ToLower(article.Title))
navigation = append(navigation, Nav{Title: title, Path: article.Path})
+92 -22
View File
@@ -12,6 +12,9 @@ type TemplatePointer struct {
}
type HtmlTemplate struct {
Reader TemplatePointer
Talk TemplatePointer
Album TemplatePointer
Article TemplatePointer
Categories TemplatePointer
Dashboard TemplatePointer
@@ -19,6 +22,11 @@ type HtmlTemplate struct {
Index TemplatePointer
}
type ApiTemplate struct {
Info TemplatePointer
Mindustry TemplatePointer
}
func (t TemplatePointer) WriteData(w io.Writer, data interface{}) {
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{} {
return map[string]interface{}{
"Title": title,
"Data": data,
"Config": config.Cfg,
"Navs": Navigation,
"Title": title, // 页面标题
"Data": data, // 页面数据
"Config": config.Cfg, // 网站配置
"Navs": Navigation, // 导航栏数据
}
}
// initHtmlTemplate 初始化 HTML 模板
//
// Parameters:
//
// viewDir: 视图文件目录路径
//
// Returns:
//
// HtmlTemplate: HTML 模板对象
// error: 错误信息(如果有)
func initHtmlTemplate(viewDir string) (HtmlTemplate, error) {
var htmlTemplate HtmlTemplate
tp, err := readHtmlTemplate(
[]string{"index", "extraNav", "dashboard", "categories", "article"},
[]string{"index", "extraNav", "dashboard", "categories", "article", "album", "talk", "reader"},
viewDir)
if err != nil {
return htmlTemplate, err
return HtmlTemplate{}, err
}
htmlTemplate.Index = tp[0]
htmlTemplate.ExtraNav = tp[1]
htmlTemplate.Dashboard = tp[2]
htmlTemplate.Categories = tp[3]
htmlTemplate.Article = tp[4]
return htmlTemplate, nil
return HtmlTemplate{
Index: tp[0],
ExtraNav: tp[1],
Dashboard: tp[2],
Categories: tp[3],
Article: tp[4],
Album: tp[5],
Talk: tp[6],
Reader: tp[7],
}, nil
}
func SpreadDigit(n int) []int {
var r []int
for i := 1; i <= n; i++ {
@@ -71,21 +99,63 @@ func SpreadDigit(n int) []int {
return r
}
// readHtmlTemplate 读取 HTML 模板文件
//
// Parameters:
//
// htmlFileName: HTML 文件名列表
// viewDir: 视图文件目录路径
//
// Returns:
//
// []TemplatePointer: HTML 模板指针列表
// error: 错误信息(如果有)
func readHtmlTemplate(htmlFileName []string, viewDir string) ([]TemplatePointer, error) {
var htmlTemplate []TemplatePointer
head := viewDir + "/layouts/head.html"
footer := viewDir + "/layouts/footer.html"
head := viewDir + "/layouts/head.gohtml"
nav := viewDir + "/layouts/nav.gohtml"
footer := viewDir + "/layouts/footer.gohtml"
reviews := viewDir + "/layouts/reviews.gohtml"
for _, name := range htmlFileName {
tp, err := template.New(name+".html").
tp, err := template.New(name+".gohtml").
Funcs(template.FuncMap{"SpreadDigit": SpreadDigit}).
ParseFiles(viewDir+"/"+name+".html", head, footer)
ParseFiles(viewDir+"/"+name+".gohtml", head, nav, footer, reviews)
if err != nil {
return htmlTemplate, err
return nil, err
}
htmlTemplate = append(htmlTemplate, TemplatePointer{tp})
}
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
}
+1
View File
@@ -13,6 +13,7 @@ func InitRoute() {
http.HandleFunc("/categories", controller.Category)
http.HandleFunc("/article", controller.Article)
http.HandleFunc("/extra-nav", controller.ExtraNav)
http.HandleFunc("/memos", controller.Memos)
http.HandleFunc(config.Cfg.GitHookUrl, controller.GithubHook)
http.HandleFunc(config.Cfg.Dashboard, controller.Dashboard)
+60
View File
@@ -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
View File
@@ -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>
+132
View File
@@ -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(`![](${t.externalLink})`)
else imgs.push(`![](${url}/o/r/${t.id}/${t.publicId}/${t.filename})`)
})
}
})
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" .}}
+5
View File
@@ -0,0 +1,5 @@
{{define "footer"}}
</div>
</body>
</html>
{{ end }}
+18
View File
@@ -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 }}
+67
View File
@@ -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" .}}
+10
View File
@@ -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 "" }}
<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/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.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"
@@ -44,12 +47,8 @@
</div>
<script>
document.getElementById('article').innerHTML = marked({{ .Data.Body }});
document.getElementById('article').innerHTML = marked({{ .Data.Body }});
</script>
<script src="/public/js/prism.js"></script>
{{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}}
{{template "reviews" .}}
{{template "footer" .}}
@@ -18,7 +18,7 @@
<p>Action</p>
<div class="item-content">
<div class="action">
<a href="{{ .Config.DashboardEntrance }}?action=updateArticle">更新文章</a>
<a href="{{ .Config.Dashboard }}?action=updateArticle">更新文章</a>
</div>
</div>
{{ if .Data.msg }}
@@ -32,7 +32,7 @@
<span class="action-tip">提示:更新文章会执行git pull命令,和你的仓库网络有关,等待时间可能会稍长。</span>
<script>
function selectColor(index) {
window.location.href = '{{ .Config.DashboardEntrance }}?theme=' + index
window.location.href = '{{ .Config.Dashboard }}?theme=' + index
}
function searchArticle() {
var searchKey = document.getElementById('search-input').value;
@@ -1,13 +1,15 @@
{{define "footer"}}
</div>
<div class="footer">
{{if ne .Config.Icp ""}}
<span>{{ .Config.Icp }}</span><span class="footer-divider"> | </span>
{{ end }}
<span>
© 2018 - <script>document.write(new Date().getFullYear())</script> {{ .Config.Author }}
Powered By <a href="{{ .Config.AppRepository }}" target="_blank"> {{ .Config.AppName }} </a>
</span>
<span id="busuanzi_container_site_pv" style='display:none'>
访问量<span id="busuanzi_value_site_pv"></span>次
当月访问量<span id="busuanzi_value_site_pv"></span>次
</span>
</div>
</body>
@@ -10,9 +10,12 @@
<meta name="description" content="{{ .Config.HtmlDescription }}" />
<title>{{ .Title }} - {{ .Config.SiteName }}</title>
<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 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 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>
@@ -40,31 +43,11 @@
if(key)return paramsData.hasOwnProperty(key) ? paramsData[key] : null;
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>
</head>
<body class="theme{{ .Config.ThemeColor }}">
<nav class="head">
<div class="container head-content">
<div class="logo">{{ .Config.SiteName }}</div>
<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>
<header>
{{template "nav" .}}
</header>
<div class="post-warp">
{{ end }}
+74
View File
@@ -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 }}
+26
View File
@@ -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 }}
+33 -42
View File
@@ -41,7 +41,6 @@ a{
}
a:hover{
color: #673ab7;
color: var(--primary,#673ab7);
}
@@ -61,7 +60,6 @@ blockquote::before{
content: '“';
font-family: fantasy;
font-size: 40px;
color: #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 {
position: relative;
width: 100%;
@@ -248,7 +217,6 @@ hr:after{
padding-bottom: 6px;
}
.pagination li a:hover,.pagination .active a{
border-bottom: 3px solid #673ab7;
border-bottom: 3px solid var(--primary,#673ab7);
}
@@ -283,8 +251,6 @@ hr:after{
padding: 4px 20px;
font-size: 14px;
color: #fff;
background: #673ab7;
border: 2px solid #673ab7;
background: var(--primary,#673ab7);
border: 2px solid var(--primary,#673ab7);
text-decoration: none;
@@ -305,7 +271,6 @@ hr:after{
font-weight: normal;
}
.articles li .title:hover{
color: #673ab7;
color: var(--primary,#673ab7);
}
.article-info{
@@ -369,19 +334,19 @@ hr:after{
align-items: center;
}
.search-box{
height: 40px;
height: 36px;
display: flex;
border-radius: 2px;
border-radius: 16px;
align-items: center;
justify-content: space-between;
border: 1px solid #673ab7;
border: 1px solid var(--primary,#673ab7);
border: 2px solid var(--primary,#673ab7);
width: 320px;
}
.search-input{
text-align: center;
flex: 1;
border: none;
border-radius: 16px;
height: 30px;
padding: 0 10px;
}
@@ -434,7 +399,6 @@ hr:after{
border-radius: 2px;
opacity: .8;
color: #fff;
background-color: #673ab7;
background-color: var(--primary,#673ab7);
font-size: 14px;
}
@@ -444,7 +408,6 @@ hr:after{
}
.action-tip{
font-size: 14px;
color: #673ab7;
color: var(--primary,#673ab7);
}
.action-msg{
@@ -463,7 +426,6 @@ hr:after{
opacity: 1;
min-width: 288px;
border-radius: 4px;
background-color: #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);
}
@@ -475,3 +437,32 @@ hr:after{
font-size: 16px;
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;
}
+47
View File
@@ -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

+287
View File
@@ -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" .}}
+91
View File
@@ -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" .}}
+215
View File
@@ -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 }}
+143
View File
@@ -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
}
+9
View File
@@ -4,10 +4,15 @@
- "pageSize": 首页每一页的文章数量,
- "descriptionLen": 文章没有配置description字段时,默认取文章内容多少个字作为描述,
- "author": 博客作者,网站底部展示,
- "qq": 作者的QQ号,在说说中显示头像
- "icp": 网站的备案号,
- "webHookSecret": 博客文章更新勾子的密钥,这里要和你在仓库设置的密钥一样,
- "categoryDisplayQuantity": 在分类页面下,每个分类下最多展示多少篇文章,
- "utterancesRepo": 是否开启utterances评论,留空没有评论,否则填写评论存储的仓库name/repo,
- "gitalk_repo": 是否开启Gitalks评论,留空没有评论,否则填写评论存储的仓库repo,
- "gitalk_clientID": 应用id,
- "gitalk_clientSecret": 应用授权密钥,
- "gitalk_owner": 仓库所有者,
- "timeLayout": 解析时间的格式,保持和你文章里面的date字段一样,除非了解Golang的时间解析,否则不要修改,
- "siteName": 网站的名字,
- "documentGitUrl": 你文章的git地址,应用会把文章克隆在当前目录下,必须公开并且以.git结尾,
@@ -17,6 +22,10 @@
- "themeColor": 博客的主题颜色,
- "dashboardEntrance": 网站仪表盘的访问路径,留空使用/admin,
- "themeOption": 网站可选择的主题颜色
- "memos_url": 说说和影集的memos网站地址不带后面的/
- "memos_user": 用户名称或者id
- "memos_album_tag": 影集所使用的标签
- "memos_talk_tag": 说说所使用的标签
## MD 文章支持的字段