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 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 `../stack|concept/<slug>/` relative form (locale- and base-agnostic), with
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.href
348
- ? entry.href
349
- : def
350
- ? `${up}glossary/#${id}`
351
- : null;
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.21.0",
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/CodeCompose7/stack-site-builder.git"
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), an external link (globe), and a plain term (book) glossary
34
- // fallbacks for avatars without a logo.
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 anchors into the catalog home's category sections it only makes
96
- // sense when the site keeps the catalog home template.
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: '<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"/>',
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;