stack-site-builder 1.10.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/LICENSE +21 -0
- package/README.md +45 -0
- package/dev/local-samples-client.js +284 -0
- package/dev/local-samples.mjs +594 -0
- package/index.d.ts +12 -0
- package/index.mjs +124 -0
- package/markdown.mjs +394 -0
- package/package.json +52 -0
- package/src/components/ArrowUpRight.astro +24 -0
- package/src/components/ArticleCard.astro +56 -0
- package/src/components/ArticleDetail.astro +100 -0
- package/src/components/ArticleLink.astro +45 -0
- package/src/components/BackToTop.astro +47 -0
- package/src/components/BlogIndex.astro +137 -0
- package/src/components/Breadcrumb.astro +37 -0
- package/src/components/CategoryIndex.astro +115 -0
- package/src/components/ChevronDown.astro +23 -0
- package/src/components/CodeSamples.astro +141 -0
- package/src/components/CollapsibleGrid.astro +50 -0
- package/src/components/ConceptCard.astro +59 -0
- package/src/components/ConceptDetail.astro +102 -0
- package/src/components/ConceptIndex.astro +143 -0
- package/src/components/ConceptLink.astro +43 -0
- package/src/components/ConceptTools.astro +49 -0
- package/src/components/DeckView.astro +772 -0
- package/src/components/DetailTabs.astro +219 -0
- package/src/components/FilterControls.astro +407 -0
- package/src/components/Glossary.astro +264 -0
- package/src/components/Home.astro +90 -0
- package/src/components/LanguageSwitcher.astro +86 -0
- package/src/components/ListControls.astro +182 -0
- package/src/components/MermaidLoader.astro +96 -0
- package/src/components/PricingSection.astro +100 -0
- package/src/components/ProjectFileList.astro +68 -0
- package/src/components/ProjectFileTree.astro +95 -0
- package/src/components/ProjectRelatedTools.astro +70 -0
- package/src/components/ProjectViewer.astro +431 -0
- package/src/components/RelatedArticles.astro +52 -0
- package/src/components/RelatedConcepts.astro +60 -0
- package/src/components/RelatedTools.astro +55 -0
- package/src/components/SampleIndex.astro +464 -0
- package/src/components/SamplePage.astro +95 -0
- package/src/components/SampleProject.astro +56 -0
- package/src/components/Slide.astro +36 -0
- package/src/components/SlidesIndex.astro +60 -0
- package/src/components/StackCard.astro +183 -0
- package/src/components/StackDetail.astro +507 -0
- package/src/components/StarIcon.astro +21 -0
- package/src/components/TagIndex.astro +51 -0
- package/src/components/ThemeToggle.astro +106 -0
- package/src/components/Toc.astro +39 -0
- package/src/components/TocRail.astro +74 -0
- package/src/components/ToolAvatar.astro +133 -0
- package/src/components/ToolMeta.astro +95 -0
- package/src/components/VendorIndex.astro +51 -0
- package/src/content.ts +228 -0
- package/src/i18n/ui.ts +337 -0
- package/src/layouts/BaseLayout.astro +287 -0
- package/src/lib/articles.ts +27 -0
- package/src/lib/assets.ts +19 -0
- package/src/lib/category-tree.ts +64 -0
- package/src/lib/concepts.ts +35 -0
- package/src/lib/dates.ts +21 -0
- package/src/lib/facets.ts +57 -0
- package/src/lib/github.ts +186 -0
- package/src/lib/inline-md.ts +30 -0
- package/src/lib/pricing.ts +22 -0
- package/src/lib/project.ts +309 -0
- package/src/lib/samples.ts +88 -0
- package/src/lib/slides.ts +25 -0
- package/src/lib/stacks.ts +129 -0
- package/src/lib/toc-rail-client.ts +79 -0
- package/src/pages/article/[...id].astro +24 -0
- package/src/pages/article/category/[id].astro +17 -0
- package/src/pages/article/index.astro +12 -0
- package/src/pages/categories/[id].astro +25 -0
- package/src/pages/concept/[...id].astro +24 -0
- package/src/pages/concept/category/[id].astro +17 -0
- package/src/pages/concept/index.astro +12 -0
- package/src/pages/glossary.astro +12 -0
- package/src/pages/index.astro +9 -0
- package/src/pages/ko/article/[...id].astro +24 -0
- package/src/pages/ko/article/category/[id].astro +17 -0
- package/src/pages/ko/article/index.astro +12 -0
- package/src/pages/ko/categories/[id].astro +24 -0
- package/src/pages/ko/concept/[...id].astro +24 -0
- package/src/pages/ko/concept/category/[id].astro +17 -0
- package/src/pages/ko/concept/index.astro +12 -0
- package/src/pages/ko/glossary.astro +12 -0
- package/src/pages/ko/index.astro +9 -0
- package/src/pages/ko/sample/[folder].astro +16 -0
- package/src/pages/ko/sample/index.astro +12 -0
- package/src/pages/ko/slides/[deck].astro +14 -0
- package/src/pages/ko/slides/index.astro +12 -0
- package/src/pages/ko/stack/[...id].astro +51 -0
- package/src/pages/ko/tags/[tag].astro +18 -0
- package/src/pages/ko/vendors/[vendor].astro +18 -0
- package/src/pages/sample/[folder].astro +16 -0
- package/src/pages/sample/index.astro +12 -0
- package/src/pages/slides/[deck].astro +14 -0
- package/src/pages/slides/index.astro +12 -0
- package/src/pages/stack/[...id].astro +51 -0
- package/src/pages/tags/[tag].astro +18 -0
- package/src/pages/vendors/[vendor].astro +18 -0
- package/src/styles/global.css +2350 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { useTranslations, type Lang } from '../i18n/ui';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
lang: Lang;
|
|
6
|
+
count: number; // total number of cards in the slot
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Show the first 6 cards (3 rows × 2 cols); collapse the rest behind a toggle.
|
|
10
|
+
// The 6-item cutoff is enforced in CSS (:nth-child(n+7)); keep them in sync.
|
|
11
|
+
const LIMIT = 6;
|
|
12
|
+
const { lang, count } = Astro.props;
|
|
13
|
+
const t = useTranslations(lang);
|
|
14
|
+
const extra = count - LIMIT;
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<div class="mt-3" data-collapse-grid={extra > 0 ? '' : undefined}>
|
|
18
|
+
<div class="grid gap-3 sm:grid-cols-2" data-collapse-items>
|
|
19
|
+
<slot />
|
|
20
|
+
</div>
|
|
21
|
+
{
|
|
22
|
+
extra > 0 && (
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
data-collapse-toggle
|
|
26
|
+
class="mt-3 text-xs font-medium text-[var(--aas-accent)]"
|
|
27
|
+
>
|
|
28
|
+
<span data-more>{t('collapse.more').replace('{n}', String(extra))}</span>
|
|
29
|
+
<span data-less hidden>
|
|
30
|
+
{t('collapse.less')}
|
|
31
|
+
</span>
|
|
32
|
+
</button>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<script>
|
|
38
|
+
// Delegated toggle: reveal/hide the cards beyond the CSS cutoff. One listener
|
|
39
|
+
// covers every CollapsibleGrid on the page (the script is bundled once).
|
|
40
|
+
document.addEventListener('click', (e) => {
|
|
41
|
+
const target = e.target as HTMLElement | null;
|
|
42
|
+
const btn = target?.closest?.('[data-collapse-toggle]');
|
|
43
|
+
if (!btn) return;
|
|
44
|
+
const wrap = btn.closest('[data-collapse-grid]');
|
|
45
|
+
if (!wrap) return;
|
|
46
|
+
const expanded = wrap.toggleAttribute('data-expanded');
|
|
47
|
+
btn.querySelector('[data-more]')?.toggleAttribute('hidden', expanded);
|
|
48
|
+
btn.querySelector('[data-less]')?.toggleAttribute('hidden', !expanded);
|
|
49
|
+
});
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Image } from 'astro:assets';
|
|
3
|
+
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
4
|
+
import { useTranslations, type Lang } from '../i18n/ui';
|
|
5
|
+
import { formatDate } from '../lib/dates';
|
|
6
|
+
import { inlineMd } from '../lib/inline-md';
|
|
7
|
+
import type { ImageMetadata } from 'astro';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
lang: Lang;
|
|
11
|
+
slug: string;
|
|
12
|
+
title: string;
|
|
13
|
+
description: string;
|
|
14
|
+
tags: string[];
|
|
15
|
+
image?: ImageMetadata;
|
|
16
|
+
imageAlt?: string;
|
|
17
|
+
version?: string;
|
|
18
|
+
updated?: Date;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { lang, slug, title, description, tags, image, imageAlt, version, updated } = Astro.props;
|
|
22
|
+
const t = useTranslations(lang);
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<a
|
|
26
|
+
href={getRelativeLocaleUrl(lang, `concept/${slug}/`)}
|
|
27
|
+
data-name={title.toLowerCase()}
|
|
28
|
+
data-date={updated ? updated.toISOString() : ''}
|
|
29
|
+
class="aas-lift block overflow-hidden rounded-3xl border border-[var(--aas-border)] bg-[var(--aas-panel)] no-underline"
|
|
30
|
+
>
|
|
31
|
+
{image && <Image src={image} alt={imageAlt ?? title} class="aspect-[16/9] w-full object-cover" />}
|
|
32
|
+
<div class="p-5">
|
|
33
|
+
{
|
|
34
|
+
(updated || version) && (
|
|
35
|
+
<p class="text-xs text-[var(--aas-muted)]">
|
|
36
|
+
{updated && <time datetime={updated.toISOString().slice(0, 10)}>{formatDate(updated, lang)}</time>}
|
|
37
|
+
{updated && version && <span> · </span>}
|
|
38
|
+
{version && <span>{t('meta.docVersion')} {version}</span>}
|
|
39
|
+
</p>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
<h3 class="mt-1 text-lg font-semibold text-[var(--aas-text)]">{title}</h3>
|
|
43
|
+
<p
|
|
44
|
+
class="aas-md mt-2 text-sm leading-relaxed text-[var(--aas-muted)]"
|
|
45
|
+
set:html={inlineMd(description)}
|
|
46
|
+
/>
|
|
47
|
+
{
|
|
48
|
+
tags.length > 0 && (
|
|
49
|
+
<div class="mt-3 flex flex-wrap gap-1.5">
|
|
50
|
+
{tags.slice(0, 4).map((tag) => (
|
|
51
|
+
<span class="rounded-full border border-[var(--aas-border)] px-2 py-0.5 text-xs text-[var(--aas-muted)]">
|
|
52
|
+
{tag}
|
|
53
|
+
</span>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
</div>
|
|
59
|
+
</a>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { render } from 'astro:content';
|
|
3
|
+
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
4
|
+
import { getArticles, articleSlugOf, type ArticleEntry } from '../lib/articles';
|
|
5
|
+
import { getConcepts, conceptSlugOf, conceptToolSlugs, type ConceptEntry } from '../lib/concepts';
|
|
6
|
+
import { inlineMd } from '../lib/inline-md';
|
|
7
|
+
import { Image } from 'astro:assets';
|
|
8
|
+
import { useTranslations, type Lang } from '../i18n/ui';
|
|
9
|
+
import MermaidLoader from './MermaidLoader.astro';
|
|
10
|
+
import TocRail from './TocRail.astro';
|
|
11
|
+
import Breadcrumb from './Breadcrumb.astro';
|
|
12
|
+
import { conceptTree, conceptCatOf } from '@aas-data/concept-categories';
|
|
13
|
+
import RelatedTools from './RelatedTools.astro';
|
|
14
|
+
import RelatedConcepts from './RelatedConcepts.astro';
|
|
15
|
+
import RelatedArticles from './RelatedArticles.astro';
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
entry: ConceptEntry;
|
|
19
|
+
lang: Lang;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { entry, lang } = Astro.props;
|
|
23
|
+
const t = useTranslations(lang);
|
|
24
|
+
const { Content, headings } = await render(entry);
|
|
25
|
+
const data = entry.data;
|
|
26
|
+
const tocItems = headings.filter((h) => h.depth >= 2 && h.depth <= 3);
|
|
27
|
+
const bodyHasMermaid = (entry.body ?? '').includes('```mermaid');
|
|
28
|
+
|
|
29
|
+
// Related sections (cards), like a stack detail page: the tools this concept
|
|
30
|
+
// uses (flattened from its role groups), sibling concepts, and linked writing.
|
|
31
|
+
const articles = await getArticles(lang);
|
|
32
|
+
const articleBySlug = new Map(articles.map((a) => [articleSlugOf(a), a]));
|
|
33
|
+
const relatedArticles = data.articles
|
|
34
|
+
.map((sl) => articleBySlug.get(sl))
|
|
35
|
+
.filter(Boolean) as ArticleEntry[];
|
|
36
|
+
|
|
37
|
+
const concepts = await getConcepts(lang);
|
|
38
|
+
const conceptBySlug = new Map(concepts.map((c) => [conceptSlugOf(c), c]));
|
|
39
|
+
const relatedConcepts = data.related
|
|
40
|
+
.map((sl) => conceptBySlug.get(sl))
|
|
41
|
+
.filter(Boolean) as ConceptEntry[];
|
|
42
|
+
|
|
43
|
+
const fmtDate = (d: Date) =>
|
|
44
|
+
new Intl.DateTimeFormat(lang === 'ko' ? 'ko-KR' : 'en-US', {
|
|
45
|
+
year: 'numeric',
|
|
46
|
+
month: 'long',
|
|
47
|
+
day: 'numeric',
|
|
48
|
+
timeZone: 'UTC',
|
|
49
|
+
}).format(d);
|
|
50
|
+
|
|
51
|
+
// Breadcrumb: All concepts › category › subcategory… (each segment linked).
|
|
52
|
+
const crumbs = [
|
|
53
|
+
{ label: t('concept.backToConcepts'), href: getRelativeLocaleUrl(lang, 'concept/') },
|
|
54
|
+
...conceptTree.pathOf(conceptCatOf(data.category)).map((c) => ({
|
|
55
|
+
label: c.label[lang],
|
|
56
|
+
href: getRelativeLocaleUrl(lang, `concept/category/${c.id}/`),
|
|
57
|
+
})),
|
|
58
|
+
];
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
<Breadcrumb segments={crumbs} />
|
|
62
|
+
|
|
63
|
+
<header class="mt-4 pb-6">
|
|
64
|
+
<h1 class="text-3xl font-bold tracking-tight">{data.title}</h1>
|
|
65
|
+
{
|
|
66
|
+
(data.version || data.updated) && (
|
|
67
|
+
<p class="mt-1 text-sm text-[var(--aas-muted)]">
|
|
68
|
+
{data.version && <span>{t('meta.docVersion')} {data.version}</span>}
|
|
69
|
+
{data.version && data.updated && <span> · </span>}
|
|
70
|
+
{data.updated && (
|
|
71
|
+
<span>
|
|
72
|
+
{t('concept.updated')} {fmtDate(data.updated)}
|
|
73
|
+
</span>
|
|
74
|
+
)}
|
|
75
|
+
</p>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
<p class="mt-2 aas-md text-lg whitespace-pre-line text-[var(--aas-muted)]" set:html={inlineMd(data.description)} />
|
|
79
|
+
{
|
|
80
|
+
data.image && (
|
|
81
|
+
<Image
|
|
82
|
+
src={data.image}
|
|
83
|
+
alt={data.imageAlt ?? data.title}
|
|
84
|
+
class="mt-5 aspect-[2/1] w-full rounded-2xl border border-[var(--aas-border)] object-cover"
|
|
85
|
+
/>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
</header>
|
|
89
|
+
|
|
90
|
+
<RelatedConcepts lang={lang} concepts={relatedConcepts} />
|
|
91
|
+
|
|
92
|
+
<div class="mt-8 flex items-start gap-8">
|
|
93
|
+
<article class="prose max-w-none aas-reading min-w-0 flex-1">
|
|
94
|
+
<Content />
|
|
95
|
+
</article>
|
|
96
|
+
<TocRail lang={lang} items={tocItems} />
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<RelatedTools lang={lang} slugs={conceptToolSlugs(entry)} />
|
|
100
|
+
<RelatedArticles lang={lang} articles={relatedArticles} />
|
|
101
|
+
|
|
102
|
+
{bodyHasMermaid && <MermaidLoader />}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
3
|
+
import ConceptCard from './ConceptCard.astro';
|
|
4
|
+
import Breadcrumb from './Breadcrumb.astro';
|
|
5
|
+
import ListControls from './ListControls.astro';
|
|
6
|
+
import ArrowUpRight from './ArrowUpRight.astro';
|
|
7
|
+
|
|
8
|
+
const PAGE = 12;
|
|
9
|
+
import { conceptTree, conceptCatOf } from '@aas-data/concept-categories';
|
|
10
|
+
import { getConcepts, conceptSlugOf, type ConceptEntry } from '../lib/concepts';
|
|
11
|
+
import { useTranslations, type Lang } from '../i18n/ui';
|
|
12
|
+
|
|
13
|
+
// Doubles as the concept index (no `categoryId` → all concepts, grouped by
|
|
14
|
+
// category) and a category browse page (`categoryId` → that category, with a
|
|
15
|
+
// breadcrumb, its direct concepts, and a section per subcategory).
|
|
16
|
+
interface Props {
|
|
17
|
+
lang: Lang;
|
|
18
|
+
categoryId?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { lang, categoryId } = Astro.props;
|
|
22
|
+
const t = useTranslations(lang);
|
|
23
|
+
// Default order is most-recently-updated first (matches the ListControls
|
|
24
|
+
// default), so the server render and the client sort agree — no reflow on load.
|
|
25
|
+
const all = (await getConcepts(lang)).sort(
|
|
26
|
+
(a, b) =>
|
|
27
|
+
(b.data.updated?.getTime() ?? 0) - (a.data.updated?.getTime() ?? 0) ||
|
|
28
|
+
a.data.title.localeCompare(b.data.title),
|
|
29
|
+
);
|
|
30
|
+
const catUrl = (cid: string) => getRelativeLocaleUrl(lang, `concept/category/${cid}/`);
|
|
31
|
+
|
|
32
|
+
const itemsIn = (cid: string): ConceptEntry[] => {
|
|
33
|
+
const ids = new Set(conceptTree.descendantIds(cid));
|
|
34
|
+
return all.filter((c) => ids.has(conceptCatOf(c.data.category)));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const node = categoryId ? conceptTree.map.get(categoryId) : undefined;
|
|
38
|
+
const sections = node ? conceptTree.childrenOf(node.id) : conceptTree.roots;
|
|
39
|
+
const direct = node ? all.filter((c) => conceptCatOf(c.data.category) === node.id) : [];
|
|
40
|
+
|
|
41
|
+
// A leaf category page shows one flat list — that grid gets ?page= pagination.
|
|
42
|
+
// Index and parent-category pages stay grouped overviews: each section is
|
|
43
|
+
// capped to PAGE cards with a "see all" link to the fuller list.
|
|
44
|
+
const isLeaf = !!node && sections.length === 0;
|
|
45
|
+
|
|
46
|
+
const path = node ? conceptTree.pathOf(node.id) : [];
|
|
47
|
+
const crumbs = node
|
|
48
|
+
? [
|
|
49
|
+
{ label: t('concept.backToConcepts'), href: getRelativeLocaleUrl(lang, 'concept/') },
|
|
50
|
+
...path.map((c, i) => ({
|
|
51
|
+
label: c.label[lang],
|
|
52
|
+
href: i === path.length - 1 ? undefined : catUrl(c.id),
|
|
53
|
+
})),
|
|
54
|
+
]
|
|
55
|
+
: [];
|
|
56
|
+
|
|
57
|
+
const card = (c: ConceptEntry) => ({
|
|
58
|
+
lang,
|
|
59
|
+
slug: conceptSlugOf(c),
|
|
60
|
+
title: c.data.title,
|
|
61
|
+
description: c.data.description,
|
|
62
|
+
tags: c.data.tags,
|
|
63
|
+
image: c.data.image,
|
|
64
|
+
imageAlt: c.data.imageAlt,
|
|
65
|
+
version: c.data.version,
|
|
66
|
+
updated: c.data.updated,
|
|
67
|
+
});
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
{
|
|
71
|
+
node ? (
|
|
72
|
+
<>
|
|
73
|
+
<Breadcrumb segments={crumbs} />
|
|
74
|
+
<header class="mt-4">
|
|
75
|
+
<h1 class="text-3xl font-bold tracking-tight">{node.label[lang]}</h1>
|
|
76
|
+
<p class="mt-2 max-w-2xl text-lg text-[var(--aas-muted)]">
|
|
77
|
+
{node.detail?.[lang] ?? node.description[lang]}
|
|
78
|
+
</p>
|
|
79
|
+
</header>
|
|
80
|
+
</>
|
|
81
|
+
) : (
|
|
82
|
+
<section class="py-6">
|
|
83
|
+
<h1 class="text-4xl font-bold tracking-tight">{t('concept.title')}</h1>
|
|
84
|
+
<p class="mt-3 max-w-2xl text-lg text-[var(--aas-muted)]">{t('concept.tagline')}</p>
|
|
85
|
+
</section>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
{all.length === 0 && <p class="mt-8 text-[var(--aas-muted)]">{t('concept.empty')}</p>}
|
|
90
|
+
|
|
91
|
+
{all.length > 0 && <div class="mt-6"><ListControls lang={lang} /></div>}
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
direct.length > 0 && (
|
|
95
|
+
<div
|
|
96
|
+
class="mt-4 grid gap-5 sm:grid-cols-2 lg:grid-cols-3"
|
|
97
|
+
data-sortgrid
|
|
98
|
+
data-paginate={isLeaf ? true : undefined}
|
|
99
|
+
data-page-size={isLeaf ? PAGE : undefined}
|
|
100
|
+
>
|
|
101
|
+
{direct.map((c) => (
|
|
102
|
+
<ConceptCard {...card(c)} />
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
sections.map((cat) => {
|
|
110
|
+
const items = itemsIn(cat.id);
|
|
111
|
+
return (
|
|
112
|
+
items.length > 0 && (
|
|
113
|
+
<section class="mt-8">
|
|
114
|
+
<h2 class="text-2xl font-semibold">
|
|
115
|
+
<a
|
|
116
|
+
href={catUrl(cat.id)}
|
|
117
|
+
class="text-[var(--aas-text)] no-underline hover:text-[var(--aas-accent)]"
|
|
118
|
+
>
|
|
119
|
+
{cat.label[lang]}
|
|
120
|
+
<ArrowUpRight size={20} class="ml-1 text-[var(--aas-muted)]" />
|
|
121
|
+
</a>
|
|
122
|
+
</h2>
|
|
123
|
+
<p class="mt-1 text-sm text-[var(--aas-muted)]">{cat.description[lang]}</p>
|
|
124
|
+
<div class="mt-4 grid gap-5 sm:grid-cols-2 lg:grid-cols-3" data-sortgrid>
|
|
125
|
+
{items.slice(0, PAGE).map((c) => (
|
|
126
|
+
<ConceptCard {...card(c)} />
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
{items.length > PAGE && (
|
|
130
|
+
<a
|
|
131
|
+
href={catUrl(cat.id)}
|
|
132
|
+
class="mt-3 inline-block text-sm font-medium text-[var(--aas-accent)] no-underline"
|
|
133
|
+
>
|
|
134
|
+
{t('list.seeAll').replace('{n}', String(items.length))} →
|
|
135
|
+
</a>
|
|
136
|
+
)}
|
|
137
|
+
</section>
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
{isLeaf && <nav data-pager aria-label="Pagination" class="mt-8 flex flex-wrap items-center justify-center gap-1.5" />}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
// An inline, full-width callout linking to another concept, for use inside a
|
|
3
|
+
// concept's MDX body, e.g.
|
|
4
|
+
// <ConceptLink slug="agent-tools" />
|
|
5
|
+
// Visually distinct from the <Tools/> mini-cards: an accent-tinted bar with the
|
|
6
|
+
// target concept's title + description and a → affordance. Locale is resolved
|
|
7
|
+
// from the current page (concepts are served under /<lang>/...).
|
|
8
|
+
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
9
|
+
import { getConcepts, conceptSlugOf, type ConceptEntry } from '../lib/concepts';
|
|
10
|
+
import { useTranslations, type Lang } from '../i18n/ui';
|
|
11
|
+
import { inlineMd } from '../lib/inline-md';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
slug: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { slug } = Astro.props;
|
|
18
|
+
const lang: Lang = Astro.currentLocale === 'ko' ? 'ko' : 'en';
|
|
19
|
+
const t = useTranslations(lang);
|
|
20
|
+
const concepts = await getConcepts(lang);
|
|
21
|
+
const target = concepts.find((c) => conceptSlugOf(c) === slug) as ConceptEntry | undefined;
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
target && (
|
|
26
|
+
<a
|
|
27
|
+
href={getRelativeLocaleUrl(lang, `concept/${conceptSlugOf(target)}/`)}
|
|
28
|
+
class="aas-lift mt-3 flex items-center gap-4 rounded-xl border p-4 no-underline"
|
|
29
|
+
style="background: color-mix(in srgb, var(--aas-accent) 7%, var(--aas-panel)); border-color: color-mix(in srgb, var(--aas-accent) 30%, transparent);"
|
|
30
|
+
>
|
|
31
|
+
<div class="min-w-0 flex-1">
|
|
32
|
+
<span class="block text-xs font-semibold tracking-wide text-[var(--aas-accent)] uppercase">
|
|
33
|
+
{t('concept.learnMore')}
|
|
34
|
+
</span>
|
|
35
|
+
<span class="mt-0.5 block font-semibold text-[var(--aas-text)]">{target.data.title}</span>
|
|
36
|
+
<span class="aas-md mt-1 block text-sm text-[var(--aas-muted)]" set:html={inlineMd(target.data.description)} />
|
|
37
|
+
</div>
|
|
38
|
+
<span aria-hidden="true" class="shrink-0 text-xl text-[var(--aas-accent)]">
|
|
39
|
+
→
|
|
40
|
+
</span>
|
|
41
|
+
</a>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Inline tool cards for use inside a concept's MDX body, e.g.
|
|
3
|
+
// <Tools slugs={["langgraph", "crewai"]} />
|
|
4
|
+
// Renders the same mini-cards as the catalog's "related tools", resolving the
|
|
5
|
+
// locale from the current page (concepts are served under /<lang>/...).
|
|
6
|
+
import { getRelativeLocaleUrl } from 'astro:i18n';
|
|
7
|
+
import { getStacks, slugOf, type StackEntry } from '../lib/stacks';
|
|
8
|
+
import { categoryMap } from '@aas-data/categories';
|
|
9
|
+
import ToolAvatar from './ToolAvatar.astro';
|
|
10
|
+
import type { Lang } from '../i18n/ui';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
slugs: string[] | string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { slugs } = Astro.props;
|
|
17
|
+
const list = Array.isArray(slugs)
|
|
18
|
+
? slugs
|
|
19
|
+
: String(slugs)
|
|
20
|
+
.split(',')
|
|
21
|
+
.map((s) => s.trim())
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
|
|
24
|
+
const lang: Lang = Astro.currentLocale === 'ko' ? 'ko' : 'en';
|
|
25
|
+
const stacks = await getStacks(lang);
|
|
26
|
+
const bySlug = new Map(stacks.map((s) => [slugOf(s), s]));
|
|
27
|
+
const tools = list.map((s) => bySlug.get(s)).filter(Boolean) as StackEntry[];
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
tools.length > 0 && (
|
|
32
|
+
<div class="mt-3 grid gap-3 sm:grid-cols-2">
|
|
33
|
+
{tools.map((e) => (
|
|
34
|
+
<a
|
|
35
|
+
href={getRelativeLocaleUrl(lang, `stack/${slugOf(e)}/`)}
|
|
36
|
+
class="aas-lift flex items-center gap-3 rounded-xl border border-[var(--aas-border)] bg-[var(--aas-panel)] p-3 no-underline"
|
|
37
|
+
>
|
|
38
|
+
<ToolAvatar name={e.data.name} logo={e.data.logo} logoDark={e.data.logoDark} category={e.data.category} size={36} />
|
|
39
|
+
<div class="min-w-0">
|
|
40
|
+
<span class="block truncate font-semibold text-[var(--aas-text)]">{e.data.name}</span>
|
|
41
|
+
<span class="block truncate text-xs text-[var(--aas-muted)]">
|
|
42
|
+
{categoryMap.get(e.data.category)?.label[lang]}
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
</a>
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|