Repository: Foleyzhao/lacerate Branch: main Commit: 4f3ccd4f804b Files: 40 Total size: 82.2 KB Directory structure: gitextract__84n7yyp/ ├── .gitignore ├── LICENSE ├── README.md ├── cmd/ │ └── cmd.go ├── core/ │ ├── command/ │ │ └── command.go │ ├── common/ │ │ └── banner.go │ ├── config/ │ │ └── config.go │ ├── log/ │ │ └── logger.go │ ├── model/ │ │ ├── archive.go │ │ ├── category.go │ │ ├── post.go │ │ └── tag.go │ ├── service/ │ │ ├── about.go │ │ ├── archive.go │ │ ├── category.go │ │ ├── compile.go │ │ ├── post.go │ │ ├── tag.go │ │ └── watcher.go │ └── utils/ │ ├── crypto.go │ ├── file.go │ ├── markdown.go │ ├── slice.go │ ├── storage.go │ ├── string.go │ ├── template.go │ └── time.go ├── doc/ │ ├── 博客编写指南.md │ └── 配置文件说明.md ├── go.mod ├── go.sum └── theme/ └── blog/ ├── assets/ │ └── css/ │ ├── basic.css │ └── style.css └── layout/ ├── archive.tpl ├── category.tpl ├── home.tpl ├── main.tpl ├── page.tpl ├── post.tpl └── tag.tpl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # Go workspace file go.work # Customize markdown storage config.yml .idea ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================

Lacerate

一个Goland编写的简单的静态博客生成器

[下 载](https://github.com/Foleyzhao/lacerate/releases) | [主 页](https://happy.zj.cn/) GitHub All Releases


👏 欢迎使用 **Lacerate** ! ✍️ **Lacerate** 一个简单的静态博客生成器。 ## 特性👇 📝 使用 **Markdown** 语法,进行快速创作 🌉 对文章进行分类 🏷️ 对文章进行标签分组 📋 根据年月进行文章归档 🌁 自定义关于我页面 💻 支持多客户端: **𝖶𝗂𝗇𝖽𝗈𝗐𝗌** / **𝖬𝖺𝖼𝖮𝖲** / **Linux** ## 教程 [配置文件说明](https://github.com/Foleyzhao/lacerate/blob/main/doc/%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%AF%B4%E6%98%8E.md) | [博客编写指南](https://github.com/Foleyzhao/lacerate/blob/main/doc/%E5%8D%9A%E5%AE%A2%E7%BC%96%E5%86%99%E6%8C%87%E5%8D%97.md) ### 快速启动 ```bash git clone https://github.com/Foleyzhao/lacerate.git go build -o lacerate ./cmd/cmd.go nohup ./lacerate run > lacerate.log 2>&1 & ``` 访问: http://localhost:8090/ ### 详细指令 ```bash # lacerate command [args...] # 初始化博客文件夹 lacerate init # 新建 markdown 文件 lacerate new filename # 编译博客 lacerate compile/c # 打开文件监听器 lacerate watch/w # 运行http服务,默认端口8090 lacerate http [port] # 运行lacerate,默认端口8090 lacerate run [port] ``` ## 联系 [主页](https://happy.zj.cn/) | 邮箱: foleyzhao@163.com ## 示例截图




## 贡献 欢迎任何形式的贡献。可以使用 [pull requests](https://github.com/Foleyzhao/lacerate/pulls) 或 [issues](https://github.com/Foleyzhao/lacerate/issues) 的方式提交任何想法。 ## 支持
## License [Apache-2.0](https://github.com/Foleyzhao/lacerate/blob/main/LICENSE). Copyright (c) 2024 Lacerate ================================================ FILE: cmd/cmd.go ================================================ package main import ( "flag" "github.com/fatih/color" "lacerate/core/command" "lacerate/core/common" "lacerate/core/config" "lacerate/core/service" "os" "strconv" ) var ( // 命令行参数 args []string ) // 入口函数 func main() { _, _ = color.New(color.FgGreen).Fprintln(os.Stdout, common.Banner) flag.Parse() args = flag.Args() if len(args) == 0 || len(args) > 3 { command.PrintHelp() os.Exit(1) } switch args[0] { default: command.PrintHelp() os.Exit(1) case "init": command.Initialize() case "new": if len(args) == 2 { name := args[1] service.CreateMarkdown(name) } else { panic("the file name is missing.") } case "compile", "c": service.Compile() case "watch", "w": service.NewWatch(config.Config().Paths, config.Config().Suffix).Watcher() done := make(chan bool) <-done case "run": service.Compile() service.NewWatch(config.Config().Paths, config.Config().Suffix).Watcher() var port = 8090 if len(args) == 2 { p, err := strconv.Atoi(args[1]) if err != nil { panic(err) } port = p } command.ListenHttpServer(port) case "http", "web": var port = 8090 if len(args) == 2 { p, err := strconv.Atoi(args[1]) if err != nil { panic(err) } port = p } command.ListenHttpServer(port) } } ================================================ FILE: core/command/command.go ================================================ package command import ( "fmt" "lacerate/core/config" "lacerate/core/log" "net/http" "os" "strconv" ) const ( // HELP 帮助信息 HELP = ` Usage: lacerate command [args...] Initialize the blog folder lacerate init Create a new markdown file lacerate new filename Compile the blog lacerate compile/c Open the file listener lacerate watch/w Open the file server lacerate http/web [port] Run all Lacerate services lacerate run [port] ` ) // PrintHelp 打印帮助信息 func PrintHelp() { fmt.Println(HELP) } // Initialize 初始化操作 func Initialize() { config.CreateConf() CreateDir() log.Log.Debug("the initialization is successful!") } // ListenHttpServer 启动http服务 func ListenHttpServer(port int) { log.Log.Info("open the built-in web server...") p := strconv.Itoa(port) http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir(config.GlobalConf.Html+"/assets/")))) http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(config.GlobalConf.Html)))) log.Log.Debugf("the built-in web server is successfully turned on and the port is monitored: %d...", port) err := http.ListenAndServe(":"+p, nil) if err != nil { log.Log.Errorf("listen http serve error: %s", err) } } // CreateDir 创建博客目录 func CreateDir() { _, err := os.Stat(config.GlobalConf.Html) if os.IsNotExist(err) { if err := os.MkdirAll(config.GlobalConf.Html, os.ModePerm); err != nil { panic(err) } } _, err = os.Stat(config.GlobalConf.Markdown) if os.IsNotExist(err) { if err := os.MkdirAll(config.GlobalConf.Markdown, os.ModePerm); err != nil { panic(err) } } _, err = os.Stat(config.GlobalConf.Storage) if os.IsNotExist(err) { if err := os.MkdirAll(config.GlobalConf.Storage, os.ModePerm); err != nil { panic(err) } } _, err = os.Stat(config.GlobalConf.Theme) if os.IsNotExist(err) { if err := os.MkdirAll(config.GlobalConf.Theme, os.ModePerm); err != nil { panic(err) } } } ================================================ FILE: core/common/banner.go ================================================ package common // Banner banner var Banner = ` ========================================================= ▄ ▄ █ ▄▄▄ ▄▄▄ ▄▄▄ ▄ ▄▄ ▄▄▄ ▄▄█▄▄ ▄▄▄ █ ▀ █ █▀ ▀ █▀ █ █▀ ▀ ▀ █ █ █▀ █ █ ▄▀▀▀█ █ █▀▀▀▀ █ ▄▀▀▀█ █ █▀▀▀▀ █▄▄▄▄▄ ▀▄▄▀█ ▀█▄▄▀ ▀█▄▄▀ █ ▀▄▄▀█ ▀▄▄ ▀█▄▄▀ Author: HappyNewYear ========================================================= ` ================================================ FILE: core/config/config.go ================================================ package config import ( "gopkg.in/yaml.v3" "io" "os" ) var GlobalConf = Config() var confFileName = "config.yml" var confContent = ` # 站点信息 title: xxx's Blog subtitle: 主页 description: xxx's Blog keywords: blog # 作者信息 author: HappyNewYear avatar: /assets/avatar.jpg github: https://github.com/Foleyzhao email: foleyzhao@163.com # 配置信息 summary_line: 6 home_post_num: 10 # 文件存储 theme: theme/blog markdown: markdown html: /data/www/html storage: storage # 文件监听 paths: - markdown suffix: - md - yml # 自定义信息 home_title: xxx's Blog archive_title: 归档 tag_title: 标签 category_title: 分类 about_title: 关于我 ` // SystemConfig 系统配置 type SystemConfig struct { Title string `yaml:"title"` SubTitle string `yaml:"subtitle"` Description string `yaml:"description"` Keywords string `yaml:"keywords"` Author string `yaml:"name"` Avatar string `yaml:"avatar"` Github string `yaml:"github"` Email string `yaml:"email"` SummaryLine int `yaml:"summary_line"` HomePostNum int `yaml:"home_post_num"` Theme string `yaml:"theme"` Markdown string `yaml:"markdown"` Html string `yaml:"html"` Storage string `yaml:"storage"` Paths []string `yaml:"paths"` Suffix []string `yaml:"suffix"` HomeTitle string `yaml:"home_title,omitempty"` ArchiveTitle string `yaml:"archive_title,omitempty"` TagTitle string `yaml:"tag_title,omitempty"` CategoryTitle string `yaml:"category_title,omitempty"` AboutTitle string `yaml:"about_title,omitempty"` } // 加载系统配置 func loadConf() ([]byte, error) { _, err := os.Stat(confFileName) if os.IsNotExist(err) { CreateConf() } file, err := os.Open(confFileName) if err != nil { return nil, err } defer func(file *os.File) { _ = file.Close() }(file) return io.ReadAll(file) } // CreateConf 创建系统配置文件 func CreateConf() { _, err := os.Stat(confFileName) if os.IsNotExist(err) { _, err := os.OpenFile(confFileName, os.O_WRONLY|os.O_CREATE, 0755) if err != nil { panic(err) } var confWrite = []byte(confContent) err = os.WriteFile(confFileName, confWrite, 0666) if err != nil { panic(err) } } } // Config 系统配置 func Config() SystemConfig { confContent, err := loadConf() if err != nil { panic("failed to load configuration file: " + err.Error()) } c := SystemConfig{} err = yaml.Unmarshal(confContent, &c) if err != nil { panic("failed to parse the configuration file: " + err.Error()) } return c } ================================================ FILE: core/log/logger.go ================================================ package log import "github.com/sirupsen/logrus" // Log 日志记录器 var Log = logrus.WithFields(logrus.Fields{}) // 初始化 func init() { Log.Logger.SetFormatter(&logrus.TextFormatter{ ForceColors: true, }) Log.Logger.SetLevel(logrus.DebugLevel) } ================================================ FILE: core/model/archive.go ================================================ package model import "time" // PublishedYears 年份归档列表 type PublishedYears []*PublishedYear // PublishedMonths 月份归档列表 type PublishedMonths []*PublishedMonth // PublishedYear 年份归档 type PublishedYear struct { YearStr string `json:"year"` Months []*PublishedMonth `json:"months"` MonthDict map[string]*PublishedMonth `json:"-"` } // PublishedMonth 月份归档 type PublishedMonth struct { MonthStr string `json:"month"` Posts []*Post `json:"posts"` Month time.Month `json:"-"` } func (y PublishedYears) Len() int { return len(y) } func (y PublishedYears) Swap(i, j int) { y[i], y[j] = y[j], y[i] } func (y PublishedYears) Less(i, j int) bool { return y[i].YearStr > y[j].YearStr } func (m PublishedMonths) Len() int { return len(m) } func (m PublishedMonths) Swap(i, j int) { m[i], m[j] = m[j], m[i] } func (m PublishedMonths) Less(i, j int) bool { return m[i].Month > m[j].Month } ================================================ FILE: core/model/category.go ================================================ package model // Category 分类 type Category struct { Count int `json:"count"` Name string `json:"name"` Posts []*Post `json:"posts"` Url string `json:"url"` } ================================================ FILE: core/model/post.go ================================================ package model // PostList 文章列表 type PostList []*Post // Post 文章 type Post struct { Id int `json:"id"` Title string `json:"title"` Description string `json:"description"` Summary string `json:"summary"` Content string `json:"content"` Tags []string `json:"tags"` Category []string `json:"category"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` Url string `json:"url"` } func (p PostList) Len() int { return len(p) } func (p PostList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p PostList) Less(i, j int) bool { return p[i].CreatedAt > p[j].CreatedAt } ================================================ FILE: core/model/tag.go ================================================ package model // Tag 标签 type Tag struct { Count int `json:"count"` Name string `json:"name"` Posts []*Post `json:"posts"` Url string `json:"url"` } ================================================ FILE: core/service/about.go ================================================ package service import ( "lacerate/core/config" "lacerate/core/model" "lacerate/core/utils" "os" "path" "time" ) // GetAbout 获取about内容 func GetAbout() (post *model.Post, err error) { post = &model.Post{} about := path.Join(config.GlobalConf.Markdown, "/about.md") if _, err := os.Stat(about); os.IsNotExist(err) { return post, nil } content, err := os.ReadFile(about) if err != nil { return nil, err } post.Title = "" post.Content = utils.MarkdownToHtml(string(content)) post.CreatedAt = time.Now().Unix() return post, nil } ================================================ FILE: core/service/archive.go ================================================ package service import ( "lacerate/core/model" "lacerate/core/utils" "sort" "time" ) // GetArchive 获取归档信息 func GetArchive() []*model.PublishedYear { archiveYear := make(model.PublishedYears, 0) _archiveYear := make(map[string]*model.PublishedYear) for _, post := range postList { yearStr := utils.Year(post.CreatedAt) monthStr := utils.Month(post.CreatedAt) _month := time.Unix(post.CreatedAt, 0).Month() year := _archiveYear[yearStr] if year == nil { year = &model.PublishedYear{YearStr: yearStr, Months: make([]*model.PublishedMonth, 0), MonthDict: make(map[string]*model.PublishedMonth)} _archiveYear[yearStr] = year } month := year.MonthDict[monthStr] if month == nil { month = &model.PublishedMonth{MonthStr: monthStr, Posts: []*model.Post{}, Month: _month} year.MonthDict[monthStr] = month } month.Posts = append(month.Posts, post) } for _, year := range _archiveYear { monthArray := make(model.PublishedMonths, 0) for _, month := range year.MonthDict { monthArray = append(monthArray, month) } sort.Sort(monthArray) year.MonthDict = nil year.Months = monthArray archiveYear = append(archiveYear, year) } sort.Sort(archiveYear) return archiveYear } ================================================ FILE: core/service/category.go ================================================ package service import ( "lacerate/core/model" ) // 分类列表 var categoryList map[string]*model.Category // 初始化 func init() { categoryList = make(map[string]*model.Category) } // GetCategoryList 获取菜单列表 func GetCategoryList() map[string]*model.Category { return categoryList } ================================================ FILE: core/service/compile.go ================================================ package service import ( "html/template" "lacerate/core/config" "lacerate/core/log" "lacerate/core/utils" "os" "path" "strings" ) // 全局数据 var data = map[string]interface{}{ "title": config.GlobalConf.Title, "subtitle": config.GlobalConf.SubTitle, "description": config.GlobalConf.Description, "keywords": config.GlobalConf.Keywords, "author": config.GlobalConf.Author, "avatar": config.GlobalConf.Avatar, "github": config.GlobalConf.Github, "email": config.GlobalConf.Email, } // html模板函数字典 var funcMaps = template.FuncMap{ "unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq, "md5": utils.Xmd5, } // Compile 编译博客 func Compile() { defer func() { if r := recover(); r != nil { log.Log.Errorf("panic recovered from: %v", r) } }() log.Log.Info("start compiling your blog...") checkThemeFile() copyAssetsFile() LoadPostList() // 创建页面 CompileHome() CompilePost() CompileArchive() CompileTagPage() CompileTag() CompileCategoryPage() CompileCategory() CompileAbout() storageBlogMap() log.Log.Debug("compilation complete...") } // 存储文章 func storageBlogMap() { storage, err := utils.NewStorage(config.GlobalConf.Storage, "storage.json") if err != nil { panic(err) } err = storage.Store(GetPostList()) if err != nil { panic(err) } } // CompileHome 编译主页 func CompileHome() { title := config.GlobalConf.HomeTitle if len(strings.TrimSpace(title)) == 0 { data["title"] = "home page" } else { data["title"] = title } data["postList"] = GetHomePostList() data["categoryList"] = GetCategoryList() data["tagList"] = GetTagList() err := utils.MkDir(config.GlobalConf.Html) if err != nil { panic(err) } homePath := path.Join(config.GlobalConf.Html, "index.html") htmlFile, err := os.Create(homePath) if err != nil { panic(err) } t, err := template.New("main.tpl").Funcs(funcMaps).ParseFiles(config.GlobalConf.Theme+"/layout/main.tpl", config.GlobalConf.Theme+"/layout/home.tpl") if err != nil { panic(err) } err = t.Execute(htmlFile, data) if err != nil { panic(err) } } // 拷贝静态文件 func copyAssetsFile() { err := utils.CopyDir(path.Join(config.GlobalConf.Theme, "assets"), path.Join(config.GlobalConf.Html, "assets")) if err != nil { panic(err) } } // 校验模板文件 func checkThemeFile() { if _, err := os.Stat(config.GlobalConf.Theme); os.IsNotExist(err) { panic("you need to initialize and add the template file first.") } } // CompileCategoryPage 编译分类导航页 func CompileCategoryPage() { subTitle := config.GlobalConf.CategoryTitle if len(strings.TrimSpace(subTitle)) == 0 { data["subtitle"] = "article category" } else { data["subtitle"] = subTitle } data["categoryList"] = GetCategoryList() data["tagList"] = GetTagList() filepath := path.Join(config.GlobalConf.Html, "category") err := utils.MkDir(filepath) if err != nil { panic(err) } filename := path.Join(filepath, "index.html") htmlFile, err := os.Create(filename) if err != nil { panic(err) } t, err := template.New("main.tpl").Funcs(funcMaps).ParseFiles(config.GlobalConf.Theme+"/layout/category.tpl", config.GlobalConf.Theme+"/layout/main.tpl") if err != nil { panic(err) } err = t.Execute(htmlFile, data) if err != nil { panic(err) } } // CompileCategory 编译分类页 func CompileCategory() { cateList := GetCategoryList() data["categoryList"] = cateList data["tagList"] = GetTagList() for _, cate := range cateList { data["subtitle"] = cate.Name data["pageTitle"] = cate.Name data["content"] = cate.Posts data["count"] = cate.Count filepath := path.Join(config.GlobalConf.Html, "category", cate.Name) err := utils.MkDir(filepath) if err != nil { panic(err) } filename := path.Join(filepath, "index.html") htmlFile, err := os.Create(filename) if err != nil { panic(err) } t, err := template.New("main.tpl").Funcs(funcMaps).ParseFiles(config.GlobalConf.Theme+"/layout/page.tpl", config.GlobalConf.Theme+"/layout/main.tpl") if err != nil { panic(err) } err = t.Execute(htmlFile, data) if err != nil { panic(err) } } } // CompileTagPage 编译标签导航页 func CompileTagPage() { subTitle := config.GlobalConf.TagTitle if len(strings.TrimSpace(subTitle)) == 0 { data["subtitle"] = "article tags" } else { data["subtitle"] = subTitle } data["categoryList"] = GetCategoryList() data["tagList"] = GetTagList() filePath := path.Join(config.GlobalConf.Html, "tag") err := utils.MkDir(filePath) if err != nil { panic(err) } fileName := path.Join(filePath, "index.html") htmlFile, err := os.Create(fileName) if err != nil { panic(err) } t, err := template.New("main.tpl").Funcs(funcMaps).ParseFiles(config.GlobalConf.Theme+"/layout/tag.tpl", config.GlobalConf.Theme+"/layout/main.tpl") if err != nil { panic(err) } err = t.Execute(htmlFile, data) if err != nil { panic(err) } } // CompileTag 编译标签页 func CompileTag() { tags := GetTagList() data["categoryList"] = GetCategoryList() data["tagList"] = GetTagList() for _, tag := range tags { data["subtitle"] = tag.Name data["pageTitle"] = tag.Name data["content"] = tag.Posts data["count"] = tag.Count data["tpl"] = config.GlobalConf.Theme + "/layout/page.html" filepath := path.Join(config.GlobalConf.Html, "tag", tag.Name) err := utils.MkDir(filepath) if err != nil { panic(err) } filename := path.Join(filepath, "index.html") htmlFile, err := os.Create(filename) if err != nil { panic(err) } t, err := template.New("main.tpl").Funcs(funcMaps).ParseFiles(config.GlobalConf.Theme+"/layout/page.tpl", config.GlobalConf.Theme+"/layout/main.tpl") if err != nil { panic(err) } err = t.Execute(htmlFile, data) if err != nil { panic(err) } } } // CompileAbout 编译关于我页 func CompileAbout() { about, err := GetAbout() if err != nil { panic(err) } subTitle := config.GlobalConf.AboutTitle if len(strings.TrimSpace(subTitle)) == 0 { data["subtitle"] = "about me" } else { data["subtitle"] = subTitle } data["post"] = about data["categoryList"] = GetCategoryList() data["tagList"] = GetTagList() filePath := path.Join(config.GlobalConf.Html, "about.html") htmlFile, err := os.Create(filePath) if err != nil { panic(err) } t, err := template.New("main.tpl").Funcs(funcMaps).ParseFiles(config.GlobalConf.Theme+"/layout/post.tpl", config.GlobalConf.Theme+"/layout/main.tpl") if err != nil { panic(err) } err = t.Execute(htmlFile, data) if err != nil { panic(err) } } // CompilePost 编译文章页 func CompilePost() { data["categoryList"] = GetCategoryList() data["tagList"] = GetTagList() for _, post := range postList { data["subtitle"] = post.Title data["description"] = strings.TrimSpace(post.Summary) data["keywords"] = strings.Join(post.Tags, ",") data["post"] = post url := CreatePostLink(post) filePath := path.Join(config.GlobalConf.Html, url) err := utils.MkDir(filePath) if err != nil { panic(err) } fileName := path.Join(filePath, "index.html") htmlFile, err := os.Create(fileName) if err != nil { panic(err) } t, err := template.New("main.tpl").Funcs(funcMaps).ParseFiles(config.GlobalConf.Theme+"/layout/post.tpl", config.GlobalConf.Theme+"/layout/main.tpl") if err != nil { panic(err) } err = t.Execute(htmlFile, data) if err != nil { panic(err) } } } // CompileArchive 编译归档页 func CompileArchive() { subTitle := config.GlobalConf.ArchiveTitle if len(strings.TrimSpace(subTitle)) == 0 { data["subtitle"] = "article archiving" } else { data["subtitle"] = subTitle } data["archive"] = GetArchive() data["categoryList"] = GetCategoryList() data["tagList"] = GetTagList() filePath := path.Join(config.GlobalConf.Html, "archive") err := utils.MkDir(filePath) if err != nil { panic(err) } fileName := path.Join(filePath, "index.html") htmlFile, err := os.Create(fileName) if err != nil { panic(err) } t, err := template.New("main.tpl").Funcs(funcMaps).ParseFiles(config.GlobalConf.Theme+"/layout/archive.tpl", config.GlobalConf.Theme+"/layout/main.tpl") if err != nil { panic(err) } err = t.Execute(htmlFile, data) if err != nil { panic(err) } } ================================================ FILE: core/service/post.go ================================================ package service import ( "bufio" "bytes" "fmt" "gopkg.in/yaml.v3" "io" "lacerate/core/config" "lacerate/core/log" "lacerate/core/model" "lacerate/core/utils" "os" "path" "path/filepath" "regexp" "sort" "strings" "time" ) // 文章列表 var postList []*model.Post // Content markdown内容 type Content struct { Title string Description string Date string Categories []string Tags []string Content string } // GetPostList 获取文章列表 func GetPostList() []*model.Post { return postList } // CreateMarkdown 创建markdown文件 func CreateMarkdown(filename string) string { file := path.Join(config.GlobalConf.Markdown, filename+".md") _, err := os.Stat(file) if !os.IsNotExist(err) { log.Log.Errorf("The file already exists.") os.Exit(1) } src, err := utils.CreateFile(config.GlobalConf.Markdown, filename+".md") if err != nil { panic(err) } date := time.Now().Format("2006-01-02") now := time.Now().Format("15:04:05") informationContent := `--- date: ` + date + ` time: ` + now + ` title: ` + filename + ` categories: - tagList: - - ---` err = utils.WriteFile(src, informationContent) if err != nil { panic(err) } return src } // MarkdownList 获取markdown文件夹下所有文件 func MarkdownList(markdownDir string) (markdownList []string) { _ = filepath.Walk(markdownDir, func(path string, f os.FileInfo, err error) error { if err != nil { //忽略错误 return err } if f.IsDir() { return nil } //if strings.ToLower(f.Name()) == "readme.md" { // return nil //} if f.Name() == "about.md" { return nil } if strings.HasSuffix(f.Name(), ".md") { markdownList = append(markdownList, path) } return nil }) return markdownList } // LoadPostList 加载文章列表 func LoadPostList() { postList = make([]*model.Post, 0) markdownList := MarkdownList(config.GlobalConf.Markdown) for _, markdown := range markdownList { post, err := loadMarkdownContent(markdown) if err == nil { post.Url = CreatePostLink(post) postList = append(postList, post) for _, _cate := range post.Category { if len(_cate) <= 0 { continue } category := categoryList[_cate] if category == nil { category = &model.Category{Count: 0, Name: _cate, Posts: make([]*model.Post, 0), Url: "/category/" + _cate} categoryList[_cate] = category } category.Count += 1 category.Posts = append(category.Posts, post) } for _, _tag := range post.Tags { if len(_tag) <= 0 { continue } tag := tagList[_tag] if tag == nil { tag = &model.Tag{Count: 0, Name: _tag, Posts: make([]*model.Post, 0), Url: "/tag/" + _tag} tagList[_tag] = tag } tag.Count += 1 tag.Posts = append(tag.Posts, post) } } else { panic(err) } } sort.Sort(model.PostList(postList)) } // 加载markdown内容生成文章 func loadMarkdownContent(file string) (post *model.Post, err error) { post = &model.Post{} content, err := ReadMarkdownContent(file) if err != nil { return nil, err } if post.Summary == "" { summaryLine := config.GlobalConf.SummaryLine post.Summary, err = generateSummary(content.Content, summaryLine) if err != nil { return nil, err } } post.Title = content.Title post.Description = content.Description post.Category = content.Categories post.Tags = content.Tags post.Content = utils.MarkdownToHtml(content.Content) post.CreatedAt = utils.Str2Unix("2006-01-02", content.Date) return post, nil } // 生成摘要 func generateSummary(content string, lines int) (string, error) { buff := bufio.NewReader(bytes.NewBufferString(content)) dst := "" for lines > 0 { line, err := buff.ReadString('\n') if err != nil || io.EOF == err { break } if strings.Contains(strings.ToLower(line), "[toc]") { continue } reg := regexp.MustCompile(`!\[(.*)\]\((.*)\)`) if reg.MatchString(line) { continue } if strings.Trim(line, "\r\n\t ") == "```" { continue } dst += line lines-- } return utils.MarkdownToHtml(dst), nil } // ReadMarkdownContent 读取markdown内容 func ReadMarkdownContent(path string) (content *Content, err error) { f, err := os.Open(path) if err != nil { return nil, err } defer func(f *os.File) { _ = f.Close() }(f) br := bufio.NewReader(f) line, err := br.ReadString('\n') if err != nil { return nil, err } if !strings.HasPrefix(line, "---") { err = fmt.Errorf("markdown file format error, the file header must start with '---': " + path) return nil, err } buf := bytes.NewBuffer(nil) for { line, err = br.ReadString('\n') if err != nil { if err != io.EOF { return nil, err } } if strings.HasPrefix(line, "---") { break } buf.WriteString(line) } err = yaml.Unmarshal(buf.Bytes(), &content) contentByte, err := io.ReadAll(br) if err != nil { return nil, err } fi, _ := f.Stat() if content.Title == "" { content.Title = strings.Replace(strings.TrimRight(fi.Name(), ".md"), config.GlobalConf.Markdown+"/", "", 1) } if content.Date == "" { content.Date = utils.Format(fi.ModTime().Unix()) } content.Content = string(contentByte) return } // CreatePostLink 创建文章链接 func CreatePostLink(art *model.Post) string { t := time.Unix(art.CreatedAt, 0) year, month, day := t.Date() link := fmt.Sprintf("/%s/%d/%d/%d/%s/", "post", year, month, day, utils.Convert(art.Title)) return link } // GetHomePostList 获取首页文章列表 func GetHomePostList() []*model.Post { num := config.GlobalConf.HomePostNum if num == 0 || len(postList) <= num { num = len(postList) } homePostList := make([]*model.Post, num) copy(homePostList, postList) return homePostList } ================================================ FILE: core/service/tag.go ================================================ package service import ( "lacerate/core/model" ) // 标签列表 var tagList map[string]*model.Tag // 初始化 func init() { tagList = make(map[string]*model.Tag) } // GetTagList 获取标签列表 func GetTagList() map[string]*model.Tag { return tagList } ================================================ FILE: core/service/watcher.go ================================================ package service import ( "github.com/fsnotify/fsnotify" "lacerate/core/log" "os" "strings" "time" ) var ( // 文件事件与事件时间字典 eventTime = make(map[string]int64) // 触发编译时间 scheduleTime time.Time ) // Watch 文件监控 type Watch struct { Paths []string // 监控文件路径 Suffix []string // 监控文件后缀 } // NewWatch 新建文件监控 func NewWatch(paths []string, suffix []string) *Watch { return &Watch{paths, suffix} } // Watcher 文件监控 func (w *Watch) Watcher() { // 初始化监听器 log.Log.Info("initialize the file listener...") watcher, err := fsnotify.NewWatcher() if err != nil { panic("failed to initialize the file listener: " + err.Error()) } go func() { for { select { case event := <-watcher.Events: build := true if !w.checkFileSuffix(event.Name) { continue } if event.Op&fsnotify.Chmod == fsnotify.Chmod { log.Log.Infof(" skin %s ", event) continue } mt := w.getFileModTime(event.Name) if t := eventTime[event.Name]; mt == t { log.Log.Infof(" skin %s ", event.String()) build = false } eventTime[event.Name] = mt if build { go func() { scheduleTime = time.Now().Add(1 * time.Second) for { time.Sleep(scheduleTime.Sub(time.Now())) if time.Now().After(scheduleTime) { break } return } log.Log.Infof("triggers a compilation event: %s ", event) Compile() }() } case err := <-watcher.Errors: log.Log.Errorf("monitoring failed %s ", err) } } }() for _, path := range w.Paths { log.Log.Infof("listen to folders: [%s] ", path) err = watcher.Add(path) if err != nil { log.Log.Errorf("failed to monitor folder: [%s] ", err) os.Exit(2) } } log.Log.Debug("the monitoring is successfully initialized...") } // 校验文件后缀名 func (w *Watch) checkFileSuffix(name string) bool { for _, s := range w.Suffix { if strings.HasSuffix(name, "."+s) { return true } } return false } // 获取文件最后更新时间 func (w *Watch) getFileModTime(path string) int64 { path = strings.Replace(path, "\\", "/", -1) f, err := os.Open(path) if err != nil { log.Log.Errorf("the file failed to open [ %s ]", err) return time.Now().Unix() } defer func(f *os.File) { _ = f.Close() }(f) fi, err := f.Stat() if err != nil { log.Log.Errorf("unable to get file information [ %s ]", err) return time.Now().Unix() } return fi.ModTime().Unix() } ================================================ FILE: core/utils/crypto.go ================================================ package utils import ( "crypto/md5" "encoding/hex" ) // Xmd5 md5编码 func Xmd5(text string) string { ctx := md5.New() ctx.Write([]byte(text)) return hex.EncodeToString(ctx.Sum(nil)) } ================================================ FILE: core/utils/file.go ================================================ package utils import ( "bufio" "errors" "fmt" "io" "os" "path" ) // CreateFile 创建文件 func CreateFile(dir string, name string) (string, error) { src := path.Join(dir, name) _, err := os.Stat(src) if os.IsExist(err) { return src, nil } if err := os.MkdirAll(dir, 0777); err != nil { if os.IsPermission(err) { panic("insufficient permissions") } return "", err } _, err = os.Create(src) if err != nil { return "", err } return src, nil } // MkDir 创建路径 func MkDir(filepath string) error { if _, err := os.Stat(filepath); err != nil { if os.IsNotExist(err) { err = os.MkdirAll(filepath, os.ModePerm) if err != nil { return err } } else { return err } } return nil } // CopyFile 复制文件 func CopyFile(src, des string) (w int64, err error) { srcFile, err := os.Open(src) if err != nil { return 0, err } defer func(srcFile *os.File) { _ = srcFile.Close() }(srcFile) desFile, err := os.Create(des) if err != nil { return 0, err } defer func(desFile *os.File) { _ = desFile.Close() }(desFile) return io.Copy(desFile, srcFile) } // CopyDir 复制路径 func CopyDir(source string, dest string) (err error) { fi, err := os.Stat(source) if err != nil { return err } if !fi.IsDir() { return errors.New("source is not a directory") } _, err = os.Open(dest) if os.IsExist(err) { err = os.RemoveAll(dest) if err != nil { return err } } err = os.MkdirAll(dest, fi.Mode()) if err != nil { return err } entries, err := os.ReadDir(source) for _, entry := range entries { sfp := source + "/" + entry.Name() dfp := dest + "/" + entry.Name() if entry.IsDir() { err = CopyDir(sfp, dfp) if err != nil { panic(err) } } else { _, err = CopyFile(sfp, dfp) if err != nil { panic(err) } } } return } // WriteFile 写文件 func WriteFile(file string, text string) error { f, err := os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660) if err != nil { _ = fmt.Errorf("open file error: %s", err) } defer func(f *os.File) { _ = f.Close() }(f) w := bufio.NewWriter(f) _, err = w.Write([]byte(text)) if err != nil { return err } return w.Flush() } ================================================ FILE: core/utils/markdown.go ================================================ package utils import ( bf "github.com/russross/blackfriday" "log" "regexp" "strings" ) // TocTitle 目录标题 var TocTitle = "

目录:

" // nav正则 var navRegex = regexp.MustCompile(`(?ismU)`) // MarkdownToHtml markdown转html func MarkdownToHtml(content string) (str string) { defer func() { e := recover() if e != nil { str = content log.Println("Render markdown err:", e) } }() htmlFlags := 0 if strings.Contains(strings.ToLower(content), "[toc]") { htmlFlags |= bf.HTML_TOC } htmlFlags |= bf.HTML_USE_XHTML htmlFlags |= bf.HTML_USE_SMARTYPANTS htmlFlags |= bf.HTML_SMARTYPANTS_FRACTIONS htmlFlags |= bf.HTML_SMARTYPANTS_LATEX_DASHES htmlFlags |= bf.HTML_FOOTNOTE_RETURN_LINKS renderer := bf.HtmlRenderer(htmlFlags, "", "") extensions := 0 extensions |= bf.EXTENSION_NO_INTRA_EMPHASIS extensions |= bf.EXTENSION_TABLES extensions |= bf.EXTENSION_FENCED_CODE extensions |= bf.EXTENSION_AUTOLINK extensions |= bf.EXTENSION_STRIKETHROUGH extensions |= bf.EXTENSION_SPACE_HEADERS extensions |= bf.EXTENSION_HARD_LINE_BREAK extensions |= bf.EXTENSION_FOOTNOTES str = string(bf.Markdown([]byte(content), renderer, extensions)) if htmlFlags&bf.HTML_TOC != 0 { found := navRegex.FindIndex([]byte(str)) if len(found) > 0 { toc := str[found[0]:found[1]] toc = TocTitle + toc str = str[found[1]:] reg := regexp.MustCompile(`\[toc\]|\[TOC\]`) str = reg.ReplaceAllString(str, toc) } } return str } ================================================ FILE: core/utils/slice.go ================================================ package utils // Count 统计分片长度 func Count(sl []string) (num int) { num = 0 for _, s := range sl { if s != "" { num += 1 } } return } // Lt 判断小于 func Lt(a, b int) bool { return a < b } // Eq 判断等于 func Eq(a, b int) bool { return a == b } // Gt 判断大于 func Gt(a, b int) bool { return a > b } ================================================ FILE: core/utils/storage.go ================================================ package utils import ( "bufio" "encoding/json" "os" "path" ) // Storage 文件存储 type Storage struct { storagePath string // 文件存储路径 name string // 文件名 } // NewStorage 新建文件存储 func NewStorage(storagePath, fileName string) (*Storage, error) { if _, err := os.Stat(storagePath); err != nil { if os.IsNotExist(err) { err = os.MkdirAll(storagePath, os.ModePerm) if err != nil { return nil, err } } else { return nil, err } } return &Storage{storagePath: storagePath, name: fileName}, nil } // Get 解析文件存储 func (s *Storage) Get(value interface{}) error { var filepath = path.Join(s.storagePath, s.name) return storageRead(filepath, value) } // Store 缓存文件存储 func (s *Storage) Store(value interface{}) error { var filepath = path.Join(s.storagePath, s.name) return storageWrite(filepath, value) } // Del 删除文件存储 func (s *Storage) Del() error { var filepath = path.Join(s.storagePath, s.name) return os.Remove(filepath) } // 读文件存储 func storageRead(storagePath string, value interface{}) error { f, err := os.OpenFile(storagePath, os.O_RDWR, 0666) defer func(f *os.File) { _ = f.Close() }(f) if err != nil { return err } return json.NewDecoder(bufio.NewReader(f)).Decode(&value) } // 写文件存储 func storageWrite(storagePath string, value interface{}) error { content, err := json.Marshal(value) if err != nil { return err } return os.WriteFile(storagePath, content, os.ModePerm) } ================================================ FILE: core/utils/string.go ================================================ package utils import ( "strings" ) // Convert 文章标题转换文章链接 func Convert(str string) string { str = strings.ToLower(str) ss := strings.SplitN(str, " ", -1) return strings.Join(ss, "-") } ================================================ FILE: core/utils/template.go ================================================ package utils import "html/template" // Unescaped 解析html func Unescaped(x string) interface{} { return template.HTML(x) } ================================================ FILE: core/utils/time.go ================================================ package utils import "time" // Format 日期格式化 func Format(unix int64) string { t := time.Unix(unix, 0) return t.Format("2006-01-02") } // Month 月份格式化 func Month(unix int64) string { t := time.Unix(unix, 0) return t.Format("1") } // Year 年份格式化 func Year(unix int64) string { t := time.Unix(unix, 0) return t.Format("2006") } // CMonth 日期格式化(不带年份) func CMonth(unix int64) string { t := time.Unix(unix, 0) return t.Format("01-02") } // Str2Unix 字符串转时间 func Str2Unix(layout, timeStr string) int64 { tm, _ := time.Parse(layout, timeStr) return tm.Unix() } ================================================ FILE: doc/博客编写指南.md ================================================ 博客(markdown)需要以 `---` 开头进行说明: ```md --- date: 日期 xxxx-xx-xx title: 标题 categories: - 分类 tags: - 标签 --- ``` ================================================ FILE: doc/配置文件说明.md ================================================ `config.yml`配置说明: ```yaml # 站点信息 title: 站点标题 subtitle: 站点子标题 description: 站点描述 keywords: 站点关键字 # 作者信息 author: 作者名称 avatar: 头像图标 github: github地址 email: 邮箱 # 配置信息 summary_line: 首页文章行数 home_post_num: 首页文章数量 # 文件存储 theme: 模板文件夹 markdown: markdown文件夹 html: html文件夹 storage: 存储文件夹 # 文件监听 paths: - 监听文件 suffix: - 监听文件后缀 # 自定义信息 home_title: 主页子标题 archive_title: 归档页面子标题 tag_title: 标签页面子标题 category_title: 分类页面子标题 about_title: 关于我页面子标题 ``` ================================================ FILE: go.mod ================================================ module lacerate go 1.22 require ( github.com/fatih/color v1.16.0 github.com/fsnotify/fsnotify v1.7.0 github.com/russross/blackfriday v1.6.0 github.com/sirupsen/logrus v1.9.3 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect golang.org/x/sys v0.18.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) ================================================ FILE: go.sum ================================================ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: theme/blog/assets/css/basic.css ================================================ html { height: 100%; max-height: 100%; padding: 0; margin: 0; } body { padding: 0; margin: 0; line-height: 1.6em; } .clear { clear: both; display: block; overflow: hidden; visibility: hidden; width: 0; height: 0; } h1, h2, h3, h4, h5, h6 { text-rendering: optimizeLegibility; line-height: 1; margin: 2rem 0; } h1 { font-size: 2.1rem; line-height: 1.2em; } h2 { font-size: 1.9rem; line-height: 1.2em; } h3 { font-size: 1.75rem; } h4 { font-size: 1.3rem; } h5 { font-size: 1.3rem; } h6 { font-size: 1.3rem; } img { max-width: 100%; height: auto; } p, ul, ol, dl { margin: 1em 0; } ol ol, ul ul, ul ol, ol ul { margin: 0.4em 0; } ul p, ol p, li p, .content li p, blockquote p, .content blockquote p, .post blockquote p, .post li p { margin: 0; overflow: visible; } a img { border: none; } dl dt { float: left; width: 180px; overflow: hidden; clear: left; text-align: right; text-overflow: ellipsis; white-space: nowrap; font-weight: bold; margin-bottom: 1em; } dl dd { margin-left: 200px; margin-bottom: 1em; } hr { display: block; height: 1px; border: 0; border-top: 1px solid #efefef; margin: 3.2em 0; padding: 0; } blockquote { -moz-box-sizing: border-box; box-sizing: border-box; margin: 1.6em 0 1.6em -2.3em; padding: 0 0 0 1.6em; border-radius: 0.4em; border-left: #FF6600 0.4em solid; } blockquote p { margin: 0.8em 0; } blockquote small { display: inline-block; margin: 0.8em 0 0.8em 1.5em; font-size: 0.9em; color: #ccc; } blockquote small:before { content: '\2014 \00A0'; } blockquote cite { font-weight: bold; } blockquote cite a { font-weight: normal; } mark { background-color: #ffc336; } code, tt { padding: 1px 3px; font-family: Inconsolata, monospace, sans-serif; font-size: 0.85em; white-space: pre-wrap; border: 1px solid #E3EDF3; background: #f7f7f9; color: #d14; border-radius: 2px; } .label { padding: 1px 5px 1px; font-size: 11.25px; font-weight: bold; font-family: 'Lato', "Open Sans", "PingFang SC", "Microsoft YaHei", sans-serif; color: #fff; text-transform: uppercase; background-color: #999; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; margin-left: 1mm; } .label-important { background-color: #b94a48; margin-right: 2mm; } pre { -moz-box-sizing: border-box; box-sizing: border-box; margin: 1.6em 0; border: 1px solid #E3EDF3; width: 100%; padding: 10px; font-family: Inconsolata, monospace, sans-serif; font-size: 0.9em; white-space: pre; overflow: auto; background: #F7FAFB; border-radius: 3px; } pre code, tt { font-size: inherit; white-space: -moz-pre-wrap; white-space: pre-wrap; background: transparent; border: none; color: #333; padding: 0; } kbd { display: inline-block; margin-bottom: 0.4em; padding: 1px 8px; border: #ccc 1px solid; color: #666; text-shadow: #fff 0 1px 0; font-size: 0.9em; font-weight: bold; background: #f4f4f4; border-radius: 4px; box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 1px 0 0 white inset; } table { -moz-box-sizing: border-box; box-sizing: border-box; margin: 1em 0; width: 100%; max-width: 100%; border-width: 1px; border-style: solid; background-color: transparent; } table, table tr, table tr td, table tr th { border-color: #e5e5e5; } table th { color: #666666; background-color: #fdfdfd; } tr th { border-bottom-width: 1px; border-bottom-style: solid; text-align: left; } tr th, tr td { padding: 5px 20px; border-right: 1px solid; font-size: 1rem; } tr th:last-child, tr td:last-child { border-right: 0px; } table th { font-weight: bold; } table tbody > tr:nth-child(odd) > td, table tbody > tr:nth-child(odd) > th { background-color: #f9f9f9; } .gist { font-size: 12px; } .gist table { margin: 0; width: auto; } .gist table pre { font-size: 12px; } .gist table .line-numbers { font-size: 12px; } .codehilitetable { margin: 0; width: auto; } .codehilitetable tr th { border: none; } .codehilitetable tr th, .codehilitetable tr td { padding: 0; border: none; } .codehilitetable .linenos pre { background: transparent; border: none; } .codehilitetable pre { margin: 0; } .toc { border: 1px solid #f0f0f0; margin-bottom: 20px; padding: 10px 30px; } #fb_comments_container { overflow: hidden; margin: 0 auto; } #fb_comments_container #fb_comments { list-style-type: none; padding: 0; } #fb_comments_container #fb_comments h1 { font-size: 1.3em; } #fb_comments_container #fb_comments h2 { font-size: 1.2em; } #fb_comments_container #fb_comments h3 { font-size: 1.1em; } #fb_comments_container #fb_comments h4, #fb_comments_container #fb_comments h5, #fb_comments_container #fb_comments h6 { font-size: 1.05em; } #fb_comments_container #fb_comments .comment { position: relative; padding: 25px 0; border-bottom: 1px solid rgba(150, 150, 150, 0.2); *border-bottom: 1px solid #f0f0f0; } #fb_comments_container #fb_comments .comment .avatar { position: absolute; top: 25px; left: 0; width: 50px; float: left; } #fb_comments_container #fb_comments .comment .avatar img { width: 48px; border: none; border-radius: 5px; margin: 0; } #fb_comments_container #fb_comments .comment .comment_body, #fb_comments_container #fb_comments .comment .c_content { margin-left: 70px; display: block; } #fb_comments_container #fb_comments .comment .comment_body p, #fb_comments_container #fb_comments .comment .c_content p { margin: 5px 0 15px 0; padding: 0; line-height: 1.8; } #fb_comments_container #fb_comments .comment .comment_body .author, #fb_comments_container #fb_comments .comment .c_content .author { line-height: 1.5em; margin: 0; padding: 0; } #fb_comments_container #fb_comments .comment .comment_body .author b, #fb_comments_container #fb_comments .comment .c_content .author b { color: #555; } #fb_comments_container #fb_comments .comment .comment_body .author small, #fb_comments_container #fb_comments .comment .c_content .author small { font-weight: normal; padding-left: 10px; font-size: 0.7em; color: #666; } #fb_new_comment { padding-bottom: 50px; } #fb_new_comment textarea { border-radius: 5px; height: 80px; width: 98%; padding: 5px; font-size: 1em; border: 1px solid rgba(150, 150, 150, 0.5); *border: 1px solid #a8a8a8; line-height: 1.5; } #fb_new_comment .comment_error { color: red; text-align: center; display: block; font-size: 0.8em; padding-top: 1em; } #fb_new_comment .c_button:hover { background: #E60900; color: #fff; text-decoration: none; } #fb_new_comment .c_button, #fb_new_comment #c_submit { cursor: pointer; font-family: "Hiragino Sans GB", "Microsoft YaHei", sans-serif; font-size: 1em; line-height: 1.3em; letter-spacing: 1px; border-radius: 5px; padding: 5px 5px 2px 5px; } #fb_new_comment .input_body { margin-top: 10px; } #fb_new_comment .input_body ul { list-style: none; padding: 5px 0; margin: auto 0; } #fb_new_comment .input_body ul li { float: left; margin-right: 2.2%; *margin-right: 22px; } #fb_new_comment .input_body ul li label { line-height: 1em; } #fb_new_comment .input_body ul li input { border-radius: 5px; border: 1px solid #ddd; padding: 5px; background: rgba(255, 255, 255, 0.5); margin: 0 0 10px 0; } #SwfStore_farbox_0 { height: 0; overflow: hidden; } @media screen and (max-width: 320px) { #fb_comments .c_content, #fb_comments .comment_body { margin-left: 57px; } } .codehilite code, .codehilite pre { word-break: break-word; color: #fdce93; background-color: #3f3f3f; padding: 10px; border-radius: 3px; } .codehilite .hll { background-color: #222; } .codehilite .c { color: #7f9f7f; } .codehilite .err { color: #e37170; background-color: #3d3535; } .codehilite .g { color: #7f9f7f; } .codehilite .k { color: #f0dfaf; } .codehilite .l { color: #ccc; } .codehilite .n { color: #dcdccc; } .codehilite .o { color: #f0efd0; } .codehilite .x { color: #ccc; } .codehilite .p { color: #41706f; } .codehilite .cm { color: #7f9f7f; } .codehilite .cp { color: #7f9f7f; } .codehilite .c1 { color: #7f9f7f; } .codehilite .cs { color: #cd0000; font-weight: bold; } .codehilite .gd { color: #cd0000; } .codehilite .ge { color: #ccc; font-style: italic; } .codehilite .gr { color: red; } .codehilite .gh { color: #dcdccc; font-weight: bold; } .codehilite .gi { color: #00cd00; } .codehilite .go { color: gray; } .codehilite .gp { color: #dcdccc; font-weight: bold; } .codehilite .gs { color: #ccc; font-weight: bold; } .codehilite .gu { color: purple; font-weight: bold; } .codehilite .gt { color: #0040D0; } .codehilite .kc { color: #dca3a3; } .codehilite .kd { color: #ffff86; } .codehilite .kn { color: #dfaf8f; font-weight: bold; } .codehilite .kp { color: #cdcf99; } .codehilite .kr { color: #cdcd00; } .codehilite .kt { color: #00cd00; } .codehilite .ld { color: #cc9393; } .codehilite .m { color: #8cd0d3; } .codehilite .s { color: #cc9393; } .codehilite .na { color: #9ac39f; } .codehilite .nb { color: #efef8f; } .codehilite .nc { color: #efef8f; } .codehilite .no { color: #ccc; } .codehilite .nd { color: #ccc; } .codehilite .ni { color: #c28182; } .codehilite .ne { color: #c3bf9f; font-weight: bold; } .codehilite .nf { color: #efef8f; } .codehilite .nl { color: #ccc; } .codehilite .nn { color: #8fbede; } .codehilite .nx { color: #ccc; } .codehilite .py { color: #ccc; } .codehilite .nt { color: #9ac39f; } .codehilite .nv { color: #dcdccc; } .codehilite .ow { color: #f0efd0; } .codehilite .w { color: #ccc; } .codehilite .mf { color: #8cd0d3; } .codehilite .mh { color: #8cd0d3; } .codehilite .mi { color: #8cd0d3; } .codehilite .mo { color: #8cd0d3; } .codehilite .sb { color: #cc9393; } .codehilite .sc { color: #cc9393; } .codehilite .sd { color: #cc9393; } .codehilite .s2 { color: #cc9393; } .codehilite .se { color: #cc9393; } .codehilite .sh { color: #cc9393; } .codehilite .si { color: #cc9393; } .codehilite .sx { color: #cc9393; } .codehilite .sr { color: #cc9393; } .codehilite .s1 { color: #cc9393; } .codehilite .ss { color: #cc9393; } .codehilite .bp { color: #efef8f; } .codehilite .vc { color: #efef8f; } .codehilite .vg { color: #dcdccc; } .codehilite .vi { color: #ffffc7; } .codehilite .il { color: #8cd0d3; } @media (max-width: 480px) { code { padding: 0; margin: 0; } } ================================================ FILE: theme/blog/assets/css/style.css ================================================ html { background-color: #fff; -webkit-font-smoothing: antialiased; } body { color: rgba(0, 0, 0, 0.5); font-family: georgia,palatino,"Helvetica Neue", Helvetica, "Hiragino Sans GB", "STHeitiSC-Light", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; font-size: 15px; width: 100%; margin: 0 auto 30px auto; background-color: #fff; } ::selection{ background: #ec6200; color:#FFF; } ::-moz-selection{ background:#ec6200; color:#FFF; } p { line-height: 1.9em; font-weight: 400; font-size: 16px; } a { text-decoration: none; } a:link, a:visited { opacity: 1; -webkit-transition: all 0.15s linear; -moz-transition: all 0.15s linear; -o-transition: all 0.15s linear; -ms-transition: all 0.15s linear; transition: all 0.15s linear; color: #424242; } a:hover, a:active { color: #555; } .animated { -webkit-animation-fill-mode: both; -moz-animation-fill-mode: both; -ms-animation-fill-mode: both; -o-animation-fill-mode: both; animation-fill-mode: both; -webkit-animation-duration: 1s; -moz-animation-duration: 1s; -ms-animation-duration: 1s; -o-animation-duration: 1s; animation-duration: 1s; } .animated.hinge { -webkit-animation-duration: 1s; -moz-animation-duration: 1s; -ms-animation-duration: 1s; -o-animation-duration: 1s; animation-duration: 1s; } @-webkit-keyframes fadeInDown { 0% { opacity: 0; -webkit-transform: translateY(-20px); } 100% { opacity: 1; -webkit-transform: translateY(0); } } @-moz-keyframes fadeInDown { 0% { opacity: 0; -moz-transform: translateY(-20px); } 100% { opacity: 1; -moz-transform: translateY(0); } } @-o-keyframes fadeInDown { 0% { opacity: 0; -o-transform: translateY(-20px); } 100% { opacity: 1; -o-transform: translateY(0); } } @keyframes fadeInDown { 0% { opacity: 0; transform: translateY(-20px); } 100% { opacity: 1; transform: translateY(0); } } .fadeInDown { -webkit-animation-name: fadeInDown; -moz-animation-name: fadeInDown; -o-animation-name: fadeInDown; animation-name: fadeInDown; } .content { height: auto; float: right; width: 70%; margin-top: 60px; } .page-top { width: 69.9%; position: fixed; right: 0; z-index: 9999; background-color: #fff; height: 60px; border-bottom: 1px solid #f2f2f2; } .page-top .nav { list-style: none; padding: 18px 30px; float: left; font-size: 12px; } .page-top .nav li { position: relative; display: initial; padding-right: 20px; } .page-top .nav a { color: #5A5A5A; } .page-top .nav a:hover { color: #464545; } .page-top .nav a.current { color: #5A5A5A; padding-bottom: 22px; border-bottom: 1px solid #5A5A5A; } .page-top .information { float: right; padding-top: 12px; padding-right: 20px; } .page-top .information .avatar { float: right; } .page-top .information .avatar img { width: 32px; height: 32px; border-radius: 300px; } .page-top .information .back_btn { float: left; padding-top: 5px; margin-right: -10px; } .page-top .information .back_btn li { display: initial; padding-right: 40px; } .sidebar { width: 30%; -webkit-background-size: cover; background-size: cover; background-color: #fff; height: 100%; transition: 0.8s; top: 0; left: 0; position: fixed; border-right: 1px solid #f2f2f2; } .sidebar .logo-title { text-align: center; padding-top: 240px; } .sidebar .logo-title .description { font-size: 14px; color: #565654; } .sidebar .logo-title .logo { margin: 0 auto; } .sidebar .logo-title .title h3 { text-transform: uppercase; font-size: 2rem; font-weight: bold; letter-spacing: 2px; line-height: 1; margin: 0; } .sidebar .logo-title .title a { text-decoration: none; color: #464646; font-weight: bold; } .sidebar .social-links { list-style: none; padding: 0; font-size: 14px; text-align: center; } .sidebar .social-links i { margin-right: 3px; } .sidebar .social-links li { display: inline; padding: 0 4px; line-height: 0; } .sidebar .social-links a { color: #565654; } .post { background-color: #FFF; margin: 30px; } .post .post-title h1 { text-transform: uppercase; font-size: 30px; letter-spacing: 5px; line-height: 1; } .post .post-title h2 { text-transform: uppercase; letter-spacing: 1px; font-size: 28px; line-height: 1; font-weight: 600; color: #5f5f5f; } .post .post-title h3 { text-transform: uppercase; letter-spacing: 1px; line-height: 1; font-weight: 600; color: #464646; font-size: 22px; margin: 0; } .post .post-title a { text-decoration: none; letter-spacing: 1px; color: #5f5f5f; font-family: Open Sans; } .post .post-content a { text-decoration: none; letter-spacing: 1px; color: #4786D6; } .post .post-content h3 { color: #5F5F5F; font-size: 22px; font-weight: 600; font-family: Open Sans; } .post .post-content h4 { color: #5F5F5F; font-size: 16px; font-family: Open Sans; } .post .post-content img { max-width: 100%; min-width: 700px; } .post .post-footer { padding: 0 0 30px 0; border-bottom: 1px solid #FFD8C9; } .post .post-footer .meta { max-width: 100%; height: 25px; color: #bbbbbb; } .post .post-footer .meta .info { float: left; font-size: 12px; } .post .post-footer .meta .info .date { margin-right: 5px; } .post .post-footer .meta a { text-decoration: none; color: #bbbbbb; } .post .post-footer .meta i { margin-right: 3px; } .post .post-footer .tags { padding-bottom: 15px; font-size: 13px; } .post .post-footer .tags ul { list-style-type: none; display: inline; margin: 0; padding: 0; } .post .post-footer .tags ul li { list-style-type: none; margin: 0; padding-right: 5px; display: inline; } .post .post-footer .tags a { text-decoration: none; color: rgba(0, 0, 0, 0.44); font-weight: 400; } .post .post-footer .tags a:hover { text-decoration: none; } .pagination { margin: 30px; padding: 0px 0 56px 0; border-bottom: 1px solid #f2f2f2; } .pagination ul { list-style: none; margin: 0; padding: 0; height: 13px; } .pagination ul li { margin: 0 2px 0 2px; display: inline; line-height: 1; } .pagination ul li a { text-decoration: none; } .pagination .pre { float: left; } .pagination .next { float: right; } .like-reblog-buttons { float: right; } .like-button { float: right; padding: 0 0 0 10px; } .reblog-button { float: right; padding: 0; } #install-btn { position: fixed; bottom: 0px; right: 6px; } #disqus_thread { margin: 30px; border-bottom: 1px solid #f2f2f2; } .footer { clear: both; text-align: center; font-size: 10px; margin: 0 auto; bottom: 0; position: absolute; width: 100%; } .footer a { color: #A6A6A6; } .footer span { color: #888; } .archive { width: 100%; } .list-with-title { font-size: 14px; margin: 30px; padding: 0; } .list-with-title li { list-style-type: none; padding: 0; } .list-with-title .listing-title { font-size: 24px; color: #666666; font-weight: 600; line-height: 2.2em; } .list-with-title .listing { padding: 0; } .list-with-title .listing .listing-post { padding-bottom: 5px; } .list-with-title .listing .listing-post .post-time { float: right; color: #C5C5C5; } .list-with-title .listing .listing-post a { color: #8F8F8F; } .list-with-title .listing .listing-post a:hover { color: #464545; } .share { padding-left: 30px; display: flex; width: 100%; height: 60px; display: -webkit-box; } .evernote { width: 32px; height: 32px; border-radius: 300px; background-color: #3E3E3E; margin-right: 5px; } .evernote a { color: #fff; padding: 11px; font-size: 12px; } .evernote a:hover { color: #ED6243; padding: 11px; } .weibo { width: 32px; height: 32px; border-radius: 300px; background-color: #ED6243; margin-right: 5px; } .weibo a { color: #fff; padding: 9px; } .weibo a:hover { color: #BD4226; } .twitter { width: 32px; height: 32px; border-radius: 300px; background-color: #59C0FD; margin-right: 5px; } .twitter a { color: #fff; padding: 9px; } .twitter a:hover { color: #4B9ECE; } .about { margin: 30px; } .about h3 { font-size: 22px; } .comment-count { color: #666; } .tab-community { color: #666; } .read_more { font-size: 14px; } .back-button { padding-top: 30px; max-width: 100px; padding-left: 40px; float: left; } a.btn { color: #868686; font-weight: 400; } .btn { display: inline-block; position: relative; outline: 0; color: rgba(0, 0, 0, 0.44); background: transparent; font-size: 14px; text-align: center; text-decoration: none; cursor: pointer; border: 1px solid rgba(0, 0, 0, 0.15); white-space: nowrap; font-weight: 400; font-style: normal; border-radius: 999em; } .btn:hover { display: inline-block; position: relative; outline: 0px; color: #464545; background: transparent; font-size: 14px; text-align: center; text-decoration: none; cursor: pointer; border: 1px solid #464545; white-space: nowrap; font-weight: 400; font-style: normal; border-radius: 999em; } [role="back"] { padding: 0.5em 1.25em; line-height: 1.666em; } [role="home"] { padding: 0.5em 1.25em; line-height: 1.666em; } [role="navigation"] { padding: 0.5em 1.25em; line-height: 1.666em; } [role="tags"] { padding: 6px 12px; } .menu { float: right; padding-top: 30px; } .menu .btn-down { margin: 0px; } .menu .btn-down li { list-style: none; width: 100px; } .menu .btn-down li a { display: inline-block; position: relative; padding: 0.5em 1.25em; outline: 0; color: rgba(0, 0, 0, 0.44); background: transparent; font-size: 14px; text-align: center; text-decoration: none; cursor: pointer; border: 1px solid rgba(0, 0, 0, 0.15); white-space: nowrap; font-weight: 400; font-style: normal; border-radius: 999em; margin-top: 5px; } .menu .btn-down li a:hover { position: relative; padding: 0.5em 1.25em; outline: 0; color: #fff; background: #3CBD10; font-size: 14px; text-align: center; text-decoration: none; cursor: pointer; border: 1px solid rgba(0, 0, 0, 0.15); white-space: nowrap; font-weight: 400; font-style: normal; border-radius: 999em; margin-top: 5px; } .menu .btn-down div { position: absolute; visibility: hidden; width: 100px; float: right; } @media screen and (max-width: 414px) { .sidebar { width: 100%; position: absolute; border-right: none; } .sidebar .logo-title { padding-top: 100px; } .sidebar .logo-title .title img { width: 100px; } .sidebar .logo-title .title h3 { font-size: 20px; } .page-top { display: none; } .content { margin-top: 360px; width: 100%; overflow: hidden; } .footer { display: none; } .share { display: flex; display: -webkit-box; } } .profile h2 { margin: 20px 0; font-family: 'Montserrat', sans-serif; font-size: 16px; text-transform: uppercase; letter-spacing: 5px; color: #109289; } .cate-list { margin: 20px auto; text-transform: uppercase; text-align: center; color: #109289; width: 80%; font-size: 0.85em; font-family: 'Lato', "Open Sans", "PingFang SC", "Microsoft YaHei", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .cate-list .cate-title { width: 48%; } .cate-list .cate-title a { display: inline-block; margin: 5px 0; text-decoration: none; color: dimgrey; } .meta .info a:hover { color: #FF7838; } .post-nav { margin: 30px 0; } .comment_container { margin: 30px; } ================================================ FILE: theme/blog/layout/archive.tpl ================================================ {{ define "content" }} {{ range .archive }}
{{ .YearStr }}
{{ end }} {{ end }} ================================================ FILE: theme/blog/layout/category.tpl ================================================ {{ define "content" }}
{{ range .categoryList }} {{ end }}
{{ end }} ================================================ FILE: theme/blog/layout/home.tpl ================================================ {{ define "content" }} {{ range .postList }}

{{ .Title }}

{{ .Summary | unescaped }}
{{ .CreatedAt | format }}    {{ range .Category }} {{ . }}  {{ end }}    {{ range .Tags }} {{ . }}  {{ end }}
{{ end }} {{ end }} ================================================ FILE: theme/blog/layout/main.tpl ================================================ {{.title}}
{{ template "content" . }}
================================================ FILE: theme/blog/layout/page.tpl ================================================ {{ define "content" }}
{{ .pageTitle }}({{ .count }})
{{ end }} ================================================ FILE: theme/blog/layout/post.tpl ================================================ {{ define "content" }}

{{ .post.Title }}

{{ .post.Content | unescaped }}
{{ end }} ================================================ FILE: theme/blog/layout/tag.tpl ================================================ {{ define "content" }}
{{ range .tagList }} {{ end }}
{{ end }}