@grove-dev/astro 0.2.6 → 0.2.13
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/dist/lib/display.d.ts +11 -17
- package/dist/lib/display.d.ts.map +1 -1
- package/dist/lib/display.js +43 -43
- package/dist/lib/display.js.map +1 -1
- package/dist/lib/format.d.ts +6 -7
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +11 -12
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/lenses.d.ts +35 -73
- package/dist/lib/lenses.d.ts.map +1 -1
- package/dist/lib/lenses.js +144 -126
- package/dist/lib/lenses.js.map +1 -1
- package/dist/lib/repo.d.ts +11 -22
- package/dist/lib/repo.d.ts.map +1 -1
- package/dist/lib/repo.js +12 -25
- package/dist/lib/repo.js.map +1 -1
- package/dist/lib/scores.d.ts +15 -16
- package/dist/lib/scores.d.ts.map +1 -1
- package/dist/lib/scores.js +4 -11
- package/dist/lib/scores.js.map +1 -1
- package/dist/lib/search.d.ts +47 -129
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +195 -180
- package/dist/lib/search.js.map +1 -1
- package/dist/lib/taxonomy-counts.d.ts +7 -21
- package/dist/lib/taxonomy-counts.d.ts.map +1 -1
- package/dist/lib/taxonomy-counts.js +12 -31
- package/dist/lib/taxonomy-counts.js.map +1 -1
- package/package.json +4 -4
- package/src/components/AppsIndexRow.astro +55 -184
- package/src/components/AppsPagination.astro +45 -108
- package/src/components/CategoryGrid.astro +43 -31
- package/src/components/ContributorsGrid.astro +3 -3
- package/src/components/CurationGrid.astro +41 -0
- package/src/components/ExploreByStack.astro +6 -0
- package/src/components/FilterGroupMenu.astro +49 -0
- package/src/components/FilterOptions.astro +44 -0
- package/src/components/Hero.astro +198 -147
- package/src/components/Icon.astro +10 -3
- package/src/components/ItemCard.astro +266 -248
- package/src/components/RecordSection.astro +68 -33
- package/src/components/RefinePanel.astro +115 -141
- package/src/components/StackGrid.astro +32 -24
- package/src/components/WhyThisExists.astro +32 -32
- package/src/layouts/BaseLayout.astro +104 -49
- package/src/layouts/Container.astro +5 -44
- package/src/layouts/Footer.astro +31 -20
- package/src/layouts/Header.astro +40 -19
- package/src/layouts/SectionHeader.astro +27 -43
- package/src/layouts/Seo.astro +38 -56
- package/src/layouts/ThemeToggle.astro +35 -48
- package/src/lib/display.ts +46 -53
- package/src/lib/format.ts +11 -12
- package/src/lib/lenses.ts +151 -172
- package/src/lib/repo.ts +16 -28
- package/src/lib/scores.ts +15 -19
- package/src/lib/search.ts +222 -290
- package/src/lib/taxonomy-counts.ts +12 -46
- package/src/styles.css +207 -1
- package/templates/default/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/templates/default/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/templates/default/.github/ISSUE_TEMPLATE/record_submission.md +48 -0
- package/templates/default/.github/pull_request_template.md +27 -0
- package/templates/default/.github/workflows/build.yml +55 -0
- package/templates/default/.github/workflows/cleanup-stale-records.yml +48 -0
- package/templates/default/.github/workflows/daily-refresh.yml +53 -0
- package/templates/default/.github/workflows/sync-contributors.yml +53 -0
- package/templates/default/.github/workflows/sync-github-metadata.yml +58 -0
- package/templates/default/.github/workflows/validate-data.yml +35 -0
- package/templates/default/data/records/coolify.yml +46 -0
- package/templates/default/data/records/gitea.yml +46 -0
- package/templates/default/data/records/hoppscotch.yml +47 -0
- package/templates/default/data/records/inventree.yml +47 -0
- package/templates/default/data/records/label-studio.yml +46 -0
- package/templates/default/data/records/logseq.yml +48 -0
- package/templates/default/data/records/private-gpt.yml +47 -0
- package/templates/default/data/records/wakapi.yml +44 -0
- package/templates/default/data/taxonomy/categories.yml +47 -0
- package/templates/default/data/taxonomy/distribution-channels.yml +43 -0
- package/templates/default/data/taxonomy/platforms.yml +27 -0
- package/templates/default/data/taxonomy/stacks.yml +41 -0
- package/templates/default/grove.config.ts +1 -1
- package/templates/default/package.json +5 -4
- package/templates/default/public/icons/stacks/bun.svg +1 -0
- package/templates/default/public/icons/stacks/clojure.svg +1 -0
- package/templates/default/public/icons/stacks/clojurescript.svg +1 -0
- package/templates/default/public/icons/stacks/deno.svg +1 -0
- package/templates/default/public/icons/stacks/django.svg +1 -0
- package/templates/default/public/icons/stacks/docker.svg +1 -0
- package/templates/default/public/icons/stacks/go.svg +1 -0
- package/templates/default/public/icons/stacks/llm.svg +1 -0
- package/templates/default/public/icons/stacks/node.js.svg +1 -0
- package/templates/default/public/icons/stacks/sveltekit.svg +1 -0
- package/templates/default/public/icons/stacks/vue.svg +1 -0
- package/templates/default/public/llms-full.txt +197 -5
- package/templates/default/public/llms.txt +107 -6
- package/templates/default/public/sitemap.xml +63 -0
- package/templates/default/scripts/build-llms.mjs +123 -0
- package/templates/default/scripts/build-records-json.mjs +27 -0
- package/templates/default/scripts/build-sitemap.mjs +20 -0
- package/templates/default/scripts/cleanup-stale-records.mjs +20 -0
- package/templates/default/scripts/enrich-github-metadata.mjs +99 -0
- package/templates/default/scripts/fetch-icons.mjs +137 -0
- package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +86 -0
- package/templates/default/scripts/parse-legacy-readme.mjs +59 -0
- package/templates/default/scripts/refresh-records-activity.mjs +24 -0
- package/templates/default/scripts/repair-license-format.mjs +72 -0
- package/templates/default/scripts/report-cleanup-candidates.mjs +21 -0
- package/templates/default/scripts/seed-from-github.mjs +62 -0
- package/templates/default/scripts/sync-contributors.mjs +145 -0
- package/templates/default/scripts/sync-github-metadata.mjs +28 -0
- package/templates/default/scripts/validate-records.mjs +27 -0
- package/templates/default/src/data/records.ts +134 -51
- package/templates/default/src/pages/404.astro +64 -0
- package/templates/default/src/pages/{projects/[slug].astro → [slug]/[itemSlug].astro} +236 -66
- package/templates/default/src/pages/[slug]/index.astro +476 -0
- package/templates/default/src/pages/about.astro +165 -82
- package/templates/default/src/pages/apps/[itemSlug].astro +301 -0
- package/templates/default/src/pages/apps/index.astro +146 -0
- package/templates/default/src/pages/contributors.astro +184 -0
- package/templates/default/src/pages/index.astro +119 -162
- package/templates/default/src/pages/submit.astro +276 -114
- package/templates/default/wrangler.jsonc +28 -0
- package/LICENSE +0 -21
- package/templates/default/data/generated/apps.json +0 -1
- package/templates/default/data/generated/records.full.json +0 -8
- package/templates/default/data/generated/records.index.json +0 -7
- package/templates/default/data/generated/records.json +0 -8
- package/templates/default/data/generated/repo-stats.json +0 -1
- package/templates/default/data/generated/site-config.json +0 -31
- package/templates/default/src/components/layout/BaseLayout.astro +0 -47
- package/templates/default/src/components/layout/Container.astro +0 -18
- package/templates/default/src/components/layout/Footer.astro +0 -27
- package/templates/default/src/components/layout/Header.astro +0 -43
- package/templates/default/src/lib/markdown.ts +0 -171
- package/templates/default/src/pages/projects/index.astro +0 -193
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* WhyThisExists — the "what is this site for?" section.
|
|
4
4
|
*
|
|
5
5
|
* Renders 3 short, opinionated points (icon + title + body). The
|
|
6
|
-
* icon set is small and generic: "
|
|
6
|
+
* icon set is small and generic: "item" (window/grid glyph),
|
|
7
7
|
* "code" (angle brackets), "search" (magnifier). Defaults to
|
|
8
8
|
* the canonical "directory, source, search" trio.
|
|
9
9
|
*
|
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
interface WhyPoint {
|
|
15
15
|
title: string;
|
|
16
16
|
body: string;
|
|
17
|
-
icon: "
|
|
17
|
+
icon: "item" | "code" | "search";
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
interface Props {
|
|
21
21
|
points?: WhyPoint[];
|
|
22
|
+
eyebrow?: string;
|
|
22
23
|
title?: string;
|
|
23
24
|
description?: string;
|
|
24
25
|
class?: string;
|
|
@@ -26,26 +27,27 @@ interface Props {
|
|
|
26
27
|
|
|
27
28
|
const {
|
|
28
29
|
points,
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
eyebrow = "Why this exists",
|
|
31
|
+
title = "Most directory discovery is fragmented.",
|
|
32
|
+
description = "Lists are useful, but they often become long, static pages without enough context to compare what is real, maintained, and worth exploring.",
|
|
31
33
|
class: className = "",
|
|
32
34
|
} = Astro.props;
|
|
33
35
|
|
|
34
36
|
const defaultPoints: WhyPoint[] = [
|
|
35
37
|
{
|
|
36
|
-
icon: "
|
|
37
|
-
title: "
|
|
38
|
-
body: "
|
|
38
|
+
icon: "item",
|
|
39
|
+
title: "Real projects",
|
|
40
|
+
body: "Not SDKs, templates, or tiny demos. Focused on complete projects you can actually run.",
|
|
39
41
|
},
|
|
40
42
|
{
|
|
41
43
|
icon: "code",
|
|
42
|
-
title: "
|
|
43
|
-
body: "
|
|
44
|
+
title: "Codebase context",
|
|
45
|
+
body: "Stack, platform, license, activity, maturity, and contribution signals in one place.",
|
|
44
46
|
},
|
|
45
47
|
{
|
|
46
48
|
icon: "search",
|
|
47
|
-
title: "
|
|
48
|
-
body: "
|
|
49
|
+
title: "Searchable discovery",
|
|
50
|
+
body: "Browse by category, stack, platform, stars, recency, and status instead of scanning a flat list.",
|
|
49
51
|
},
|
|
50
52
|
];
|
|
51
53
|
|
|
@@ -53,35 +55,39 @@ const items = points && points.length > 0 ? points : defaultPoints;
|
|
|
53
55
|
---
|
|
54
56
|
|
|
55
57
|
<section
|
|
56
|
-
|
|
58
|
+
id="about"
|
|
59
|
+
class:list={["grove-section scroll-mt-20 border-b border-ink-200 dark:border-ink-800", className]}
|
|
57
60
|
aria-labelledby="grove-why-heading"
|
|
58
61
|
>
|
|
59
62
|
<div class="grove-container mx-auto px-5 sm:px-7">
|
|
60
|
-
<header class="
|
|
63
|
+
<header class="flex flex-col gap-2">
|
|
64
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
65
|
+
{eyebrow}
|
|
66
|
+
</span>
|
|
61
67
|
<h2
|
|
62
68
|
id="grove-why-heading"
|
|
63
|
-
class="m-0 text-[1.5rem] sm:text-[1.
|
|
69
|
+
class="m-0 max-w-[40ch] text-[1.5rem] sm:text-[1.75rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
|
|
64
70
|
>
|
|
65
71
|
{title}
|
|
66
72
|
</h2>
|
|
67
73
|
{description && (
|
|
68
|
-
<p class="m-0 max-w-
|
|
74
|
+
<p class="m-0 max-w-[64ch] text-[0.9375rem] leading-relaxed text-ink-500 dark:text-ink-400">
|
|
69
75
|
{description}
|
|
70
76
|
</p>
|
|
71
77
|
)}
|
|
72
78
|
</header>
|
|
73
79
|
|
|
74
|
-
<
|
|
75
|
-
class="m-0 grid list-none grid-cols-1 gap-
|
|
80
|
+
<ul
|
|
81
|
+
class="m-0 mt-7 grid list-none grid-cols-1 gap-3 p-0 sm:grid-cols-3"
|
|
76
82
|
aria-label="Why this directory"
|
|
77
83
|
>
|
|
78
|
-
{items.map((p
|
|
79
|
-
<li class="
|
|
80
|
-
<
|
|
84
|
+
{items.map((p) => (
|
|
85
|
+
<li class="grove-card p-4">
|
|
86
|
+
<div
|
|
81
87
|
aria-hidden="true"
|
|
82
|
-
class="
|
|
88
|
+
class="mb-3 flex h-8 w-8 items-center justify-center rounded-md border border-ink-200 bg-white text-ink-700 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-300"
|
|
83
89
|
>
|
|
84
|
-
{p.icon === "
|
|
90
|
+
{p.icon === "item" && (
|
|
85
91
|
<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
|
86
92
|
<path d="M1.75 3.5a.25.25 0 0 1 .25-.25h12a.25.25 0 0 1 .25.25v8a.25.25 0 0 1-.25.25h-12a.25.25 0 0 1-.25-.25v-8ZM2 4.75v6h12v-6H2ZM2.5 6.5h4v2.5h-4V6.5Z"></path>
|
|
87
93
|
</svg>
|
|
@@ -96,21 +102,15 @@ const items = points && points.length > 0 ? points : defaultPoints;
|
|
|
96
102
|
<path d="M10.68 11.74a6 6 0 1 1 1.06-1.06l3.04 3.04a.75.75 0 1 1-1.06 1.06l-3.04-3.04ZM11.5 7a4.5 4.5 0 1 0-9 0 4.5 4.5 0 0 0 9 0Z"></path>
|
|
97
103
|
</svg>
|
|
98
104
|
)}
|
|
99
|
-
</
|
|
100
|
-
<
|
|
101
|
-
aria-hidden="true"
|
|
102
|
-
class="font-mono text-2xs text-ink-400 dark:text-ink-500"
|
|
103
|
-
>
|
|
104
|
-
0{i + 1}
|
|
105
|
-
</span>
|
|
106
|
-
<h3 class="m-0 text-base font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
105
|
+
</div>
|
|
106
|
+
<h3 class="m-0 text-[0.9375rem] font-semibold text-ink-900 dark:text-ink-100">
|
|
107
107
|
{p.title}
|
|
108
108
|
</h3>
|
|
109
|
-
<p class="m-0
|
|
109
|
+
<p class="m-0 mt-1.5 text-[0.8125rem] leading-relaxed text-ink-500 dark:text-ink-400">
|
|
110
110
|
{p.body}
|
|
111
111
|
</p>
|
|
112
112
|
</li>
|
|
113
113
|
))}
|
|
114
|
-
</
|
|
114
|
+
</ul>
|
|
115
115
|
</div>
|
|
116
116
|
</section>
|
|
@@ -2,19 +2,18 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* BaseLayout — wraps every page. Owns the theme-detection inline
|
|
4
4
|
* script and the global shell (Header, Footer). Per-page SEO
|
|
5
|
-
* (title, description, OG, JSON-LD) is delegated to
|
|
6
|
-
* page can declare what it needs without polluting the layout
|
|
5
|
+
* (title, description, OG, JSON-LD) is delegated to `<Seo />` so
|
|
6
|
+
* each page can declare what it needs without polluting the layout
|
|
7
|
+
* file.
|
|
7
8
|
*
|
|
8
|
-
* Generic: takes a `site` prop with brand + nav
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Generic: takes a `site` prop with brand + nav + stats. No
|
|
10
|
+
* `siteConfig` import — consumers compose the props from their
|
|
11
|
+
* own config (typically `data/generated/site-config.json` for the
|
|
12
|
+
* showcase).
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `ThemeToggle.astro` uses.
|
|
14
|
+
* Optional `gaId` adds a GA4 measurement snippet. Omit for
|
|
15
|
+
* privacy-first deployments.
|
|
16
16
|
*/
|
|
17
|
-
|
|
18
17
|
import "../styles.css";
|
|
19
18
|
import Header from "./Header.astro";
|
|
20
19
|
import Footer from "./Footer.astro";
|
|
@@ -25,6 +24,18 @@ interface NavItem {
|
|
|
25
24
|
href: string;
|
|
26
25
|
}
|
|
27
26
|
|
|
27
|
+
interface SiteStats {
|
|
28
|
+
totalApps?: number;
|
|
29
|
+
totalCategories?: number;
|
|
30
|
+
totalStacks?: number;
|
|
31
|
+
totalOwners?: number;
|
|
32
|
+
totalStars?: number;
|
|
33
|
+
originalRepo?: string;
|
|
34
|
+
originalStars?: number;
|
|
35
|
+
originalForks?: number;
|
|
36
|
+
originalContributors?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
28
39
|
interface Site {
|
|
29
40
|
name: string;
|
|
30
41
|
tagline?: string;
|
|
@@ -32,6 +43,15 @@ interface Site {
|
|
|
32
43
|
nav: NavItem[];
|
|
33
44
|
repoUrl?: string;
|
|
34
45
|
url?: string;
|
|
46
|
+
/** Optional directory-level stats — populated from site-config.json
|
|
47
|
+
* at generate time. Components use it to render hero counts,
|
|
48
|
+
* origin card stats, and footer summary lines. */
|
|
49
|
+
stats?: SiteStats;
|
|
50
|
+
blueprintConfig?: {
|
|
51
|
+
routeSlug?: string;
|
|
52
|
+
labelSingular?: string;
|
|
53
|
+
labelPlural?: string;
|
|
54
|
+
};
|
|
35
55
|
}
|
|
36
56
|
|
|
37
57
|
interface Props {
|
|
@@ -43,7 +63,7 @@ interface Props {
|
|
|
43
63
|
jsonLd?: Record<string, unknown> | Record<string, unknown>[];
|
|
44
64
|
/** Optional Google Analytics 4 Measurement ID (e.g. "G-XXXXXX"). */
|
|
45
65
|
gaId?: string;
|
|
46
|
-
/** Path used for JSON-LD `SearchAction` target. Defaults to "/
|
|
66
|
+
/** Path used for JSON-LD `SearchAction` target. Defaults to "/items". */
|
|
47
67
|
searchPath?: string;
|
|
48
68
|
/** Submit button label rendered in the header. Omit to hide. */
|
|
49
69
|
submitLabel?: string;
|
|
@@ -63,6 +83,8 @@ const {
|
|
|
63
83
|
submitLabel,
|
|
64
84
|
submitHref,
|
|
65
85
|
} = Astro.props;
|
|
86
|
+
const directoryPath =
|
|
87
|
+
searchPath ?? `/${site.blueprintConfig?.routeSlug ?? "items"}`;
|
|
66
88
|
|
|
67
89
|
const favicon =
|
|
68
90
|
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%2318181b'/%3E%3C/svg%3E";
|
|
@@ -87,11 +109,6 @@ const favicon =
|
|
|
87
109
|
|
|
88
110
|
{gaId && (
|
|
89
111
|
<>
|
|
90
|
-
{/* GA4 — gtag.js loader. We send the initial page_view with
|
|
91
|
-
the current path + query string so the dashboard
|
|
92
|
-
distinguishes /projects?stack=flutter from
|
|
93
|
-
/projects?stack=react (otherwise they'd all collapse to
|
|
94
|
-
/projects). */}
|
|
95
112
|
<script
|
|
96
113
|
is:inline
|
|
97
114
|
async
|
|
@@ -118,7 +135,7 @@ const favicon =
|
|
|
118
135
|
noindex={noindex}
|
|
119
136
|
jsonLd={jsonLd}
|
|
120
137
|
site={site}
|
|
121
|
-
searchPath={
|
|
138
|
+
searchPath={directoryPath}
|
|
122
139
|
/>
|
|
123
140
|
|
|
124
141
|
<slot name="head" />
|
|
@@ -168,8 +185,8 @@ const favicon =
|
|
|
168
185
|
<slot name="header">
|
|
169
186
|
<Header
|
|
170
187
|
site={site}
|
|
171
|
-
submitLabel={submitLabel}
|
|
172
|
-
submitHref={submitHref}
|
|
188
|
+
submitLabel={submitLabel ?? (site.blueprintConfig ? `Submit ${site.blueprintConfig.labelSingular ?? "item"}` : undefined)}
|
|
189
|
+
submitHref={submitHref ?? (site.blueprintConfig ? "/submit" : undefined)}
|
|
173
190
|
/>
|
|
174
191
|
</slot>
|
|
175
192
|
|
|
@@ -178,17 +195,80 @@ const favicon =
|
|
|
178
195
|
</main>
|
|
179
196
|
|
|
180
197
|
<slot name="footer">
|
|
181
|
-
<Footer
|
|
198
|
+
<Footer
|
|
199
|
+
site={site}
|
|
200
|
+
stats={{
|
|
201
|
+
totalApps: site.stats?.totalApps,
|
|
202
|
+
totalCategories: site.stats?.totalCategories,
|
|
203
|
+
totalStacks: site.stats?.totalStacks,
|
|
204
|
+
totalOwners: site.stats?.totalOwners,
|
|
205
|
+
}}
|
|
206
|
+
itemsLabel={site.blueprintConfig?.labelPlural ?? "items"}
|
|
207
|
+
discover={{
|
|
208
|
+
heading: "Discover",
|
|
209
|
+
items: [
|
|
210
|
+
...(site.stats?.totalApps
|
|
211
|
+
? [{ label: "Browse all", href: directoryPath }]
|
|
212
|
+
: []),
|
|
213
|
+
{ label: "Contributors", href: "/contributors" },
|
|
214
|
+
...(site.stats?.originalRepo
|
|
215
|
+
? [{ label: "View on GitHub", href: site.stats.originalRepo, external: true }]
|
|
216
|
+
: []),
|
|
217
|
+
],
|
|
218
|
+
}}
|
|
219
|
+
contribute={{
|
|
220
|
+
heading: "Contribute",
|
|
221
|
+
items: [
|
|
222
|
+
...(site.repoUrl
|
|
223
|
+
? [
|
|
224
|
+
{
|
|
225
|
+
label: "Submit via GitHub",
|
|
226
|
+
href: `${site.repoUrl}/blob/main/CONTRIBUTING.md`,
|
|
227
|
+
external: true,
|
|
228
|
+
},
|
|
229
|
+
]
|
|
230
|
+
: []),
|
|
231
|
+
...(site.repoUrl
|
|
232
|
+
? [
|
|
233
|
+
{
|
|
234
|
+
label: "Report an issue",
|
|
235
|
+
href: `${site.repoUrl}/issues`,
|
|
236
|
+
external: true,
|
|
237
|
+
},
|
|
238
|
+
]
|
|
239
|
+
: []),
|
|
240
|
+
],
|
|
241
|
+
}}
|
|
242
|
+
project={{
|
|
243
|
+
heading: "Project",
|
|
244
|
+
items: [
|
|
245
|
+
...(site.repoUrl
|
|
246
|
+
? [
|
|
247
|
+
{
|
|
248
|
+
label: "Source on GitHub",
|
|
249
|
+
href: site.repoUrl,
|
|
250
|
+
external: true,
|
|
251
|
+
},
|
|
252
|
+
]
|
|
253
|
+
: []),
|
|
254
|
+
...(site.repoUrl
|
|
255
|
+
? [
|
|
256
|
+
{
|
|
257
|
+
label: "Changelog",
|
|
258
|
+
href: `${site.repoUrl}/blob/main/CHANGELOG.md`,
|
|
259
|
+
external: true,
|
|
260
|
+
},
|
|
261
|
+
]
|
|
262
|
+
: []),
|
|
263
|
+
],
|
|
264
|
+
}}
|
|
265
|
+
/>
|
|
182
266
|
</slot>
|
|
183
267
|
|
|
184
268
|
<slot name="body-end" />
|
|
185
269
|
|
|
186
270
|
{gaId && (
|
|
187
271
|
<script is:inline define:vars={{ gaId }}>
|
|
188
|
-
// Mirror in-page SPA navigation (history.pushState /
|
|
189
|
-
// replaceState) into GA4 page_view events so filter changes
|
|
190
|
-
// on /projects show up as separate page views in the
|
|
191
|
-
// dashboard instead of one inflated session.
|
|
192
272
|
(function () {
|
|
193
273
|
var last = location.pathname + location.search;
|
|
194
274
|
function report() {
|
|
@@ -215,30 +295,5 @@ const favicon =
|
|
|
215
295
|
})();
|
|
216
296
|
</script>
|
|
217
297
|
)}
|
|
218
|
-
|
|
219
|
-
{gaId && (
|
|
220
|
-
<script is:inline>
|
|
221
|
-
// Delegated click handler for `data-event` elements. We do
|
|
222
|
-
// this in one place so individual components just emit data
|
|
223
|
-
// attributes and stay declarative.
|
|
224
|
-
(function () {
|
|
225
|
-
document.addEventListener("click", function (e) {
|
|
226
|
-
var el = e.target instanceof Element
|
|
227
|
-
? e.target.closest("[data-event]")
|
|
228
|
-
: null;
|
|
229
|
-
if (!el) return;
|
|
230
|
-
var name = el.getAttribute("data-event");
|
|
231
|
-
if (!name) return;
|
|
232
|
-
var params = {
|
|
233
|
-
source: el.getAttribute("data-event-source") || undefined,
|
|
234
|
-
app_slug: el.getAttribute("data-event-app") || undefined,
|
|
235
|
-
channel: el.getAttribute("data-event-channel") || undefined,
|
|
236
|
-
link_url: el.getAttribute("href") || undefined,
|
|
237
|
-
};
|
|
238
|
-
window.gtag("event", name, params);
|
|
239
|
-
});
|
|
240
|
-
})();
|
|
241
|
-
</script>
|
|
242
|
-
)}
|
|
243
298
|
</body>
|
|
244
299
|
</html>
|
|
@@ -1,57 +1,18 @@
|
|
|
1
1
|
---
|
|
2
|
-
/**
|
|
3
|
-
* Container — page-width wrapper.
|
|
4
|
-
*
|
|
5
|
-
* Centralizes the three layout widths the showcase uses:
|
|
6
|
-
*
|
|
7
|
-
* - "default" → 1140px (the standard reading width)
|
|
8
|
-
* - "wide" → 1400px (home, full-bleed grid sections)
|
|
9
|
-
* - "narrow" → 720px (about, methodology, content-heavy pages)
|
|
10
|
-
*
|
|
11
|
-
* Pure layout, no styling dependencies beyond Tailwind's `max-w-*`
|
|
12
|
-
* tokens defined in `@grove-dev/astro/styles.css`.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
2
|
interface Props {
|
|
16
3
|
size?: "default" | "wide" | "narrow";
|
|
17
4
|
class?: string;
|
|
18
|
-
/** Padding preset. Defaults to a comfortable 1.25rem / 1.75rem. */
|
|
19
|
-
padding?: "default" | "none" | "tight";
|
|
20
5
|
}
|
|
21
6
|
|
|
22
|
-
const {
|
|
23
|
-
size = "default",
|
|
24
|
-
class: className = "",
|
|
25
|
-
padding = "default",
|
|
26
|
-
} = Astro.props;
|
|
27
|
-
|
|
7
|
+
const { size = "default", class: className = "" } = Astro.props;
|
|
28
8
|
const sizeClass =
|
|
29
9
|
size === "wide"
|
|
30
|
-
? "grove-container-wide"
|
|
10
|
+
? "grove-container-wide max-w-wide"
|
|
31
11
|
: size === "narrow"
|
|
32
|
-
? "grove-container-narrow"
|
|
33
|
-
: "grove-container";
|
|
34
|
-
|
|
35
|
-
const paddingClass =
|
|
36
|
-
padding === "none"
|
|
37
|
-
? ""
|
|
38
|
-
: padding === "tight"
|
|
39
|
-
? "px-4 sm:px-5"
|
|
40
|
-
: "px-5 sm:px-7";
|
|
12
|
+
? "grove-container-narrow max-w-narrow"
|
|
13
|
+
: "grove-container max-w-container";
|
|
41
14
|
---
|
|
42
15
|
|
|
43
|
-
<div class
|
|
16
|
+
<div class={`${sizeClass} mx-auto px-5 sm:px-7 ${className}`}>
|
|
44
17
|
<slot />
|
|
45
18
|
</div>
|
|
46
|
-
|
|
47
|
-
<style>
|
|
48
|
-
.grove-container {
|
|
49
|
-
max-width: var(--container-container, 1140px);
|
|
50
|
-
}
|
|
51
|
-
.grove-container-wide {
|
|
52
|
-
max-width: var(--container-wide, 1400px);
|
|
53
|
-
}
|
|
54
|
-
.grove-container-narrow {
|
|
55
|
-
max-width: var(--container-narrow, 720px);
|
|
56
|
-
}
|
|
57
|
-
</style>
|
package/src/layouts/Footer.astro
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* Footer — 4-column grid (brand + Discover + Contribute + Project) plus
|
|
4
4
|
* a thin bar at the bottom with copyright + repo link.
|
|
5
5
|
*
|
|
6
|
-
* Generic: takes a `site` prop with brand + repoUrl
|
|
7
|
-
*
|
|
6
|
+
* Generic: takes a `site` prop with brand + repoUrl + stats, plus
|
|
7
|
+
* pre-computed column data. No `siteConfig` import — consumers
|
|
8
|
+
* compose props from their own config (or from
|
|
9
|
+
* `data/generated/site-config.json` for the showcase).
|
|
8
10
|
*
|
|
9
11
|
* Each column's links are passed in as a `{ heading, items[] }` shape
|
|
10
|
-
* so consumers can customize the order and copy. Defaults are
|
|
11
|
-
*
|
|
12
|
-
* link data is supplied.
|
|
12
|
+
* so consumers can customize the order and copy. Defaults are
|
|
13
|
+
* provided so the footer still renders if no extra data is supplied.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
interface NavItem {
|
|
@@ -29,6 +30,12 @@ interface Site {
|
|
|
29
30
|
tagline?: string;
|
|
30
31
|
description?: string;
|
|
31
32
|
repoUrl?: string;
|
|
33
|
+
stats?: {
|
|
34
|
+
totalApps?: number;
|
|
35
|
+
totalCategories?: number;
|
|
36
|
+
totalStacks?: number;
|
|
37
|
+
totalOwners?: number;
|
|
38
|
+
};
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
interface Props {
|
|
@@ -39,8 +46,13 @@ interface Props {
|
|
|
39
46
|
contribute?: FooterColumn;
|
|
40
47
|
/** Project links — third content column. */
|
|
41
48
|
project?: FooterColumn;
|
|
42
|
-
/** Stats line under brand blurb
|
|
43
|
-
stats?: {
|
|
49
|
+
/** Stats line under brand blurb. */
|
|
50
|
+
stats?: { totalApps?: number; totalCategories?: number; totalOwners?: number };
|
|
51
|
+
/**
|
|
52
|
+
* Plural label for the primary count stat. Default "items" so the
|
|
53
|
+
* footer reads naturally regardless of blueprint.
|
|
54
|
+
*/
|
|
55
|
+
itemsLabel?: string;
|
|
44
56
|
/** Copyright suffix. Defaults to site.name. */
|
|
45
57
|
copyrightHolder?: string;
|
|
46
58
|
/** License line under the bar (e.g. "Content under CC BY-SA 4.0."). */
|
|
@@ -55,6 +67,7 @@ const {
|
|
|
55
67
|
contribute = { heading: "Contribute", items: [] },
|
|
56
68
|
project = { heading: "Project", items: [] },
|
|
57
69
|
stats,
|
|
70
|
+
itemsLabel = "items",
|
|
58
71
|
copyrightHolder,
|
|
59
72
|
licenseLine,
|
|
60
73
|
class: className = "",
|
|
@@ -96,8 +109,7 @@ const projectItems = project.items.length ? project.items : defaultProject;
|
|
|
96
109
|
]}
|
|
97
110
|
>
|
|
98
111
|
<div
|
|
99
|
-
class="mx-auto grid max-w-wide grid-cols-2 gap-8 px-5 py-12 md:grid-cols-4 sm:px-7"
|
|
100
|
-
style="max-width: var(--container-wide, 1400px);"
|
|
112
|
+
class="grove-container-wide mx-auto grid max-w-wide grid-cols-2 gap-8 px-5 py-12 md:grid-cols-4 sm:px-7"
|
|
101
113
|
>
|
|
102
114
|
<div class="col-span-2 md:col-span-1">
|
|
103
115
|
<div
|
|
@@ -116,25 +128,25 @@ const projectItems = project.items.length ? project.items : defaultProject;
|
|
|
116
128
|
)}
|
|
117
129
|
{stats && (
|
|
118
130
|
<p class="mt-3 m-0 text-2xs text-ink-500 dark:text-ink-400">
|
|
119
|
-
{stats.
|
|
131
|
+
{stats.totalApps !== undefined && (
|
|
120
132
|
<>
|
|
121
|
-
<span class="font-mono">{stats.
|
|
133
|
+
<span class="font-mono">{stats.totalApps}</span> {itemsLabel}
|
|
122
134
|
</>
|
|
123
135
|
)}
|
|
124
|
-
{stats.
|
|
136
|
+
{stats.totalApps !== undefined && (stats.totalCategories !== undefined || stats.totalOwners !== undefined) && (
|
|
125
137
|
<span class="mx-1 text-ink-300 dark:text-ink-700">·</span>
|
|
126
138
|
)}
|
|
127
|
-
{stats.
|
|
139
|
+
{stats.totalCategories !== undefined && (
|
|
128
140
|
<>
|
|
129
|
-
<span class="font-mono">{stats.
|
|
141
|
+
<span class="font-mono">{stats.totalCategories}</span> categories
|
|
130
142
|
</>
|
|
131
143
|
)}
|
|
132
|
-
{stats.
|
|
144
|
+
{stats.totalCategories !== undefined && stats.totalOwners !== undefined && (
|
|
133
145
|
<span class="mx-1 text-ink-300 dark:text-ink-700">·</span>
|
|
134
146
|
)}
|
|
135
|
-
{stats.
|
|
147
|
+
{stats.totalOwners !== undefined && (
|
|
136
148
|
<>
|
|
137
|
-
<span class="font-mono">{stats.
|
|
149
|
+
<span class="font-mono">{stats.totalOwners}</span> contributors
|
|
138
150
|
</>
|
|
139
151
|
)}
|
|
140
152
|
</p>
|
|
@@ -213,10 +225,9 @@ const projectItems = project.items.length ? project.items : defaultProject;
|
|
|
213
225
|
|
|
214
226
|
<div class="border-t border-ink-200 dark:border-ink-800">
|
|
215
227
|
<div
|
|
216
|
-
class="mx-auto flex max-w-wide flex-wrap items-center gap-3 px-5 py-4 text-2xs text-ink-500 sm:px-7 dark:text-ink-400"
|
|
217
|
-
style="max-width: var(--container-wide, 1400px);"
|
|
228
|
+
class="grove-container-wide mx-auto flex max-w-wide flex-wrap items-center gap-3 px-5 py-4 text-2xs text-ink-500 sm:px-7 dark:text-ink-400"
|
|
218
229
|
>
|
|
219
|
-
<span>© {year} {holder}
|
|
230
|
+
<span>© {year} {holder}{licenseLine ? `. ${licenseLine}` : "."}</span>
|
|
220
231
|
{gh && (
|
|
221
232
|
<>
|
|
222
233
|
<span class="text-ink-300 dark:text-ink-700">·</span>
|
package/src/layouts/Header.astro
CHANGED
|
@@ -26,6 +26,13 @@ interface Site {
|
|
|
26
26
|
nav: NavItem[];
|
|
27
27
|
description?: string;
|
|
28
28
|
repoUrl?: string;
|
|
29
|
+
url?: string;
|
|
30
|
+
stats?: {
|
|
31
|
+
totalStars?: number;
|
|
32
|
+
};
|
|
33
|
+
blueprintConfig?: {
|
|
34
|
+
routeSlug?: string;
|
|
35
|
+
};
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
interface Props {
|
|
@@ -47,6 +54,21 @@ const {
|
|
|
47
54
|
repoLabel = "Star",
|
|
48
55
|
class: className = "",
|
|
49
56
|
} = Astro.props;
|
|
57
|
+
const directoryPath = `/${site.blueprintConfig?.routeSlug ?? "items"}`;
|
|
58
|
+
const navItems = site.blueprintConfig
|
|
59
|
+
? [
|
|
60
|
+
{ label: "Recently added", href: `${directoryPath}?label=new` },
|
|
61
|
+
{ label: "Trending", href: `${directoryPath}?label=hot` },
|
|
62
|
+
{ label: "Categories", href: directoryPath },
|
|
63
|
+
{ label: "Stacks", href: directoryPath },
|
|
64
|
+
{ label: "About", href: "/about" },
|
|
65
|
+
]
|
|
66
|
+
: site.nav;
|
|
67
|
+
const stars = site.stats?.totalStars ?? 0;
|
|
68
|
+
const starsLabel =
|
|
69
|
+
stars >= 1000
|
|
70
|
+
? `${(stars / 1000).toFixed(stars >= 10000 ? 0 : 1)}k`
|
|
71
|
+
: String(stars);
|
|
50
72
|
---
|
|
51
73
|
|
|
52
74
|
<header
|
|
@@ -56,8 +78,7 @@ const {
|
|
|
56
78
|
]}
|
|
57
79
|
>
|
|
58
80
|
<div
|
|
59
|
-
class="mx-auto flex max-w-wide items-center gap-6 px-5 py-3 sm:px-7"
|
|
60
|
-
style="max-width: var(--container-wide, 1400px);"
|
|
81
|
+
class="grove-container-wide mx-auto flex max-w-wide items-center gap-6 px-5 py-3 sm:px-7"
|
|
61
82
|
>
|
|
62
83
|
<a
|
|
63
84
|
href="/"
|
|
@@ -72,7 +93,7 @@ const {
|
|
|
72
93
|
|
|
73
94
|
<nav class="hidden flex-1 md:block" aria-label="Primary">
|
|
74
95
|
<ul class="m-0 flex list-none gap-5 p-0">
|
|
75
|
-
{
|
|
96
|
+
{navItems.map((item) => (
|
|
76
97
|
<li>
|
|
77
98
|
<a
|
|
78
99
|
href={item.href}
|
|
@@ -89,7 +110,7 @@ const {
|
|
|
89
110
|
{submitHref && (
|
|
90
111
|
<a
|
|
91
112
|
href={submitHref}
|
|
92
|
-
class="
|
|
113
|
+
class="hidden h-9 items-center gap-1.5 rounded-md border border-ink-200 bg-white px-3 text-[0.875rem] font-medium text-ink-700 transition-colors hover:border-ink-300 hover:bg-ink-50 sm:inline-flex dark:border-ink-800 dark:bg-ink-950 dark:text-ink-300 dark:hover:border-ink-700 dark:hover:bg-ink-900/40"
|
|
93
114
|
>
|
|
94
115
|
<svg
|
|
95
116
|
viewBox="0 0 16 16"
|
|
@@ -109,22 +130,22 @@ const {
|
|
|
109
130
|
href={site.repoUrl}
|
|
110
131
|
target="_blank"
|
|
111
132
|
rel="noopener noreferrer"
|
|
112
|
-
aria-label={repoLabel}
|
|
113
|
-
title={repoLabel}
|
|
114
|
-
class="
|
|
133
|
+
aria-label={`${repoLabel} on GitHub — ${stars} stars`}
|
|
134
|
+
title={`${repoLabel} on GitHub`}
|
|
135
|
+
class="inline-flex h-9 items-center divide-x divide-ink-200 overflow-hidden rounded-md border border-ink-200 text-[0.875rem] font-medium transition-colors hover:border-ink-300 hover:bg-ink-50 dark:divide-ink-800 dark:border-ink-800 dark:hover:border-ink-700 dark:hover:bg-ink-900/40"
|
|
115
136
|
>
|
|
116
|
-
<
|
|
117
|
-
viewBox="0 0 16 16"
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
>
|
|
123
|
-
<
|
|
124
|
-
d="M8
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
137
|
+
<span class="flex items-center gap-1.5 px-2.5 py-1.5 text-ink-900 dark:text-ink-100">
|
|
138
|
+
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" fill="currentColor">
|
|
139
|
+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38v-1.33c-2.22.48-2.69-1.07-2.69-1.07-.36-.92-.89-1.17-.89-1.17-.73-.5.05-.49.05-.49.8.06 1.23.83 1.23.83.72 1.22 1.87.87 2.33.67.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48v2.19c0 .21.15.46.55.38A8 8 0 0 0 8 0Z"></path>
|
|
140
|
+
</svg>
|
|
141
|
+
<span>{repoLabel}</span>
|
|
142
|
+
</span>
|
|
143
|
+
<span class="flex items-center gap-1 bg-ink-50 px-2 py-1.5 text-ink-700 dark:bg-ink-900/60 dark:text-ink-300">
|
|
144
|
+
<svg viewBox="0 0 16 16" width="11" height="11" aria-hidden="true" fill="currentColor" class="text-amber-500">
|
|
145
|
+
<path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"></path>
|
|
146
|
+
</svg>
|
|
147
|
+
<span class="font-mono">{starsLabel}</span>
|
|
148
|
+
</span>
|
|
128
149
|
</a>
|
|
129
150
|
)}
|
|
130
151
|
|