new: Complete modules related to home page, archive page and article page

This commit is contained in:
2024-12-16 14:13:58 +08:00
parent 04267841f0
commit 65e5e9eafc
49 changed files with 1547 additions and 1 deletions

5
.gitignore vendored
View File

@@ -1,6 +1,9 @@
# 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
#
# Work dir and files
.idea
# Binaries for programs and plugins
*.exe
*.exe~

30
_post/dst/test.md.html Normal file
View File

@@ -0,0 +1,30 @@
<!--more-->
<h1 id="Heading-level-1">Heading level 1</h1>
<h2 id="Heading-level-2">Heading level 2</h2>
<h3 id="Heading-level-3">Heading level 3</h3>
<h4 id="Heading-level-4">Heading level 4</h4>
<h5 id="Heading-level-5">Heading level 5</h5>
<h6 id="Heading-level-6">Heading level 6</h6>
<p>I just love <strong>bold text</strong>.</p>
<blockquote>
<p>Dorothy followed her through many of the beautiful rooms in her castle.</p>
</blockquote>
<ol>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
<li>Fourth item</li>
</ol>
<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
<li>Fourth item</li>
</ul>
<pre><code class="language-go highlight-chroma"><span class="highlight-line"><span class="highlight-cl"><span class="highlight-kd">func</span> <span class="highlight-nf">main</span><span class="highlight-p">()</span> <span class="highlight-p">{</span>
</span></span><span class="highlight-line"><span class="highlight-cl"> <span class="highlight-nx">fmt</span><span class="highlight-p">.</span><span class="highlight-nf">Println</span><span class="highlight-p">(</span><span class="highlight-s">&#34;Hello, World&#34;</span><span class="highlight-p">)</span>
</span></span><span class="highlight-line"><span class="highlight-cl"><span class="highlight-p">}</span>
</span></span></code></pre>
<hr />
<p>This is a link: <a href="https://markdown.com.cn">Markdown link</a></p>
<p><img src="https://markdown.com.cn/assets/img/philly-magic-garden.9c0b4415.jpg" alt="Image" title="Magic Gardens" /></p>

50
_post/src/test.md Normal file
View File

@@ -0,0 +1,50 @@
---
title: Example Markdown
date: 2024-12-16 12:33:04
tags: [Example, Markdown]
categories:
- Example
cover: https://markdown.com.cn/assets/img/philly-magic-garden.9c0b4415.jpg
---
<!--more-->
# Heading level 1
## Heading level 2
### Heading level 3
#### Heading level 4
##### Heading level 5
###### Heading level 6
I just love **bold text**.
> Dorothy followed her through many of the beautiful rooms in her castle.
1. First item
2. Second item
3. Third item
4. Fourth item
- First item
- Second item
- Third item
- Fourth item
```go
func main() {
fmt.Println("Hello, World")
}
```
***
This is a link: [Markdown link](https://markdown.com.cn)。
![Image](https://markdown.com.cn/assets/img/philly-magic-garden.9c0b4415.jpg "Magic Gardens")

37
config.yaml Normal file
View File

@@ -0,0 +1,37 @@
# app config #
host: 0.0.0.0
port: 8080
template: templates/default
# site config #
site:
# basic info
info:
logo: /assets/img/logo.png
title: Molly Blog
author: yvling
language: en
copyright: Copyright © 2024 Powered by <a href="https://github.com/yv1ing/MollyBlog">MollyBlog</a>
# menu config
menu:
items:
- name: Home
icon: fa-solid fa-house
url: /
- name: Archive
icon: fa-solid fa-box-archive
url: /archive
- name: About
icon: fa-solid fa-circle-info
url: /about
# post config
post:
toc_title: Content
recent_post:
title: Recent Posts
number: 10
archive:
title: Archive
number: 10

37
config/mConfig.go Normal file
View File

@@ -0,0 +1,37 @@
package config
import (
"io"
"log"
"os"
"gopkg.in/yaml.v3"
)
type MConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Template string `yaml:"template"`
MSite mSite `yaml:"site"`
}
var MConfigInstance *MConfig
func init() {
configFile, err := os.OpenFile("config.yaml", os.O_RDONLY, os.ModePerm)
if err != nil {
log.Fatal(err)
}
defer configFile.Close()
configByte, err := io.ReadAll(configFile)
if err != nil {
log.Fatal(err)
}
err = yaml.Unmarshal(configByte, &MConfigInstance)
if err != nil {
log.Fatal(err)
}
}

9
config/mInfo.go Normal file
View File

@@ -0,0 +1,9 @@
package config
type mInfo struct {
Logo string `yaml:"logo"`
Title string `yaml:"title"`
Author string `yaml:"author"`
Language string `yaml:"language"`
Copyright string `yaml:"copyright"`
}

11
config/mMenu.go Normal file
View File

@@ -0,0 +1,11 @@
package config
type mMenu struct {
Items []mItem `yaml:"items"`
}
type mItem struct {
Name string `yaml:"name"`
Icon string `yaml:"icon"`
Url string `yaml:"url"`
}

17
config/mPost.go Normal file
View File

@@ -0,0 +1,17 @@
package config
type mPost struct {
TocTitle string `yaml:"toc_title"`
RecentPost mRecentPost `yaml:"recent_post"`
Archive mArchive `yaml:"archive"`
}
type mRecentPost struct {
Title string `yaml:"title"`
Number int `yaml:"number"`
}
type mArchive struct {
Title string `yaml:"title"`
Number int `yaml:"number"`
}

7
config/mSite.go Normal file
View File

@@ -0,0 +1,7 @@
package config
type mSite struct {
Info mInfo `yaml:"info"`
Menu mMenu `yaml:"menu"`
Post mPost `yaml:"post"`
}

40
go.mod Normal file
View File

@@ -0,0 +1,40 @@
module MollyBlog
go 1.23
require (
github.com/88250/lute v1.7.6
github.com/gin-gonic/gin v1.10.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/alecthomas/chroma v0.10.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dlclark/regexp2 v1.8.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
)

98
go.sum Normal file
View File

@@ -0,0 +1,98 @@
github.com/88250/lute v1.7.6 h1:8F4i53POAR+oEkyClsdHYPU1A7Z8rdxfRfjarD+FL9w=
github.com/88250/lute v1.7.6/go.mod h1:+wUqx/1kdFDbWtxn9LYJlaCOAeol2pjSO6w+WJTVQsg=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
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/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

62
internal/mApp/mApp.go Normal file
View File

@@ -0,0 +1,62 @@
package mApp
import (
"fmt"
"html/template"
"log"
"MollyBlog/config"
"MollyBlog/internal/model"
"github.com/88250/lute"
"github.com/gin-gonic/gin"
)
type MApp struct {
Host string
Port int
Config *config.MConfig
lute *lute.Lute
engine *gin.Engine
Posts []*model.MPost
TaggedPosts []*model.MPost
CategorizedPosts []*model.MPost
SrcFiles []model.MFileInfo
}
const (
SRC = "_post/src" // source markdown files
DST = "_post/dst" // destination html files
)
func (ma *MApp) Run() {
ma.loadRoutes()
ma.loadTemplates()
addr := fmt.Sprintf("%s:%d", ma.Host, ma.Port)
err := ma.engine.Run(addr)
if err != nil {
log.Fatal(err)
}
}
func NewMApp(cfg *config.MConfig) *MApp {
engine := gin.Default()
engine.SetFuncMap(template.FuncMap{
"add": func(a, b int) int {
return a + b
},
})
return &MApp{
Host: cfg.Host,
Port: cfg.Port,
Config: cfg,
lute: lute.New(),
engine: engine,
}
}

187
internal/mApp/mHandler.go Normal file
View File

@@ -0,0 +1,187 @@
package mApp
import (
"html/template"
"io"
"net/http"
"os"
"strconv"
"strings"
"MollyBlog/internal/model"
"MollyBlog/utils"
"github.com/gin-gonic/gin"
)
func (ma *MApp) IndexHandler(ctx *gin.Context) {
// generate recent posts
var recentPosts []model.MPost
for i := 0; i < utils.Min(len(ma.Posts), ma.Config.MSite.Post.RecentPost.Number); i++ {
tmpPost := *ma.Posts[i]
tmpPost.Date = strings.Split(tmpPost.Date, " ")[0]
recentPosts = append(recentPosts, tmpPost)
}
resData := gin.H{
"site_info": gin.H{
"logo": ma.Config.MSite.Info.Logo,
"title": ma.Config.MSite.Info.Title,
"author": ma.Config.MSite.Info.Author,
"language": ma.Config.MSite.Info.Language,
"copyright": template.HTML(ma.Config.MSite.Info.Copyright),
},
"menu": ma.Config.MSite.Menu,
"recent_post": gin.H{
"title": ma.Config.MSite.Post.RecentPost.Title,
"posts": recentPosts,
},
}
ctx.HTML(http.StatusOK, "index.html", resData)
}
func (ma *MApp) PostHandler(ctx *gin.Context) {
postHash := ctx.Param("hash")
var success bool
var html string
var realPost model.MPost
for _, post := range ma.Posts {
if post.HtmlHash == postHash {
file, err := os.OpenFile(post.HtmlPath, os.O_RDONLY, 0644)
if err != nil {
success = false
break
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
success = false
break
}
html = string(data)
realPost = *post
success = true
break
}
}
var resPost gin.H
if success {
resPost = gin.H{
"title": realPost.Title,
"cover": realPost.Cover,
"date": realPost.Date,
"tags": realPost.TagHashes,
"categories": realPost.CategoryHashes,
"content": template.HTML(html),
}
}
resData := gin.H{
"site_info": gin.H{
"logo": ma.Config.MSite.Info.Logo,
"title": ma.Config.MSite.Info.Title,
"author": ma.Config.MSite.Info.Author,
"language": ma.Config.MSite.Info.Language,
"copyright": template.HTML(ma.Config.MSite.Info.Copyright),
},
"menu": ma.Config.MSite.Menu,
"post": resPost,
"status": gin.H{
"toc_title": ma.Config.MSite.Post.TocTitle,
"success": success,
},
}
ctx.HTML(http.StatusOK, "post.html", resData)
}
func (ma *MApp) ArchiveHandler(ctx *gin.Context) {
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
size := ma.Config.MSite.Post.Archive.Number
var prePage, curPage, nxtPage, allPage int
allPage = (len(ma.Posts) + size - 1) / size
if allPage > 0 {
if page <= 0 {
curPage = 1
} else if page > allPage {
curPage = allPage
} else {
curPage = page
}
} else {
curPage = 0
}
prePage = curPage - 1
nxtPage = curPage + 1
if prePage <= 0 {
prePage = curPage
}
if nxtPage > allPage {
nxtPage = allPage
}
// generate recent posts
start := (curPage - 1) * size
offset := curPage * size
var historyPosts []model.MPost
if start >= 0 {
for i := start; i < utils.Min(len(ma.Posts), offset); i++ {
tmpPost := *ma.Posts[i]
tmpPost.Date = strings.Split(tmpPost.Date, " ")[0]
historyPosts = append(historyPosts, tmpPost)
}
}
resData := gin.H{
"site_info": gin.H{
"logo": ma.Config.MSite.Info.Logo,
"title": ma.Config.MSite.Info.Title,
"author": ma.Config.MSite.Info.Author,
"language": ma.Config.MSite.Info.Language,
"copyright": template.HTML(ma.Config.MSite.Info.Copyright),
},
"menu": ma.Config.MSite.Menu,
"page_info": gin.H{
"pre_page": prePage,
"cur_page": curPage,
"nxt_page": nxtPage,
"all_page": allPage,
},
"history_post": gin.H{
"title": ma.Config.MSite.Post.Archive.Title,
"posts": historyPosts,
},
}
ctx.HTML(http.StatusOK, "archive.html", resData)
}
func (ma *MApp) UpdateBlogHandler(ctx *gin.Context) {
var err error
err = ma.loadMarkdownFiles()
if err != nil {
_ = ctx.Error(err)
return
}
err = ma.parseMarkdowns()
if err != nil {
_ = ctx.Error(err)
return
}
ctx.JSON(http.StatusOK, gin.H{"msg": "ok"})
}

127
internal/mApp/mMarkdown.go Normal file
View File

@@ -0,0 +1,127 @@
package mApp
import (
"fmt"
"io"
"os"
"path/filepath"
"MollyBlog/internal/model"
"MollyBlog/utils"
"gopkg.in/yaml.v3"
)
// loadMarkdownFiles load markdown source files
func (ma *MApp) loadMarkdownFiles() error {
var markdownList []model.MFileInfo
markdownPath := SRC
err := filepath.Walk(markdownPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
// filter markdown files
if filepath.Ext(path) == ".md" {
markdownList = append(markdownList, model.MFileInfo{
Name: info.Name(),
Path: path,
})
}
return nil
})
if err != nil {
return err
}
ma.SrcFiles = markdownList
return nil
}
// parseMarkdowns parse markdown files to html
func (ma *MApp) parseMarkdowns() error {
htmlPath := DST
for _, file := range ma.SrcFiles {
// read markdown file
_mdFile, err := os.Open(file.Path)
if err != nil {
return err
}
defer _mdFile.Close()
_mdByte, err := io.ReadAll(_mdFile)
if err != nil {
return err
}
// extract FrontMatter and clean markdown bytes
_frontMatter, _cleanBytes := utils.ExtractFrontMatter(_mdByte)
// parse frontmatter to post metadata
var post model.MPost
err = yaml.Unmarshal(_frontMatter, &post)
if err != nil {
return err
}
// convert into html format and save to html file
_htmlPath := fmt.Sprintf("%s/%s.html", htmlPath, file.Name)
_htmlFile, err := os.Create(_htmlPath)
if err != nil {
return err
}
defer _htmlFile.Close()
// set options to lute
ma.lute.SetToC(true)
_htmlByte := ma.lute.Markdown(file.Name, _cleanBytes)
_, err = _htmlFile.Write(_htmlByte)
if err != nil {
return err
}
// save html path to post's metadata
post.HtmlHash = utils.Sha256Hash(_htmlByte)
post.HtmlPath = _htmlPath
// save tags and categories to map
for _, tag := range post.Tags {
tagHash := utils.Sha256Hash([]byte(tag))
post.TagHashes = append(post.TagHashes, model.MTag{
Name: tag,
Hash: tagHash,
})
ma.TaggedPosts = append(ma.TaggedPosts, &post)
}
for _, category := range post.Categories {
categoryHash := utils.Sha256Hash([]byte(category))
post.CategoryHashes = append(post.CategoryHashes, model.MCategory{
Name: category,
Hash: categoryHash,
})
ma.CategorizedPosts = append(ma.CategorizedPosts, &post)
}
// free the raw tag and category slice
post.Tags = nil
post.Categories = nil
ma.Posts = append(ma.Posts, &post)
}
// sort Posts by date
model.SortPostsByDate(ma.Posts)
return nil
}

9
internal/mApp/mRouter.go Normal file
View File

@@ -0,0 +1,9 @@
package mApp
func (ma *MApp) loadRoutes() {
ma.engine.GET("/", ma.IndexHandler)
ma.engine.GET("/archive", ma.ArchiveHandler)
ma.engine.GET("/post/:hash", ma.PostHandler)
ma.engine.PUT("/update", ma.UpdateBlogHandler)
}

View File

@@ -0,0 +1,11 @@
package mApp
import "fmt"
func (ma *MApp) loadTemplates() {
assetsPath := fmt.Sprintf("%s/assets", ma.Config.Template)
htmlPath := fmt.Sprintf("%s/html/*.html", ma.Config.Template)
ma.engine.Static("/assets", assetsPath)
ma.engine.LoadHTMLGlob(htmlPath)
}

View File

@@ -0,0 +1,6 @@
package model
type MCategory struct {
Name string `yaml:"name" json:"name"`
Hash string `yaml:"hash" json:"hash"`
}

View File

@@ -0,0 +1,7 @@
package model
// MFileInfo basic file info
type MFileInfo struct {
Name string
Path string
}

49
internal/model/mPost.go Normal file
View File

@@ -0,0 +1,49 @@
package model
import (
"sort"
"time"
)
// MPost post metadata
type MPost struct {
Title string `yaml:"title" json:"title"`
Cover string `yaml:"cover" json:"cover"`
Date string `yaml:"date" json:"date"`
Tags []string `yaml:"tags" json:"tags"`
TagHashes []MTag `yaml:"tag_hashes" json:"tag_hashes"`
Categories []string `yaml:"categories" json:"categories"`
CategoryHashes []MCategory `yaml:"category_hashes" json:"category_hashes"`
HtmlHash string `yaml:"htmlHash" json:"html_hash"`
HtmlPath string `yaml:"htmlPath" json:"html_path"`
}
type MPostSlice []*MPost
func (a MPostSlice) Len() int {
return len(a)
}
func (a MPostSlice) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a MPostSlice) Less(i, j int) bool {
timeFormat := "2006-01-02 15:04:05"
t1, err1 := time.Parse(timeFormat, a[i].Date)
t2, err2 := time.Parse(timeFormat, a[j].Date)
if err1 != nil || err2 != nil {
return false
}
return t1.After(t2)
}
func SortPostsByDate(Posts []*MPost) []*MPost {
sort.Sort(MPostSlice(Posts))
return Posts
}

6
internal/model/mTag.go Normal file
View File

@@ -0,0 +1,6 @@
package model
type MTag struct {
Name string `yaml:"name" json:"name"`
Hash string `yaml:"hash" json:"hash"`
}

32
main.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"log"
"os"
"MollyBlog/config"
"MollyBlog/internal/mApp"
)
func init() {
_, err := os.Stat(mApp.SRC)
if os.IsNotExist(err) {
err = os.MkdirAll(mApp.SRC, os.ModePerm)
if err != nil {
log.Fatal(err)
}
}
_, err = os.Stat(mApp.DST)
if os.IsNotExist(err) {
err = os.MkdirAll(mApp.DST, os.ModePerm)
if err != nil {
log.Fatal(err)
}
}
}
func main() {
mapp := mApp.NewMApp(config.MConfigInstance)
mapp.Run()
}

View File

@@ -0,0 +1,25 @@
.history-post-title {
color: var(--primary-color);
}
.history-post-item {
padding-top: 5px;
padding-bottom: 5px;
color: var(--primary-text-color);
list-style: none;
}
.history-post-item-wrap {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.history-post-item-date {
color: var(--secondary-text-color);
}
.history-post-pagination-item {
color: var(--secondary-text-color);
padding-right: 10px;
}

View File

@@ -0,0 +1,97 @@
:root{
/* colors */
--primary-color: #8589e0;
--secondary-color: #b2cbd3;
--primary-text-color: #c9cacc;
--secondary-text-color: #9c9c9c;
--background-color: #191c1d;
}
@font-face {
font-family: 'jetbrains-mono-medium';
src: url('../../assets/css/webfonts/jetbrains-mono-medium.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
/*font-family: jetbrains-mono-medium, serif;*/
background-color: var(--background-color);
}
a, a:link, a:visited, a:active {
text-decoration: none;
color: var(--primary-text-color);
}
a:hover {
text-decoration: underline;
text-decoration-color: var(--primary-color);
text-decoration-thickness: 2px;
}
.m-icon {
color: var(--primary-text-color);
}
.root-container {
width: 100%;
height: 100%;
}
@media screen and (max-width: 576px){
.root-container {
padding-left: 1rem;
padding-right: 1rem;
}
}
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: var(--secondary-text-color);
}
.footer {
position: fixed;
width: 100%;
bottom: 20px;
text-align: center;
color: var(--secondary-text-color);
}
.main-logo {
display: flex;
}
.main-logo-img {
width: 40px;
height: 40px;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
}
.main-logo-txt {
width: fit-content;
height: 40px;
line-height: 40px;
font-size: 36px;
padding-top: 2px;
margin-left: 10px;
color: var(--primary-color);
}
.main-menu-link {
margin-right: 10px;
}

View File

@@ -0,0 +1,20 @@
.recent-post-title {
color: var(--primary-color);
}
.recent-post-item {
padding-top: 5px;
padding-bottom: 5px;
color: var(--primary-text-color);
list-style: none;
}
.recent-post-item-wrap {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.recent-post-item-date {
color: var(--secondary-text-color);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,48 @@
/* Background */ .highlight-bg { color: #ffffff; background-color: #1f1f24 }
/* PreWrapper */ .highlight-chroma { color: #ffffff; background-color: #1f1f24; }
/* Error */ .highlight-chroma .highlight-err { color: #960050 }
/* LineTableTD */ .highlight-chroma .highlight-lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
/* LineTable */ .highlight-chroma .highlight-lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
/* LineHighlight */ .highlight-chroma .highlight-hl { background-color: #353539 }
/* LineNumbersTable */ .highlight-chroma .highlight-lnt { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* LineNumbers */ .highlight-chroma .highlight-ln { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* Line */ .highlight-chroma .highlight-line { display: flex; }
/* Keyword */ .highlight-chroma .highlight-k { color: #fc5fa3 }
/* KeywordConstant */ .highlight-chroma .highlight-kc { color: #fc5fa3 }
/* KeywordDeclaration */ .highlight-chroma .highlight-kd { color: #fc5fa3 }
/* KeywordNamespace */ .highlight-chroma .highlight-kn { color: #fc5fa3 }
/* KeywordPseudo */ .highlight-chroma .highlight-kp { color: #fc5fa3 }
/* KeywordReserved */ .highlight-chroma .highlight-kr { color: #fc5fa3 }
/* KeywordType */ .highlight-chroma .highlight-kt { color: #fc5fa3 }
/* NameBuiltin */ .highlight-chroma .highlight-nb { color: #d0a8ff }
/* NameBuiltinPseudo */ .highlight-chroma .highlight-bp { color: #a167e6 }
/* NameClass */ .highlight-chroma .highlight-nc { color: #5dd8ff }
/* NameFunction */ .highlight-chroma .highlight-nf { color: #41a1c0 }
/* NameVariable */ .highlight-chroma .highlight-nv { color: #41a1c0 }
/* LiteralString */ .highlight-chroma .highlight-s { color: #fc6a5d }
/* LiteralStringAffix */ .highlight-chroma .highlight-sa { color: #fc6a5d }
/* LiteralStringBacktick */ .highlight-chroma .highlight-sb { color: #fc6a5d }
/* LiteralStringChar */ .highlight-chroma .highlight-sc { color: #fc6a5d }
/* LiteralStringDelimiter */ .highlight-chroma .highlight-dl { color: #fc6a5d }
/* LiteralStringDoc */ .highlight-chroma .highlight-sd { color: #fc6a5d }
/* LiteralStringDouble */ .highlight-chroma .highlight-s2 { color: #fc6a5d }
/* LiteralStringEscape */ .highlight-chroma .highlight-se { color: #fc6a5d }
/* LiteralStringHeredoc */ .highlight-chroma .highlight-sh { color: #fc6a5d }
/* LiteralStringOther */ .highlight-chroma .highlight-sx { color: #fc6a5d }
/* LiteralStringRegex */ .highlight-chroma .highlight-sr { color: #fc6a5d }
/* LiteralStringSingle */ .highlight-chroma .highlight-s1 { color: #fc6a5d }
/* LiteralStringSymbol */ .highlight-chroma .highlight-ss { color: #fc6a5d }
/* LiteralNumber */ .highlight-chroma .highlight-m { color: #d0bf69 }
/* LiteralNumberBin */ .highlight-chroma .highlight-mb { color: #d0bf69 }
/* LiteralNumberFloat */ .highlight-chroma .highlight-mf { color: #d0bf69 }
/* LiteralNumberHex */ .highlight-chroma .highlight-mh { color: #d0bf69 }
/* LiteralNumberInteger */ .highlight-chroma .highlight-mi { color: #d0bf69 }
/* LiteralNumberIntegerLong */ .highlight-chroma .highlight-il { color: #d0bf69 }
/* LiteralNumberOct */ .highlight-chroma .highlight-mo { color: #d0bf69 }
/* Comment */ .highlight-chroma .highlight-c { color: #6c7986 }
/* CommentHashbang */ .highlight-chroma .highlight-ch { color: #6c7986 }
/* CommentMultiline */ .highlight-chroma .highlight-cm { color: #6c7986 }
/* CommentSingle */ .highlight-chroma .highlight-c1 { color: #6c7986 }
/* CommentSpecial */ .highlight-chroma .highlight-cs { color: #6c7986; font-style: italic }
/* CommentPreproc */ .highlight-chroma .highlight-cp { color: #fd8f3f }
/* CommentPreprocFile */ .highlight-chroma .highlight-cpf { color: #fd8f3f }

View File

@@ -0,0 +1,94 @@
pre {
padding: 10px;
border-radius: 4px;
border: var(--secondary-text-color) 1px dashed;
}
.post-menu {
max-width: 1000px;
}
.post-title {
color: var(--primary-color);
}
.post-info {
max-width: 1000px;
color: var(--secondary-text-color);
}
.post-info-wrap {
display: inline-block;
margin-top: 10px;
}
.post-info-item {
display: inline-block;
margin-right: 10px;
}
.post-content {
line-height: 1.6;
max-width: 1000px;
color: var(--primary-text-color);
}
.post-content h1,
.post-content h2 {
margin-top: 4rem;
margin-bottom: 1rem;
}
.post-content h3,
.post-content h4 {
margin-top: 3rem;
margin-bottom: 2rem;
}
.post-content h5,
.post-content h6 {
margin-top: 2rem;
margin-bottom: 2rem;
}
.post-content img {
margin-top: 1rem;
margin-bottom: 2rem;
}
.post-content img {
max-width: 100%;
}
.post-footer {
width: 100%;
padding-top: 2rem;
padding-bottom: 2rem;
text-align: center;
color: var(--secondary-text-color);
}
#toc-box-area {
position: fixed;
top: 4rem;
left: 30px;
}
#toc-box-menu {
margin-top: 20px;
display: none;
color: var(--primary-color);
}
.toc-box-menu-item {
cursor: pointer;
margin-right: 10px;
}
#toc-title {
color: var(--primary-color);
}
#toc-box li {
list-style: none;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
const tocBox = document.getElementById('toc-box');
const tList = document.getElementById('post-content').querySelectorAll('h1, h2, h3, h4, h5, h6');
const hList = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
let tmpHtml = `<div>\n`;
Array.from(tList, v => {
// get the level number
const H = hList.indexOf(v.nodeName) + 1 || 1;
tmpHtml += `<div class="li li-${H} toc-li"><a id="${v.id}" href="#">${v.textContent}</a></div>\n`;
});
tmpHtml += `</div>`
tocBox.innerHTML = tmpHtml;
Array.from(tList, v => {
const btn = document.getElementById('toc-box').querySelector(`#${v.id}`);
const ele = document.getElementById('post-content').querySelector(`#${v.id}`);
if (!btn || !ele) {
return;
}
btn.addEventListener('click', (event) => {
event.preventDefault();
window.scrollTo({ top: ele.offsetTop - 60, behavior: 'smooth' });
});
});
const tocMenu = document.getElementById('toc-box-menu');
window.addEventListener('scroll', (event) => {
const scrollFromTop = window.scrollY || document.documentElement.scrollTop;
if (scrollFromTop >= 100) {
tocMenu.style.display = 'block';
} else {
tocMenu.style.display = 'none';
}
})
function goto(path) {
window.location.href = path;
}
function backToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
})
}

View File

@@ -0,0 +1,107 @@
{{ define "archive.html" }}
<html lang="{{ .site_info.language }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .site_info.title }}</title>
<link rel="icon" type="image/x-icon" href="{{ .site_info.logo }}"/>
<link rel="stylesheet" href="../assets/css/lib/fontawesome.all.min.css">
<link rel="stylesheet" href="../assets/css/lib/bootstrap.min.css">
<link rel="stylesheet" href="../assets/css/global.css">
<link rel="stylesheet" href="../assets/css/archive.css">
<script src="../assets/js/lib/jquery.min.js"></script>
<script src="../assets/js/lib/bootstrap.min.js"></script>
<script src="../assets/js/lib/fontawesome.all.min.js"></script>
</head>
<body>
<div class="root-container">
<!-- header -->
<div class="container p-3">
<div class="row pt-3 pt-md-4 pt-lg-5">
<div class="col main-logo">
<div class="main-logo-img" style="background-image: url('{{ .site_info.logo }}');"></div>
<div class="main-logo-txt">
{{ .site_info.title }}
</div>
</div>
</div>
</div>
<!-- menu -->
<div class="container p-3">
<div class="row pt-lg-3">
<div class="col mx-auto post-menu">
{{ range $i, $v := .menu.Items }}
<i class="{{ $v.Icon }} m-icon"></i>
<a class="main-menu-link" href="{{ $v.Url }}"> {{ $v.Name }} </a>
{{ end }}
</div>
</div>
</div>
<!-- body -->
<div class="container p-3">
<div class="row pt-lg-3">
<h4 class="history-post-title"># {{ .history_post.title }}</h4>
<div class="col-12 col-md-8">
<div>
{{ range $i, $v := .history_post.posts }}
<div class="row history-post-item">
<div class="col history-post-item-wrap">
<span class="d-none d-md-inline history-post-item-date">{{ $v.Date }}&nbsp; | &nbsp;</span>
<span>
<a href="/post/{{ $v.HtmlHash }}">{{ $v.Title }}</a>
</span>
</div>
<div class="col d-none d-lg-block history-post-item-wrap">
<span style="color: var(--secondary-text-color)">
[
{{ range $i2, $v2 := $v.TagHashes }}
<a href="/tag/{{ $v2.Hash }}" style="color: var(--secondary-text-color)">{{ $v2.Name }}</a>
{{ if lt (add $i2 1) (len $v.TagHashes) }}
,
{{ end }}
{{ end }}
]
</span>
</div>
</div>
{{ end }}
</div>
</div>
</div>
<div class="row pt-3 pt-lg-4">
<div class="col-12 col-md-8">
<div class="history-post-pagination">
<span class="history-post-pagination-item">
<a href="/archive?page={{ .page_info.pre_page }}">
<
</a>
</span>
<span class="history-post-pagination-item">
{{ .page_info.cur_page }} &nbsp; / &nbsp; {{ .page_info.all_page }}
</span>
<span class="history-post-pagination-item">
<a href="/archive?page={{ .page_info.nxt_page }}">
>
</a>
</span>
</div>
</div>
</div>
</div>
</div>
<!-- footer -->
<div class="footer">
{{ .site_info.copyright }}
</div>
</body>
</html>
{{ end }}

View File

@@ -0,0 +1,86 @@
{{ define "index.html" }}
<html lang="{{ .site_info.language }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .site_info.title }}</title>
<link rel="icon" type="image/x-icon" href="{{ .site_info.logo }}"/>
<link rel="stylesheet" href="../assets/css/lib/fontawesome.all.min.css">
<link rel="stylesheet" href="../assets/css/lib/bootstrap.min.css">
<link rel="stylesheet" href="../assets/css/global.css">
<link rel="stylesheet" href="../assets/css/index.css">
<script src="../assets/js/lib/jquery.min.js"></script>
<script src="../assets/js/lib/bootstrap.min.js"></script>
<script src="../assets/js/lib/fontawesome.all.min.js"></script>
</head>
<body>
<div class="root-container">
<!-- header -->
<div class="container p-3">
<div class="row pt-3 pt-md-4 pt-lg-5">
<div class="col main-logo">
<div class="main-logo-img" style="background-image: url('{{ .site_info.logo }}');"></div>
<div class="main-logo-txt">
{{ .site_info.title }}
</div>
</div>
</div>
</div>
<!-- menu -->
<div class="container p-3">
<div class="row pt-lg-3">
<div class="col mx-auto post-menu">
{{ range $i, $v := .menu.Items }}
<i class="{{ $v.Icon }} m-icon"></i>
<a class="main-menu-link" href="{{ $v.Url }}"> {{ $v.Name }} </a>
{{ end }}
</div>
</div>
</div>
<!-- body -->
<div class="container p-3">
<div class="row pt-lg-3">
<h4 class="recent-post-title"># {{ .recent_post.title }}</h4>
<div class="col-12 col-md-8">
<div>
{{ range $i, $v := .recent_post.posts }}
<div class="row recent-post-item">
<div class="col recent-post-item-wrap">
<span class="d-none d-md-inline recent-post-item-date">{{ $v.Date }}&nbsp; | &nbsp;</span>
<span>
<a href="/post/{{ $v.HtmlHash }}">{{ $v.Title }}</a>
</span>
</div>
<div class="col d-none d-lg-block recent-post-item-wrap">
<span style="color: var(--secondary-text-color)">
[
{{ range $i2, $v2 := $v.TagHashes }}
<a href="/tag/{{ $v2.Hash }}" style="color: var(--secondary-text-color)">{{ $v2.Name }}</a>
{{ if lt (add $i2 1) (len $v.TagHashes) }}
,
{{ end }}
{{ end }}
]
</span>
</div>
</div>
{{ end }}
</div>
</div>
</div>
</div>
</div>
<!-- footer -->
<div class="footer">
{{ .site_info.copyright }}
</div>
</body>
</html>
{{ end }}

View File

@@ -0,0 +1,122 @@
{{ define "post.html" }}
<html lang="{{ .site_info.language }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .site_info.title }}</title>
<link rel="icon" type="image/x-icon" href="{{ .site_info.logo }}"/>
<link rel="stylesheet" href="../assets/css/lib/fontawesome.all.min.css">
<link rel="stylesheet" href="../assets/css/lib/bootstrap.min.css">
<link rel="stylesheet" href="../assets/css/lib/xcode-dark.css">
<link rel="stylesheet" href="../assets/css/global.css">
<link rel="stylesheet" href="../assets/css/post.css">
<script src="../assets/js/lib/jquery.min.js"></script>
<script src="../assets/js/lib/bootstrap.min.js"></script>
<script src="../assets/js/lib/fontawesome.all.min.js"></script>
</head>
<body>
<div class="root-container">
<!-- menu -->
<div class="container p-3">
<div class="row pt-3 pt-md-4 pt-lg-5">
<div class="col-12 col-md-9 mx-auto post-menu">
{{ range $i, $v := .menu.Items }}
<i class="{{ $v.Icon }} m-icon"></i>
<a class="main-menu-link" href="{{ $v.Url }}"> {{ $v.Name }} </a>
{{ end }}
</div>
</div>
</div>
<!-- toc area -->
<div class="d-none d-lg-block" id="toc-box-area">
<div id="toc-title">
<h5>{{ .status.toc_title }}</h5>
<hr/>
</div>
<div id="toc-box"></div>
<div id="toc-box-menu">
<hr/>
{{ range $i, $v := .menu.Items }}
<span class="toc-box-menu-item">
<i class="{{ $v.Icon }} m-icon" onclick="goto('{{ $v.Url }}')"></i>
</span>
{{ end }}
<span class="toc-box-menu-item" onclick="backToTop()">
<i class="fa-solid fa-circle-up"></i>
</span>
</div>
</div>
<!-- body -->
{{ if .status.success }}
<div class="container p-3">
<div class="row pt-lg-3">
<div class="col-12 col-md-9 post-info mx-auto">
<h2 class="post-title">{{ .post.title }}</h2>
<div>
<span class="post-info-wrap">
<span class="post-info-item">
{{ .post.date }}
&nbsp;
</span>
</span>
<span class="post-info-wrap">
{{ $tags := .post.tags }}
{{ if gt (len $tags) 0 }}
<span class="post-info-item">
<i class="fa-solid fa-tags"></i>
{{ range $i, $v := $tags }}
<a href="/tag/{{ $v.Hash }}">{{ $v.Name }}</a>
{{ if lt (add $i 1) (len $tags) }},{{ end }}
{{ end }}
&nbsp;
</span>
{{ end }}
{{ $categories := .post.categories }}
{{ if gt (len $categories) 0 }}
<span class="post-info-item">
<i class="fa-solid fa-folder"></i>
{{ range $i, $v := $categories }}
<a href="/category/{{ $v.Hash }}">{{ $v.Name }}</a>
{{ if lt (add $i 1) (len $categories) }},{{ end }}
{{ end }}
</span>
{{ end }}
</span>
</div>
</div>
</div>
<div class="row pt-5">
<div class="col-12 col-md-9 post-content mx-auto" id="post-content">
{{ .post.content }}
</div>
</div>
</div>
{{ end }}
<!-- post footer -->
{{ if .status.success }}
<div class="post-footer">
{{ .site_info.copyright }}
</div>
{{ end }}
</div>
<!-- post footer -->
{{ if not .status.success }}
<div class="footer">
{{ .site_info.copyright }}
</div>
{{ end }}
<script src="../assets/js/post.js"></script>
</body>
</html>
{{ end }}

11
utils/hash.go Normal file
View File

@@ -0,0 +1,11 @@
package utils
import (
"crypto/sha256"
"encoding/hex"
)
func Sha256Hash(data []byte) string {
hash := sha256.Sum256(data)
return hex.EncodeToString(hash[:])
}

9
utils/markdown.go Normal file
View File

@@ -0,0 +1,9 @@
package utils
import "regexp"
// ExtractFrontMatter Extract markdown header information and return clean text
func ExtractFrontMatter(data []byte) ([]byte, []byte) {
re := regexp.MustCompile(`(?s)^\s*---\s*\n.*?\n\s*---\s*\n`)
return re.FindSubmatch(data)[0], re.ReplaceAll(data, nil)
}

9
utils/number.go Normal file
View File

@@ -0,0 +1,9 @@
package utils
func Min(a, b int) int {
if a <= b {
return a
} else {
return b
}
}