chore: 官网拆分 homepage + 关于页瘦身 + 端口 5175(当前可用状态基线)
This commit is contained in:
1 parent
5eb11826db
commit
be151cb68f
85 files changed
+4420
-1530
No files matched your search
@@ -0,0 +1,5 @@
|
||||
module opsxshell/tools/homepage-build
|
||||
|
||||
go 1.25
|
||||
|
||||
require github.com/yuin/goldmark v1.7.8
|
||||
@@ -0,0 +1,2 @@
|
||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
@@ -0,0 +1,172 @@
|
||||
// homepage-build:把 homepage/ 源(落地页+文档 md)构建为纯静态站点 homepage/dist/。
|
||||
// 产物可直接部署到任意静态托管(nginx/Pages)——官网与桌面二进制彻底解耦。
|
||||
// 用法: go run ./tools/homepage-build [-src homepage] [-out homepage/dist]
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
gmhtml "github.com/yuin/goldmark/renderer/html"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
type PageDef struct {
|
||||
File string `json:"file"`
|
||||
Slug string `json:"slug"`
|
||||
Title string `json:"title"`
|
||||
Updated string `json:"updated,omitempty"`
|
||||
}
|
||||
type GroupDef struct {
|
||||
Name string `json:"name"`
|
||||
Pages []PageDef `json:"pages"`
|
||||
}
|
||||
type Manifest struct {
|
||||
ProductVersion string `json:"productVersion"`
|
||||
Groups []GroupDef `json:"groups"`
|
||||
}
|
||||
type TocItem struct {
|
||||
ID string
|
||||
Level int
|
||||
Text string
|
||||
}
|
||||
type SearchEntry struct {
|
||||
Slug, Title, Group, Text string
|
||||
}
|
||||
type NavLink struct {
|
||||
Slug, Title string
|
||||
}
|
||||
type Page struct {
|
||||
Slug, Title, Group, Updated string
|
||||
HTML string
|
||||
TOC []TocItem
|
||||
Prev, Next *NavLink
|
||||
}
|
||||
|
||||
var (
|
||||
hRe = regexp.MustCompile(`(?s)<(h[23])>(.*?)</h[23]>`)
|
||||
tagRe = regexp.MustCompile(`<[^>]+>`)
|
||||
mdNoise = regexp.MustCompile("(?s)```.*?```|`[^`]*`|!\\[[^\\]]*\\]\\([^)]*\\)|\\[[^\\]]*\\]\\([^)]*\\)|[#>*_\\-|]+")
|
||||
)
|
||||
|
||||
func injectHeadingIDs(html string) (string, []TocItem) {
|
||||
var toc []TocItem
|
||||
counter := 0
|
||||
out := hRe.ReplaceAllStringFunc(html, func(m string) string {
|
||||
sub := hRe.FindStringSubmatch(m)
|
||||
tag, inner := sub[1], sub[2]
|
||||
counter++
|
||||
id := fmt.Sprintf("sec-%d", counter)
|
||||
text := strings.TrimSpace(tagRe.ReplaceAllString(inner, ""))
|
||||
level := 2
|
||||
if tag == "h3" {
|
||||
level = 3
|
||||
}
|
||||
toc = append(toc, TocItem{ID: id, Level: level, Text: text})
|
||||
return fmt.Sprintf(`<%s id="%s">%s</%s>`, tag, id, inner, tag)
|
||||
})
|
||||
return out, toc
|
||||
}
|
||||
|
||||
func plainText(src string) string {
|
||||
s := mdNoise.ReplaceAllString(src, " ")
|
||||
s = strings.Join(strings.Fields(s), " ")
|
||||
if len(s) > 6000 {
|
||||
s = s[:6000]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func must(err error, what string) {
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[homepage-build] %s: %v\n", what, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func writeFile(path, content string) {
|
||||
must(os.MkdirAll(filepath.Dir(path), 0o755), "mkdir "+path)
|
||||
must(os.WriteFile(path, []byte(content), 0o644), "write "+path)
|
||||
fmt.Println("[OK]", path)
|
||||
}
|
||||
|
||||
func main() {
|
||||
src := flag.String("src", "homepage", "源目录")
|
||||
out := flag.String("out", "homepage/dist", "产物目录")
|
||||
flag.Parse()
|
||||
|
||||
// 1) 落地页: 纯 HTML 直接拷 + site assets
|
||||
raw, err := os.ReadFile(filepath.Join(*src, "index.html"))
|
||||
must(err, "读 index.html")
|
||||
writeFile(filepath.Join(*out, "index.html"), string(raw))
|
||||
if raw2, err2 := os.ReadFile(filepath.Join(*src, "terms.html")); err2 == nil {
|
||||
writeFile(filepath.Join(*out, "terms.html"), string(raw2))
|
||||
}
|
||||
siteAssets := filepath.Join(*src, "assets")
|
||||
if st, _ := os.Stat(siteAssets); st != nil {
|
||||
must(os.CopyFS(filepath.Join(*out, "site", "assets"), os.DirFS(siteAssets)), "拷 site assets")
|
||||
fmt.Println("[OK]", filepath.Join(*out, "site", "assets"))
|
||||
}
|
||||
|
||||
// 2) 文档: manifest → goldmark 渲染 → docs.html 模板
|
||||
mraw, err := os.ReadFile(filepath.Join(*src, "content", "manifest.json"))
|
||||
must(err, "读 manifest")
|
||||
var manifest Manifest
|
||||
must(json.Unmarshal(mraw, &manifest), "解析 manifest")
|
||||
tplRaw, err := os.ReadFile(filepath.Join(*src, "docs.html"))
|
||||
must(err, "读 docs.html")
|
||||
tpl, err := template.New("doc").Parse(string(tplRaw))
|
||||
must(err, "解析 docs 模板")
|
||||
md := goldmark.New(goldmark.WithExtensions(extension.Table, extension.Strikethrough, extension.TaskList), goldmark.WithRendererOptions(gmhtml.WithHardWraps()))
|
||||
|
||||
var order []string
|
||||
pages := map[string]*Page{}
|
||||
var search []SearchEntry
|
||||
var prev *NavLink
|
||||
for _, g := range manifest.Groups {
|
||||
for _, def := range g.Pages {
|
||||
srcb, err := os.ReadFile(filepath.Join(*src, "content", def.File))
|
||||
must(err, "读文档 "+def.File)
|
||||
var buf strings.Builder
|
||||
must(md.Convert(srcb, &buf), "渲染 "+def.File)
|
||||
body, toc := injectHeadingIDs(buf.String())
|
||||
p := &Page{Slug: def.Slug, Title: def.Title, Group: g.Name, Updated: def.Updated, HTML: body, TOC: toc, Prev: prev}
|
||||
pages[def.Slug] = p
|
||||
order = append(order, def.Slug)
|
||||
prev = &NavLink{Slug: def.Slug, Title: def.Title}
|
||||
search = append(search, SearchEntry{Slug: def.Slug, Title: def.Title, Group: g.Name, Text: plainText(string(srcb))})
|
||||
}
|
||||
}
|
||||
for i, slug := range order {
|
||||
if i+1 < len(order) {
|
||||
n := pages[order[i+1]]
|
||||
pages[slug].Next = &NavLink{Slug: n.Slug, Title: n.Title}
|
||||
}
|
||||
}
|
||||
// /docs/ 首页 = 第一篇(静态站无重定向,直接渲染)
|
||||
render := func(p *Page) string {
|
||||
var sb strings.Builder
|
||||
must(tpl.Execute(&sb, map[string]any{"Manifest": manifest, "Page": p, "TOC": p.TOC, "Content": template.HTML(p.HTML)}), "渲染模板 "+p.Slug)
|
||||
return sb.String()
|
||||
}
|
||||
writeFile(filepath.Join(*out, "docs", "index.html"), render(pages[order[0]]))
|
||||
for _, slug := range order {
|
||||
writeFile(filepath.Join(*out, "docs", slug, "index.html"), render(pages[slug]))
|
||||
}
|
||||
sj, _ := json.Marshal(search)
|
||||
writeFile(filepath.Join(*out, "docs", "search-data.json"), string(sj))
|
||||
// docs assets(模板引用 /docs/assets/*)
|
||||
da := filepath.Join(*src, "docs-assets")
|
||||
if st, _ := os.Stat(da); st != nil {
|
||||
must(os.CopyFS(filepath.Join(*out, "docs", "assets"), os.DirFS(da)), "拷 docs assets")
|
||||
fmt.Println("[OK]", filepath.Join(*out, "docs", "assets"))
|
||||
}
|
||||
fmt.Printf("[DONE] %d 页文档 + 落地页 → %s(纯静态,可部署任意域名)\n", len(order), *out)
|
||||
}
|
||||
Reference in new issue
Block a user