-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdata.go
More file actions
191 lines (172 loc) · 5.92 KB
/
Copy pathdata.go
File metadata and controls
191 lines (172 loc) · 5.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package main
import (
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
)
// Portfolio is the single source of truth for product metadata, shared by both sites.
type Portfolio struct {
Schema string `json:"schema"`
Count int `json:"count"`
Tiers []string `json:"tiers"`
Excluded []string `json:"excluded"`
Entries []PortfolioEntry `json:"entries"`
}
type PortfolioEntry struct {
Name string `json:"name"`
Slug string `json:"slug"`
Tier string `json:"tier"`
Order int `json:"order"`
Status string `json:"status"`
License string `json:"license"`
Private bool `json:"private"`
Tech []string `json:"tech"`
Repos []string `json:"repos"`
Summary string `json:"summary"`
Tagline string `json:"tagline"`
Source string `json:"source"`
}
func loadPortfolio(path string) (*Portfolio, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var p Portfolio
if err := json.Unmarshal(b, &p); err != nil {
return nil, err
}
return &p, nil
}
func (p *Portfolio) bySlug(slug string) *PortfolioEntry {
for i := range p.Entries {
if p.Entries[i].Slug == slug {
return &p.Entries[i]
}
}
return nil
}
// esc escapes text for safe HTML output (matches the reference renderer: & < > ").
func esc(s string) string {
r := strings.NewReplacer("&", "&", "<", "<", ">", ">", "\"", """)
return r.Replace(s)
}
var fmRe = regexp.MustCompile(`(?s)^---\n(.*?)\n---\n(.*)$`)
// frontmatter is the minimal YAML subset used by product .md files
// (scalars + simple string lists), matching _tools/portfolio/generate.mjs.
type frontmatter struct {
scalars map[string]string
lists map[string][]string
}
func parseFrontmatter(md string) (frontmatter, string, error) {
m := fmRe.FindStringSubmatch(md)
if m == nil {
return frontmatter{}, "", fmt.Errorf("no frontmatter")
}
fm := frontmatter{scalars: map[string]string{}, lists: map[string][]string{}}
var listKey string
kvRe := regexp.MustCompile(`^([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$`)
itemRe := regexp.MustCompile(`^\s+-\s+(.*)$`)
for _, line := range strings.Split(m[1], "\n") {
if it := itemRe.FindStringSubmatch(line); it != nil && listKey != "" {
fm.lists[listKey] = append(fm.lists[listKey], strings.TrimSpace(it[1]))
continue
}
kv := kvRe.FindStringSubmatch(line)
if kv == nil {
continue
}
key, val := kv[1], kv[2]
if val == "" {
fm.lists[key] = []string{}
listKey = key
} else {
fm.scalars[key] = val
listKey = ""
}
}
return fm, m[2], nil
}
// ---- Homepage content model (per-site, authored under _content/sites/*.home.json) ----
type HomeDoc struct {
Site string `json:"site"`
Kind string `json:"kind"` // "standalone" (vasic) or "jekyll" (milosvasic)
Brand string `json:"brand"`
Lang string `json:"lang"`
Title string `json:"title"`
Desc string `json:"description"`
CSS string `json:"css"` // stylesheet href
Footer string `json:"footer"` // standalone only
Blocks []HomeBlock `json:"blocks"`
}
type HomeBlock struct {
Type string `json:"type"` // hero | section | contact
ID string `json:"id"`
Eyebrow I18nText `json:"eyebrow"` // hero, or section-level header
Title I18nText `json:"title"`
Lede I18nText `json:"lede"`
CTAs []CTA `json:"ctas"` // hero CTAs, or section trailing CTA row
CTAWrap string `json:"ctaWrap"` // class for trailing CTA row (default {prefix}-hero__cta)
CTAStyle string `json:"ctaStyle"` // inline style for trailing CTA row
Stats []Stat `json:"stats"` // hero
Groups []Group `json:"groups"` // section
Contacts []Contact `json:"contacts"` // contact
}
// Group is a sub-block of a section: a prose run, a card grid, or a tech grid.
type Group struct {
Eyebrow I18nText `json:"eyebrow"`
Title I18nText `json:"title"`
Lede I18nText `json:"lede"`
MarginTop bool `json:"marginTop"` // eyebrow gets margin-top:var(--od-space-10)
Kind string `json:"kind"` // prose | cards | tech
Grid string `json:"grid"` // grid class for cards/tech
Paras []I18nText `json:"paras"` // prose
Cards []Card `json:"cards"` // cards/tech
}
// I18nText carries the visible (English) text plus its data-i18n key and an optional class.
type I18nText struct {
I18n string `json:"i18n"`
Text string `json:"text"`
Class string `json:"class"`
HTML bool `json:"html"` // Text is trusted HTML (already escaped)
}
type CTA struct {
I18n string `json:"i18n"`
Label string `json:"label"`
Href string `json:"href"` // relative_url-wrapped where needed
Variant string `json:"variant"` // primary | secondary | ghost
DL string `json:"dl"` // data-dl value (milosvasic hero buttons); if set, renders <button>
Icon string `json:"icon"` // icon symbol id (e.g. i-download)
}
type Stat struct {
I18n string `json:"i18n"`
Value string `json:"value"`
Label string `json:"label"`
}
type Card struct {
Slug string `json:"slug"` // product cards: identity + status lookup + href
Name string `json:"name"` // display title
NameI18n string `json:"nameI18n"` // data-i18n on the title (tiles / tech cards)
Status string `json:"status"` // override; if empty and slug set, pulled from portfolio
NoBadge bool `json:"noBadge"` // suppress status badge even if slug set
Tech []string `json:"tech"` // curated chips (verbatim, no +N)
Blurb I18nText `json:"blurb"`
Readmore I18nText `json:"readmore"` // read-more link label + i18n; href derived from slug
}
type Contact struct {
I18n string `json:"i18n"`
Label string `json:"label"`
HTML string `json:"html"` // trusted inner HTML for the value line
}
func loadHome(path string) (*HomeDoc, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var h HomeDoc
if err := json.Unmarshal(b, &h); err != nil {
return nil, err
}
return &h, nil
}