stack-site-builder 1.22.0 → 1.23.1

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,30 @@ 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.1] - 2026-07-29
15
+
16
+ ### Fixed
17
+
18
+ - **Prose blockquote styling** — `>` blockquotes in reading content (courses,
19
+ articles, concepts, stacks and standalone pages) rendered with the browser
20
+ default and were nearly indistinguishable from a normal paragraph. They now
21
+ get a left accent rail and a faint neutral fill so a pulled-out passage reads
22
+ as set apart. Light/dark-aware (theme tokens), and no italic so it renders
23
+ cleanly on Korean prose. Two-trailing-space hard breaks inside the quote are
24
+ preserved as before.
25
+
26
+ ## [1.23.0] - 2026-07-27
27
+
28
+ ### Added
29
+
30
+ - **Wikilink `course` / `paper` targets** — glossary entries can now point at
31
+ the opt-in sections' detail pages: `{ label: '…', course: '<slug>' }` links
32
+ `[[Term]]` to `/course/<slug>/`, `paper: '<id>'` to `/paper/<id>/` (relative,
33
+ so locale- and base-path-agnostic like the existing stack/concept/article
34
+ targets). The glossary page lists them under new Courses/Papers sections
35
+ (flat — no dependency on the sites' opt-in course/paper category data), with
36
+ graduation-cap / scroll fallback glyphs.
37
+
14
38
  ## [1.22.0] - 2026-07-25
15
39
 
16
40
  ### Added
@@ -409,6 +433,8 @@ catalog sites from a thin content-only repository.
409
433
  - **Standalone development setup** — a devcontainer and a minimal `playground/`
410
434
  consuming site for developing and previewing the theme on its own.
411
435
 
436
+ [1.23.1]: https://github.com/CodeComposeStudio/stack-site-builder/compare/v1.23.0...v1.23.1
437
+ [1.23.0]: https://github.com/CodeComposeStudio/stack-site-builder/compare/v1.22.0...v1.23.0
412
438
  [1.19.3]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.19.2...v1.19.3
413
439
  [1.19.2]: https://github.com/CodeCompose7/stack-site-builder/compare/v1.19.1...v1.19.2
414
440
  [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 |
@@ -202,6 +202,9 @@ import Bookmark from 'stack-site-builder/components/Bookmark.astro';
202
202
  <Bookmark url="https://…" title="…" description="…" />
203
203
  ```
204
204
 
205
+ Markdown/MDX authoring gotchas (nested-list indentation, blockquotes, hard
206
+ breaks, wikilinks): `docs/content-authoring.md`.
207
+
205
208
  ## RSS
206
209
 
207
210
  The articles collection feeds `/rss.xml` (default locale) and
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,7 +1,7 @@
1
1
  {
2
2
  "name": "stack-site-builder",
3
3
  "type": "module",
4
- "version": "1.22.0",
4
+ "version": "1.23.1",
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": {
@@ -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': '용어 검색…',
@@ -581,6 +581,24 @@ summary {
581
581
  list-style: decimal;
582
582
  padding-left: 1.25rem;
583
583
  }
584
+ /* Blockquotes — a quiet left-accent rail with a faint neutral fill, so a
585
+ pulled-out passage reads as set apart from the running text. No italic: it
586
+ renders on Korean prose too, where slanted CJK looks wrong. First/last child
587
+ margins collapse so the quote hugs its own padding. */
588
+ .prose :where(blockquote) {
589
+ margin: 1.25rem 0;
590
+ padding: 0.75rem 1.15rem;
591
+ border-left: 3px solid var(--aas-accent);
592
+ border-radius: 0 0.5rem 0.5rem 0;
593
+ background: var(--aas-tint);
594
+ color: color-mix(in srgb, var(--aas-text) 88%, transparent);
595
+ }
596
+ .prose :where(blockquote > :first-child) {
597
+ margin-top: 0;
598
+ }
599
+ .prose :where(blockquote > :last-child) {
600
+ margin-bottom: 0;
601
+ }
584
602
  .prose :where(a) {
585
603
  color: var(--aas-accent);
586
604
  text-decoration: none;