stack-site-builder 1.21.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +15 -2
- package/markdown.mjs +13 -8
- package/package.json +2 -2
- package/src/components/Glossary.astro +33 -0
- package/src/components/ToolAvatar.astro +7 -2
- package/src/i18n/ui.ts +4 -0
- package/src/layouts/BaseLayout.astro +21 -5
- package/src/lib/home.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,31 @@ content schema, while a consuming site supplies only content, taxonomy data and
|
|
|
11
11
|
config. Sites track the theme with `pnpm up stack-site-builder`, so each release
|
|
12
12
|
here is a plain version bump they pull in.
|
|
13
13
|
|
|
14
|
+
## [1.23.0] - 2026-07-27
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Wikilink `course` / `paper` targets** — glossary entries can now point at
|
|
19
|
+
the opt-in sections' detail pages: `{ label: '…', course: '<slug>' }` links
|
|
20
|
+
`[[Term]]` to `/course/<slug>/`, `paper: '<id>'` to `/paper/<id>/` (relative,
|
|
21
|
+
so locale- and base-path-agnostic like the existing stack/concept/article
|
|
22
|
+
targets). The glossary page lists them under new Courses/Papers sections
|
|
23
|
+
(flat — no dependency on the sites' opt-in course/paper category data), with
|
|
24
|
+
graduation-cap / scroll fallback glyphs.
|
|
25
|
+
|
|
26
|
+
## [1.22.0] - 2026-07-25
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **Cards-home catalog Browse nav** — `home.browse = { href, label?, external? }`:
|
|
31
|
+
on a cards home the stacks catalog Browse nav is hidden (it anchors into the
|
|
32
|
+
catalog home's `#categories` section, which a cards home doesn't have). Set
|
|
33
|
+
`home.browse` to add a header nav item (the same grid icon) that links to a
|
|
34
|
+
separate catalog page instead — e.g. a category browse at `/categories/tools/`.
|
|
35
|
+
`href` is a locale-less internal path (prefixed per locale) or an external URL;
|
|
36
|
+
`label` falls back to the theme's "Browse" string. The catalog home is
|
|
37
|
+
unaffected — it keeps its built-in `#categories` Browse.
|
|
38
|
+
|
|
14
39
|
## [1.21.0] - 2026-07-24
|
|
15
40
|
|
|
16
41
|
### Added
|
|
@@ -396,6 +421,7 @@ catalog sites from a thin content-only repository.
|
|
|
396
421
|
- **Standalone development setup** — a devcontainer and a minimal `playground/`
|
|
397
422
|
consuming site for developing and previewing the theme on its own.
|
|
398
423
|
|
|
424
|
+
[1.23.0]: https://github.com/CodeComposeStudio/stack-site-builder/compare/v1.22.0...v1.23.0
|
|
399
425
|
[1.19.3]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.19.2...v1.19.3
|
|
400
426
|
[1.19.2]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.19.1...v1.19.2
|
|
401
427
|
[1.19.1]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.19.0...v1.19.1
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ export const collections = defineAasCollections({ categoryMap });
|
|
|
39
39
|
| `src/data/site.ts` | Site identity: name, repo URL (`repoNav: false` hides the header's GitHub link), the `locales` it ships, optional `sections` toggles, browser icons (`icons: { favicon, appleTouch, manifest }`), the `home` template, per-locale UI string overrides |
|
|
40
40
|
| `src/data/categories.ts` | The tool-catalog category tree (validated against content) |
|
|
41
41
|
| `src/data/concept-categories.ts` · `article-categories.ts` · `course-categories.ts` · `product-categories.ts` · `paper-categories.ts` (opt-in) | Taxonomies for concepts / articles / courses / products / papers |
|
|
42
|
-
| `src/data/glossary.mjs` | `[[Term]]` wikilink targets |
|
|
42
|
+
| `src/data/glossary.mjs` | `[[Term]]` wikilink targets — each entry links a term to a `stack` / `concept` / `article` / `course` / `paper` page, an external `href`, or is a definition-only term (`def`) |
|
|
43
43
|
| `src/content/{stacks,concepts,courses,products,papers,articles,slides}/` | The content, one MDX file per locale |
|
|
44
44
|
| `src/content/pages/` | Standalone top-level pages (e.g. an About/소개), rendered at `/<slug>/` and optionally linked in the header nav |
|
|
45
45
|
| `public/` · `samples/` | Logos/favicons and runnable sample projects |
|
|
@@ -165,7 +165,20 @@ home: {
|
|
|
165
165
|
|
|
166
166
|
Localized values are either one string or a per-locale record with
|
|
167
167
|
default-locale fallback. On a cards home the header's Browse link (which
|
|
168
|
-
anchors into the catalog) hides itself
|
|
168
|
+
anchors into the catalog home's `#categories` section) hides itself — set
|
|
169
|
+
`home.browse` to add it back, pointing at a separate catalog page instead:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
home: {
|
|
173
|
+
template: 'cards',
|
|
174
|
+
// …
|
|
175
|
+
// Header nav item (grid icon) → a standalone catalog page. Use this when the
|
|
176
|
+
// catalog lives at a category browse rather than on the home. `href` is a
|
|
177
|
+
// locale-less internal path (or an external URL with `external: true`);
|
|
178
|
+
// `label` defaults to the theme's "Browse" string.
|
|
179
|
+
browse: { href: '/categories/tools/', label: { ko: '사용 도구', en: 'Tools' } },
|
|
180
|
+
},
|
|
181
|
+
```
|
|
169
182
|
|
|
170
183
|
## Papers (opt-in)
|
|
171
184
|
|
package/markdown.mjs
CHANGED
|
@@ -230,7 +230,8 @@ function remarkSlideDirectives() {
|
|
|
230
230
|
|
|
231
231
|
// Turn `[[Term]]` (and `[[Term|display text]]`) wikilinks into links, resolving
|
|
232
232
|
// each against the site's central glossary (passed as an option). Internal targets
|
|
233
|
-
// emit a
|
|
233
|
+
// (stack/concept/article/course/paper) emit a `../<kind>/<slug>/` relative form
|
|
234
|
+
// (locale- and base-agnostic), with
|
|
234
235
|
// the number of `../` set by how deep the source page sits in its locale (see `up`
|
|
235
236
|
// below); external `href` entries pass through and get
|
|
236
237
|
// target="_blank" from rehype-external-links downstream. An unknown term throws,
|
|
@@ -344,19 +345,23 @@ function remarkGlossary({ glossary, locales = ['en', 'ko'], defaultLocale = 'en'
|
|
|
344
345
|
? `${up}concept/${entry.concept}/`
|
|
345
346
|
: entry.article
|
|
346
347
|
? `${up}article/${entry.article}/`
|
|
347
|
-
: entry.
|
|
348
|
-
? entry.
|
|
349
|
-
:
|
|
350
|
-
? `${up}
|
|
351
|
-
:
|
|
348
|
+
: entry.course
|
|
349
|
+
? `${up}course/${entry.course}/`
|
|
350
|
+
: entry.paper
|
|
351
|
+
? `${up}paper/${entry.paper}/`
|
|
352
|
+
: entry.href
|
|
353
|
+
? entry.href
|
|
354
|
+
: def
|
|
355
|
+
? `${up}glossary/#${id}`
|
|
356
|
+
: null;
|
|
352
357
|
if (!url)
|
|
353
358
|
throw new Error(
|
|
354
|
-
`[glossary] term "[[${m[1]}]]" needs one of stack/concept/article/href/def`,
|
|
359
|
+
`[glossary] term "[[${m[1]}]]" needs one of stack/concept/article/course/paper/href/def`,
|
|
355
360
|
);
|
|
356
361
|
if (anchor) {
|
|
357
362
|
// A def-only target already carries its own hash — an extra
|
|
358
363
|
// anchor is a mistake, so fail the build like an unknown term.
|
|
359
|
-
if (!entry.stack && !entry.concept && !entry.article && !entry.href)
|
|
364
|
+
if (!entry.stack && !entry.concept && !entry.article && !entry.course && !entry.paper && !entry.href)
|
|
360
365
|
throw new Error(
|
|
361
366
|
`[glossary] "[[${m[1]}]]" — a definition-only term can't take a #anchor`,
|
|
362
367
|
);
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stack-site-builder",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.23.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "The engine behind the awesome-*-stack catalog sites: an Astro theme with the catalog/concepts/articles/slides/samples routes, components, styles and markdown pipeline. Sites provide content, taxonomy data and config.",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
9
|
+
"url": "git+https://github.com/CodeComposeStudio/stack-site-builder.git"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"index.mjs",
|
|
@@ -19,6 +19,8 @@ type Entry = {
|
|
|
19
19
|
stack?: string;
|
|
20
20
|
concept?: string;
|
|
21
21
|
article?: string;
|
|
22
|
+
course?: string;
|
|
23
|
+
paper?: string;
|
|
22
24
|
href?: string;
|
|
23
25
|
def?: string | { ko: string; en: string };
|
|
24
26
|
};
|
|
@@ -54,6 +56,11 @@ const articleBySlug = new Map(articles.map((a) => [articleSlugOf(a), a]));
|
|
|
54
56
|
const tools: Term[] = [];
|
|
55
57
|
const conceptTerms: Term[] = [];
|
|
56
58
|
const articleTerms: Term[] = [];
|
|
59
|
+
// Courses/papers are opt-in sections whose category data a site may not have,
|
|
60
|
+
// so their terms render as flat groups (no @aas-data/{course,paper}-categories
|
|
61
|
+
// import — that would break sites that don't supply those files).
|
|
62
|
+
const courseTerms: Term[] = [];
|
|
63
|
+
const paperTerms: Term[] = [];
|
|
57
64
|
const defTerms: Term[] = [];
|
|
58
65
|
const external: Term[] = [];
|
|
59
66
|
|
|
@@ -110,6 +117,30 @@ for (const [id, e] of Object.entries(map)) {
|
|
|
110
117
|
catLabel: top?.label[lang] ?? '',
|
|
111
118
|
search: `${label} ${leaf?.label[lang] ?? ''} ${top?.label[lang] ?? ''}`.toLowerCase(),
|
|
112
119
|
});
|
|
120
|
+
} else if (e.course) {
|
|
121
|
+
courseTerms.push({
|
|
122
|
+
id,
|
|
123
|
+
label,
|
|
124
|
+
href: getRelativeLocaleUrl(lang, `course/${e.course}/`),
|
|
125
|
+
subtitle: def,
|
|
126
|
+
external: false,
|
|
127
|
+
icon: { name: label, fallback: 'course' },
|
|
128
|
+
catId: '',
|
|
129
|
+
catLabel: '',
|
|
130
|
+
search: `${label} ${def}`.toLowerCase(),
|
|
131
|
+
});
|
|
132
|
+
} else if (e.paper) {
|
|
133
|
+
paperTerms.push({
|
|
134
|
+
id,
|
|
135
|
+
label,
|
|
136
|
+
href: getRelativeLocaleUrl(lang, `paper/${e.paper}/`),
|
|
137
|
+
subtitle: def,
|
|
138
|
+
external: false,
|
|
139
|
+
icon: { name: label, fallback: 'paper' },
|
|
140
|
+
catId: '',
|
|
141
|
+
catLabel: '',
|
|
142
|
+
search: `${label} ${def}`.toLowerCase(),
|
|
143
|
+
});
|
|
113
144
|
} else if (e.href) {
|
|
114
145
|
external.push({
|
|
115
146
|
id,
|
|
@@ -162,6 +193,8 @@ const kinds = [
|
|
|
162
193
|
{ key: 'tool', title: t('glossary.tool'), groups: groupBy(tools, rootCategories.map((c) => c.id)) },
|
|
163
194
|
{ key: 'concept', title: t('glossary.concept'), groups: groupBy(conceptTerms, conceptTree.roots.map((c) => c.id)) },
|
|
164
195
|
{ key: 'article', title: t('glossary.article'), groups: groupBy(articleTerms, articleTree.roots.map((c) => c.id)) },
|
|
196
|
+
{ key: 'course', title: t('glossary.course'), groups: flat(courseTerms) },
|
|
197
|
+
{ key: 'paper', title: t('glossary.paper'), groups: flat(paperTerms) },
|
|
165
198
|
{ key: 'term', title: t('glossary.term'), groups: flat(defTerms) },
|
|
166
199
|
{ key: 'external', title: t('glossary.external'), groups: flat(external) },
|
|
167
200
|
].filter((k) => k.groups.length > 0);
|
|
@@ -30,12 +30,17 @@ const icons: Record<string, string> = {
|
|
|
30
30
|
'<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/>',
|
|
31
31
|
observability: '<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',
|
|
32
32
|
// A concept/idea (lightbulb), an article (file-text, matching the Writing nav
|
|
33
|
-
// icon),
|
|
34
|
-
// fallbacks for avatars without
|
|
33
|
+
// icon), a course (graduation cap), a paper (scroll), an external link
|
|
34
|
+
// (globe), and a plain term (book) — glossary fallbacks for avatars without
|
|
35
|
+
// a logo.
|
|
35
36
|
concept:
|
|
36
37
|
'<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5"/><path d="M9 18h6"/><path d="M10 22h4"/>',
|
|
37
38
|
article:
|
|
38
39
|
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/>',
|
|
40
|
+
course:
|
|
41
|
+
'<path d="M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z"/><path d="M22 10v6"/><path d="M6 12.5V16a6 3 0 0 0 12 0v-3.5"/>',
|
|
42
|
+
paper:
|
|
43
|
+
'<path d="M15 12h-5"/><path d="M15 8h-5"/><path d="M19 17V5a2 2 0 0 0-2-2H4"/><path d="M8 21h12a2 2 0 0 0 2-2v-1a1 1 0 0 0-1-1H11a1 1 0 0 0-1 1v1a2 2 0 1 1-4 0V5a2 2 0 1 0-4 0v2a1 1 0 0 0 1 1h3"/>',
|
|
39
44
|
external:
|
|
40
45
|
'<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
|
|
41
46
|
term: '<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"/>',
|
package/src/i18n/ui.ts
CHANGED
|
@@ -110,6 +110,8 @@ export const ui = {
|
|
|
110
110
|
'glossary.tool': 'Tools',
|
|
111
111
|
'glossary.concept': 'Concepts',
|
|
112
112
|
'glossary.article': 'Writing',
|
|
113
|
+
'glossary.course': 'Courses',
|
|
114
|
+
'glossary.paper': 'Papers',
|
|
113
115
|
'glossary.term': 'Terms',
|
|
114
116
|
'glossary.external': 'External',
|
|
115
117
|
'glossary.search': 'Search terms…',
|
|
@@ -287,6 +289,8 @@ export const ui = {
|
|
|
287
289
|
'glossary.tool': '도구',
|
|
288
290
|
'glossary.concept': '개념',
|
|
289
291
|
'glossary.article': '글',
|
|
292
|
+
'glossary.course': '강의',
|
|
293
|
+
'glossary.paper': '논문',
|
|
290
294
|
'glossary.term': '용어',
|
|
291
295
|
'glossary.external': '외부',
|
|
292
296
|
'glossary.search': '용어 검색…',
|
|
@@ -12,7 +12,7 @@ import BackToTop from '../components/BackToTop.astro';
|
|
|
12
12
|
import Footer from '@aas-footer';
|
|
13
13
|
import { getNavPages, pageSlugOf } from '../lib/pages';
|
|
14
14
|
import { getNavProducts, getTopProducts, productSlugOf } from '../lib/products';
|
|
15
|
-
import { homeTemplate } from '../lib/home';
|
|
15
|
+
import { homeTemplate, home as homeConfig, loc } from '../lib/home';
|
|
16
16
|
import { sectionEnabled, type SectionKey } from '../lib/sections';
|
|
17
17
|
import { siteIcons } from '../lib/icons';
|
|
18
18
|
import { privateClientData } from '../lib/private';
|
|
@@ -84,6 +84,10 @@ const showRepoLink =
|
|
|
84
84
|
// layouts never drift: an icon-only row (≥sm) and a labelled dropdown menu
|
|
85
85
|
// (<sm, so a phone header stays to a few controls instead of a long icon run).
|
|
86
86
|
// `svg` is the icon's inner markup (drawn into a shared <svg> shell below).
|
|
87
|
+
// Shared 2×2 grid icon for the catalog Browse nav (catalog home + cards-home
|
|
88
|
+
// `home.browse`).
|
|
89
|
+
const CATALOG_ICON =
|
|
90
|
+
'<rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/>';
|
|
87
91
|
const allNavItems: {
|
|
88
92
|
href: string;
|
|
89
93
|
label: string;
|
|
@@ -92,18 +96,30 @@ const allNavItems: {
|
|
|
92
96
|
keepFilters?: boolean;
|
|
93
97
|
section?: SectionKey;
|
|
94
98
|
}[] = [
|
|
95
|
-
// Browse
|
|
96
|
-
//
|
|
99
|
+
// Browse (grid icon): on the catalog home it anchors into the #categories
|
|
100
|
+
// section; on a cards home it's off unless the site sets `home.browse` to
|
|
101
|
+
// point at a separate catalog page (e.g. a category browse).
|
|
97
102
|
...(homeTemplate === 'catalog'
|
|
98
103
|
? [
|
|
99
104
|
{
|
|
100
105
|
href: `${home}#categories`,
|
|
101
106
|
label: t('nav.browse'),
|
|
102
107
|
keepFilters: true,
|
|
103
|
-
svg:
|
|
108
|
+
svg: CATALOG_ICON,
|
|
104
109
|
},
|
|
105
110
|
]
|
|
106
|
-
:
|
|
111
|
+
: homeConfig?.browse
|
|
112
|
+
? [
|
|
113
|
+
{
|
|
114
|
+
href: homeConfig.browse.external
|
|
115
|
+
? homeConfig.browse.href
|
|
116
|
+
: getRelativeLocaleUrl(lang, homeConfig.browse.href.replace(/^\//, '')),
|
|
117
|
+
label: loc(homeConfig.browse.label, lang) ?? t('nav.browse'),
|
|
118
|
+
external: homeConfig.browse.external,
|
|
119
|
+
svg: CATALOG_ICON,
|
|
120
|
+
},
|
|
121
|
+
]
|
|
122
|
+
: []),
|
|
107
123
|
...(hasProducts
|
|
108
124
|
? [
|
|
109
125
|
{
|
package/src/lib/home.ts
CHANGED
|
@@ -45,6 +45,15 @@ export interface HomeConfig {
|
|
|
45
45
|
description?: Localized;
|
|
46
46
|
button?: { label: Localized; href: string; external?: boolean };
|
|
47
47
|
};
|
|
48
|
+
/** Catalog "Browse" nav link for the cards home. The stacks catalog Browse
|
|
49
|
+
* nav anchors into the catalog home's #categories section, so it's hidden on
|
|
50
|
+
* a cards home — set this to add a header nav item that points at a separate
|
|
51
|
+
* catalog page instead (e.g. a category browse at `/categories/tools/`, or a
|
|
52
|
+
* tag/vendor page). `href` is a locale-less internal path (prefixed per
|
|
53
|
+
* locale at render) or an external URL (`external: true`); `label` defaults
|
|
54
|
+
* to the theme's "Browse" string. Ignored on the catalog home, which keeps
|
|
55
|
+
* its built-in #categories Browse. */
|
|
56
|
+
browse?: { href: string; label?: Localized; external?: boolean };
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
export const home: HomeConfig | undefined = (site as { home?: HomeConfig }).home;
|