@grove-dev/astro 0.1.5 → 0.2.2
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/index.d.ts +4 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +56 -22
- package/dist/index.js.map +1 -1
- package/dist/layouts/index.d.ts +25 -0
- package/dist/layouts/index.d.ts.map +1 -0
- package/dist/layouts/index.js +25 -0
- package/dist/layouts/index.js.map +1 -0
- package/dist/lib/display.d.ts +33 -0
- package/dist/lib/display.d.ts.map +1 -0
- package/dist/lib/display.js +104 -0
- package/dist/lib/display.js.map +1 -0
- package/dist/lib/format.d.ts +30 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +73 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/index.d.ts +26 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +26 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/lenses.d.ts +96 -0
- package/dist/lib/lenses.d.ts.map +1 -0
- package/dist/lib/lenses.js +181 -0
- package/dist/lib/lenses.js.map +1 -0
- package/dist/lib/repo.d.ts +30 -0
- package/dist/lib/repo.d.ts.map +1 -0
- package/dist/lib/repo.js +44 -0
- package/dist/lib/repo.js.map +1 -0
- package/dist/lib/scores.d.ts +38 -0
- package/dist/lib/scores.d.ts.map +1 -0
- package/dist/lib/scores.js +83 -0
- package/dist/lib/scores.js.map +1 -0
- package/dist/lib/search.d.ts +199 -0
- package/dist/lib/search.d.ts.map +1 -0
- package/dist/lib/search.js +357 -0
- package/dist/lib/search.js.map +1 -0
- package/dist/lib/taxonomy-counts.d.ts +25 -0
- package/dist/lib/taxonomy-counts.d.ts.map +1 -0
- package/dist/lib/taxonomy-counts.js +41 -0
- package/dist/lib/taxonomy-counts.js.map +1 -0
- package/package.json +7 -7
- package/src/components/AppSection.astro +83 -0
- package/src/components/AppsIndexRow.astro +204 -0
- package/src/components/AppsPagination.astro +126 -0
- package/src/components/CategoryGrid.astro +86 -17
- package/src/components/ContributorsGrid.astro +149 -0
- package/src/components/DecisionRow.astro +89 -0
- package/src/components/ExploreByCategory.astro +67 -0
- package/src/components/ExploreByStack.astro +72 -0
- package/src/components/Hero.astro +246 -0
- package/src/components/Icon.astro +223 -0
- package/src/components/ItemCard.astro +289 -30
- package/src/components/MinimalAbout.astro +82 -0
- package/src/components/OriginalCollection.astro +116 -0
- package/src/components/RefinePanel.astro +197 -0
- package/src/components/ScoreBars.astro +92 -22
- package/src/components/SmartLensTabs.astro +71 -0
- package/src/components/StackGrid.astro +132 -0
- package/src/components/WhyThisExists.astro +116 -0
- package/src/index.ts +61 -14
- package/src/layouts/BaseLayout.astro +227 -15
- package/src/layouts/Container.astro +57 -0
- package/src/layouts/Footer.astro +235 -0
- package/src/layouts/Header.astro +159 -0
- package/src/layouts/SectionHeader.astro +58 -0
- package/{templates/default/src/components/layout → src/layouts}/Seo.astro +62 -38
- package/src/layouts/ThemeToggle.astro +110 -0
- package/src/layouts/astro-shim.d.ts +29 -0
- package/src/layouts/index.ts +24 -0
- package/src/lib/display.ts +118 -0
- package/src/lib/format.ts +65 -0
- package/src/lib/index.ts +26 -0
- package/src/lib/lenses.ts +241 -0
- package/src/lib/repo.ts +44 -0
- package/src/lib/scores.ts +94 -0
- package/src/lib/search.ts +481 -0
- package/src/lib/taxonomy-counts.ts +60 -0
- package/src/styles.css +366 -638
- package/templates/default/.astro/content.d.ts +155 -0
- package/templates/default/.astro/settings.json +5 -0
- package/templates/default/.astro/types.d.ts +2 -0
- package/templates/default/astro.config.mjs +8 -6
- package/templates/default/content/pages/about.md +66 -0
- package/templates/default/content/pages/methodology.md +103 -0
- package/templates/default/data/generated/records.full.json +8 -0
- package/templates/default/data/generated/records.index.json +7 -0
- package/templates/default/data/generated/records.json +8 -0
- package/templates/default/data/generated/site-config.json +31 -0
- package/templates/default/data/records/.gitkeep +0 -0
- package/templates/default/dist/_astro/BaseLayout.DfpwR3jc.css +1 -0
- package/templates/default/dist/about/index.html +165 -0
- package/templates/default/dist/icons/platforms/android.svg +1 -0
- package/templates/default/dist/icons/platforms/apple-dark.svg +1 -0
- package/templates/default/dist/icons/platforms/apple-light.svg +1 -0
- package/templates/default/dist/icons/platforms/chrome.svg +1 -0
- package/templates/default/dist/icons/platforms/chromeos.svg +5 -0
- package/templates/default/dist/icons/platforms/desktop.svg +4 -0
- package/templates/default/dist/icons/platforms/embedded.svg +5 -0
- package/templates/default/dist/icons/platforms/ios.svg +1 -0
- package/templates/default/dist/icons/platforms/linux.svg +1 -0
- package/templates/default/dist/icons/platforms/macos.svg +1 -0
- package/templates/default/dist/icons/platforms/ubuntu.svg +1 -0
- package/templates/default/dist/icons/platforms/web.svg +5 -0
- package/templates/default/dist/icons/platforms/windows.svg +6 -0
- package/templates/default/dist/icons/stacks/android.svg +1 -0
- package/templates/default/dist/icons/stacks/apple-dark.svg +1 -0
- package/templates/default/dist/icons/stacks/apple-light.svg +1 -0
- package/templates/default/dist/icons/stacks/capacitor.svg +14 -0
- package/templates/default/dist/icons/stacks/dart.svg +1 -0
- package/templates/default/dist/icons/stacks/firebase.svg +1 -0
- package/templates/default/dist/icons/stacks/flutter.svg +1 -0
- package/templates/default/dist/icons/stacks/graphql.svg +1 -0
- package/templates/default/dist/icons/stacks/ionic.svg +4 -0
- package/templates/default/dist/icons/stacks/java.svg +1 -0
- package/templates/default/dist/icons/stacks/javascript.svg +1 -0
- package/templates/default/dist/icons/stacks/kotlin.svg +1 -0
- package/templates/default/dist/icons/stacks/mongodb.svg +1 -0
- package/templates/default/dist/icons/stacks/nodejs.svg +1 -0
- package/templates/default/dist/icons/stacks/python.svg +1 -0
- package/templates/default/dist/icons/stacks/react-native.svg +1 -0
- package/templates/default/dist/icons/stacks/react.svg +1 -0
- package/templates/default/dist/icons/stacks/rust.svg +1 -0
- package/templates/default/dist/icons/stacks/solidity.svg +1 -0
- package/templates/default/dist/icons/stacks/swift.svg +1 -0
- package/templates/default/dist/icons/stacks/tensorflow.svg +1 -0
- package/templates/default/dist/icons/stacks/typescript.svg +1 -0
- package/templates/default/dist/index.html +55 -0
- package/templates/default/dist/llms-full.txt +14 -0
- package/templates/default/dist/llms.txt +11 -0
- package/templates/default/dist/og-image.svg +48 -0
- package/templates/default/dist/projects/index.html +61 -0
- package/templates/default/dist/robots.txt +53 -0
- package/templates/default/dist/sitemap.xml +21 -0
- package/templates/default/dist/submit/index.html +69 -0
- package/templates/default/grove.config.ts +36 -0
- package/templates/default/node_modules/.bin/astro +21 -0
- package/templates/default/node_modules/.bin/astro-check +21 -0
- package/templates/default/node_modules/.bin/grove +21 -0
- package/templates/default/node_modules/.bin/jiti +21 -0
- package/templates/default/node_modules/.bin/prettier +21 -0
- package/templates/default/node_modules/.bin/rollup +21 -0
- package/templates/default/node_modules/.bin/tsc +21 -0
- package/templates/default/node_modules/.bin/tsserver +21 -0
- package/templates/default/node_modules/.bin/tsx +21 -0
- package/templates/default/node_modules/.bin/vite +21 -0
- package/templates/default/node_modules/.bin/yaml +21 -0
- package/templates/default/node_modules/.vite/deps/_metadata.json +79 -0
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +433 -0
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js +5 -0
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +5 -0
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +9 -0
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +241 -0
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +1592 -0
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +186 -0
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +48 -0
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +6779 -0
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +33 -0
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +88 -0
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +72 -0
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +3757 -0
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +54 -0
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/package.json +3 -0
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +213 -0
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +525 -0
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +1020 -0
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +7 -0
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +147 -0
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +7 -0
- package/templates/default/package.json +6 -5
- package/templates/default/public/llms-full.txt +14 -0
- package/templates/default/public/llms.txt +11 -0
- package/templates/default/src/components/layout/BaseLayout.astro +10 -57
- package/templates/default/src/components/layout/Footer.astro +16 -134
- package/templates/default/src/components/layout/Header.astro +25 -126
- package/templates/default/src/data/records.ts +161 -0
- package/templates/default/src/lib/markdown.ts +171 -0
- package/templates/default/src/pages/about.astro +95 -429
- package/templates/default/src/pages/index.astro +258 -61
- package/templates/default/src/pages/projects/[slug].astro +480 -0
- package/templates/default/src/pages/projects/index.astro +193 -0
- package/templates/default/src/pages/sitemap.xml.ts +73 -61
- package/templates/default/src/pages/submit.astro +122 -411
- package/templates/default/src/styles/global.css +42 -68
- package/src/components/ActiveFilterChips.astro +0 -20
- package/src/components/DirectoryFilters.astro +0 -100
- package/src/components/DirectoryHero.astro +0 -32
- package/src/components/HealthBadge.astro +0 -12
- package/src/components/LensTabs.astro +0 -26
- package/src/components/MethodologyPanel.astro +0 -20
- package/src/components/Pagination.astro +0 -24
- package/src/components/ProjectDetail.astro +0 -166
- package/src/components/SubmitDraft.astro +0 -137
- package/templates/default/src/components/AppCard.astro +0 -248
- package/templates/default/src/components/AppSection.astro +0 -58
- package/templates/default/src/components/AppsIndexRow.astro +0 -230
- package/templates/default/src/components/AppsPagination.astro +0 -147
- package/templates/default/src/components/CategoryGrid.astro +0 -80
- package/templates/default/src/components/ContributorsGrid.astro +0 -105
- package/templates/default/src/components/DecisionRow.astro +0 -219
- package/templates/default/src/components/ExploreByCategory.astro +0 -74
- package/templates/default/src/components/ExploreByStack.astro +0 -102
- package/templates/default/src/components/Hero.astro +0 -176
- package/templates/default/src/components/MinimalAbout.astro +0 -57
- package/templates/default/src/components/OriginalCollection.astro +0 -144
- package/templates/default/src/components/RefinePanel.astro +0 -272
- package/templates/default/src/components/ScoreBars.astro +0 -72
- package/templates/default/src/components/SmartLensTabs.astro +0 -65
- package/templates/default/src/components/StackGrid.astro +0 -95
- package/templates/default/src/components/WhyThisExists.astro +0 -81
- package/templates/default/src/components/icons/Icon.astro +0 -148
- package/templates/default/src/components/layout/SectionHeader.astro +0 -42
- package/templates/default/src/components/layout/ThemeToggle.astro +0 -97
- package/templates/default/src/data/apps.ts +0 -149
- package/templates/default/src/data/categories.ts +0 -27
- package/templates/default/src/data/config.ts +0 -28
- package/templates/default/src/data/contributors.ts +0 -96
- package/templates/default/src/data/stacks.ts +0 -47
- package/templates/default/src/data/stats.ts +0 -65
- package/templates/default/src/data/types.ts +0 -248
- package/templates/default/src/lib/apps-search.ts +0 -228
- package/templates/default/src/lib/format.ts +0 -36
- package/templates/default/src/lib/lenses.ts +0 -98
- package/templates/default/src/lib/repo.ts +0 -12
- package/templates/default/src/lib/scores.ts +0 -51
- package/templates/default/src/lib/taxonomy-counts.ts +0 -31
- package/templates/default/src/pages/apps/[slug].astro +0 -709
- package/templates/default/src/pages/apps/index.astro +0 -669
- package/templates/default/src/pages/contributors.astro +0 -142
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Header — sticky brand + nav + theme toggle.
|
|
4
|
+
*
|
|
5
|
+
* Generic: takes a `site` prop with brand + nav. No `siteConfig`
|
|
6
|
+
* import — consumers compose the props from their own config.
|
|
7
|
+
*
|
|
8
|
+
* Optional `submit` button renders a "Submit app" / "Submit project"
|
|
9
|
+
* call-to-action on the right side of the nav. Set `submitHref` /
|
|
10
|
+
* `submitLabel` to enable, leave them undefined for a clean header.
|
|
11
|
+
*
|
|
12
|
+
* Optional `repoUrl` adds a "View source" GitHub button. Set
|
|
13
|
+
* `repoLabel` to change the visible text.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import ThemeToggle from "./ThemeToggle.astro";
|
|
17
|
+
|
|
18
|
+
interface NavItem {
|
|
19
|
+
label: string;
|
|
20
|
+
href: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Site {
|
|
24
|
+
name: string;
|
|
25
|
+
tagline?: string;
|
|
26
|
+
nav: NavItem[];
|
|
27
|
+
description?: string;
|
|
28
|
+
repoUrl?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Props {
|
|
32
|
+
site: Site;
|
|
33
|
+
/** Submit button label, e.g. "Submit app". Omit to hide. */
|
|
34
|
+
submitLabel?: string;
|
|
35
|
+
/** Submit button href, e.g. "/submit". Omit to hide. */
|
|
36
|
+
submitHref?: string;
|
|
37
|
+
/** GitHub button label, e.g. "View source". */
|
|
38
|
+
repoLabel?: string;
|
|
39
|
+
/** Extra class for the outer header element. */
|
|
40
|
+
class?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
site,
|
|
45
|
+
submitLabel,
|
|
46
|
+
submitHref,
|
|
47
|
+
repoLabel = "Star",
|
|
48
|
+
class: className = "",
|
|
49
|
+
} = Astro.props;
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
<header
|
|
53
|
+
class:list={[
|
|
54
|
+
"sticky top-0 z-50 border-b border-ink-200 bg-white/85 backdrop-blur dark:border-ink-800 dark:bg-ink-950/85",
|
|
55
|
+
className,
|
|
56
|
+
]}
|
|
57
|
+
>
|
|
58
|
+
<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);"
|
|
61
|
+
>
|
|
62
|
+
<a
|
|
63
|
+
href="/"
|
|
64
|
+
class="inline-flex items-center gap-2 text-[0.95rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
|
|
65
|
+
>
|
|
66
|
+
<span
|
|
67
|
+
aria-hidden="true"
|
|
68
|
+
class="inline-block h-[18px] w-[18px] rounded bg-ink-900 dark:bg-ink-100"
|
|
69
|
+
></span>
|
|
70
|
+
<span>{site.name}</span>
|
|
71
|
+
</a>
|
|
72
|
+
|
|
73
|
+
<nav class="hidden flex-1 md:block" aria-label="Primary">
|
|
74
|
+
<ul class="m-0 flex list-none gap-5 p-0">
|
|
75
|
+
{site.nav.map((item) => (
|
|
76
|
+
<li>
|
|
77
|
+
<a
|
|
78
|
+
href={item.href}
|
|
79
|
+
class="text-[0.875rem] font-medium text-ink-500 transition-colors hover:text-ink-900 dark:text-ink-400 dark:hover:text-ink-100"
|
|
80
|
+
>
|
|
81
|
+
{item.label}
|
|
82
|
+
</a>
|
|
83
|
+
</li>
|
|
84
|
+
))}
|
|
85
|
+
</ul>
|
|
86
|
+
</nav>
|
|
87
|
+
|
|
88
|
+
<div class="ml-auto flex items-center gap-2">
|
|
89
|
+
{submitHref && (
|
|
90
|
+
<a
|
|
91
|
+
href={submitHref}
|
|
92
|
+
class="grove-btn grove-btn-outline"
|
|
93
|
+
>
|
|
94
|
+
<svg
|
|
95
|
+
viewBox="0 0 16 16"
|
|
96
|
+
width="13"
|
|
97
|
+
height="13"
|
|
98
|
+
aria-hidden="true"
|
|
99
|
+
fill="currentColor"
|
|
100
|
+
>
|
|
101
|
+
<path d="M7.75 2a.75.75 0 0 1 .75.75v3.5h3.5a.75.75 0 0 1 0 1.5h-3.5v3.5a.75.75 0 0 1-1.5 0v-3.5h-3.5a.75.75 0 0 1 0-1.5h3.5v-3.5A.75.75 0 0 1 7.75 2Z"></path>
|
|
102
|
+
</svg>
|
|
103
|
+
<span>{submitLabel ?? "Submit"}</span>
|
|
104
|
+
</a>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{site.repoUrl && (
|
|
108
|
+
<a
|
|
109
|
+
href={site.repoUrl}
|
|
110
|
+
target="_blank"
|
|
111
|
+
rel="noopener noreferrer"
|
|
112
|
+
aria-label={repoLabel}
|
|
113
|
+
title={repoLabel}
|
|
114
|
+
class="grove-btn grove-btn-outline"
|
|
115
|
+
>
|
|
116
|
+
<svg
|
|
117
|
+
viewBox="0 0 16 16"
|
|
118
|
+
width="16"
|
|
119
|
+
height="16"
|
|
120
|
+
aria-hidden="true"
|
|
121
|
+
fill="currentColor"
|
|
122
|
+
>
|
|
123
|
+
<path
|
|
124
|
+
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"
|
|
125
|
+
></path>
|
|
126
|
+
</svg>
|
|
127
|
+
<span>{repoLabel}</span>
|
|
128
|
+
</a>
|
|
129
|
+
)}
|
|
130
|
+
|
|
131
|
+
<ThemeToggle />
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</header>
|
|
135
|
+
|
|
136
|
+
<script>
|
|
137
|
+
// Smooth-scroll for in-page anchor links (Header nav + Hero CTA).
|
|
138
|
+
// Works on any <a href="#…"> that points to an existing element on
|
|
139
|
+
// the current page. External links (e.g. /about, /projects?…) are
|
|
140
|
+
// skipped because they don't start with "#". Modifier-clicks
|
|
141
|
+
// (Cmd/Ctrl/Shift/middle) let the browser handle the anchor
|
|
142
|
+
// normally so users can open the same section in a new tab.
|
|
143
|
+
document.addEventListener("click", (e) => {
|
|
144
|
+
if (e.defaultPrevented) return;
|
|
145
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
|
146
|
+
if (e.button !== 0) return;
|
|
147
|
+
const link = (e.target as HTMLElement).closest<HTMLAnchorElement>(
|
|
148
|
+
'a[href^="#"]',
|
|
149
|
+
);
|
|
150
|
+
if (!link) return;
|
|
151
|
+
const id = link.getAttribute("href")!;
|
|
152
|
+
if (id.length <= 1) return;
|
|
153
|
+
const target = document.querySelector(id);
|
|
154
|
+
if (!target) return;
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
157
|
+
history.replaceState(null, "", id);
|
|
158
|
+
});
|
|
159
|
+
</script>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* SectionHeader — eyebrow + title + description, used to title
|
|
4
|
+
* every section on the home page. Optionally centered (good for
|
|
5
|
+
* "Why this exists" and friends).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
eyebrow?: string;
|
|
10
|
+
title: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
align?: "left" | "center";
|
|
13
|
+
/** 2 (default) for top-level sections, 3 for nested ones. */
|
|
14
|
+
level?: 2 | 3;
|
|
15
|
+
/** Extra class for the outer header element. */
|
|
16
|
+
class?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
eyebrow,
|
|
21
|
+
title,
|
|
22
|
+
description,
|
|
23
|
+
align = "left",
|
|
24
|
+
level = 2,
|
|
25
|
+
class: className = "",
|
|
26
|
+
} = Astro.props;
|
|
27
|
+
const Heading = `h${level}` as "h2" | "h3";
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<header
|
|
31
|
+
class:list={[
|
|
32
|
+
"mb-6 flex flex-col gap-2",
|
|
33
|
+
align === "center" ? "items-center text-center" : "",
|
|
34
|
+
className,
|
|
35
|
+
]}
|
|
36
|
+
>
|
|
37
|
+
{eyebrow && (
|
|
38
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
39
|
+
{eyebrow}
|
|
40
|
+
</span>
|
|
41
|
+
)}
|
|
42
|
+
<div class="flex flex-wrap items-end justify-between gap-3">
|
|
43
|
+
<Heading class="m-0 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
44
|
+
{title}
|
|
45
|
+
</Heading>
|
|
46
|
+
<slot name="actions" />
|
|
47
|
+
</div>
|
|
48
|
+
{description && (
|
|
49
|
+
<p
|
|
50
|
+
class:list={[
|
|
51
|
+
"m-0 text-[0.9375rem] leading-relaxed text-ink-500 dark:text-ink-400",
|
|
52
|
+
align === "center" ? "max-w-2xl" : "max-w-[56ch]",
|
|
53
|
+
]}
|
|
54
|
+
>
|
|
55
|
+
{description}
|
|
56
|
+
</p>
|
|
57
|
+
)}
|
|
58
|
+
</header>
|
|
@@ -1,36 +1,44 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { siteConfig } from "../../data/config";
|
|
3
|
-
// SPDX-License-Identifier: MIT
|
|
4
|
-
|
|
5
2
|
/**
|
|
6
|
-
*
|
|
7
|
-
* and JSON-LD structured data. Designed
|
|
8
|
-
*
|
|
9
|
-
*
|
|
3
|
+
* Seo — per-page <head> content: title, description, canonical,
|
|
4
|
+
* Open Graph, Twitter Card, and JSON-LD structured data. Designed
|
|
5
|
+
* to be used inside the <head> slot of BaseLayout. Pages pass the
|
|
6
|
+
* rich props they care about; everything else has a sensible default
|
|
7
|
+
* derived from `site` and the Astro URL.
|
|
10
8
|
*
|
|
11
9
|
* Renders:
|
|
12
10
|
* - <title>, meta description, canonical link
|
|
13
11
|
* - Open Graph (og:*) for Facebook, LinkedIn, Discord, Slack, etc.
|
|
14
12
|
* - Twitter Card (twitter:*) for X / Twitter
|
|
15
13
|
* - JSON-LD WebSite + Organization on every page
|
|
16
|
-
* - JSON-LD
|
|
17
|
-
*
|
|
14
|
+
* - JSON-LD custom payload (e.g. SoftwareSourceCode) when `jsonLd`
|
|
15
|
+
* is provided.
|
|
18
16
|
*
|
|
19
|
-
* Noindex is opt-in via the `noindex` prop. By default everything
|
|
20
|
-
*
|
|
17
|
+
* Noindex is opt-in via the `noindex` prop. By default everything
|
|
18
|
+
* is indexable.
|
|
21
19
|
*/
|
|
22
20
|
|
|
21
|
+
interface SiteRef {
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
repoUrl?: string;
|
|
26
|
+
searchPath?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
interface Props {
|
|
24
30
|
title: string;
|
|
25
31
|
description: string;
|
|
26
|
-
/** Open Graph / Twitter image. Default: /og-image.svg.
|
|
32
|
+
/** Open Graph / Twitter image. Default: /og-image.svg. */
|
|
27
33
|
image?: string;
|
|
28
|
-
/** og:type. Default: "website".
|
|
34
|
+
/** og:type. Default: "website". */
|
|
29
35
|
type?: "website" | "article" | "profile";
|
|
30
36
|
/** Set true to add noindex,nofollow. */
|
|
31
37
|
noindex?: boolean;
|
|
32
|
-
/** Extra JSON-LD payload (object) — typically SoftwareSourceCode
|
|
38
|
+
/** Extra JSON-LD payload (object) — typically SoftwareSourceCode. */
|
|
33
39
|
jsonLd?: Record<string, unknown> | Record<string, unknown>[];
|
|
40
|
+
/** Site identity (name, description, url, repoUrl). Required. */
|
|
41
|
+
site: SiteRef;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
const {
|
|
@@ -40,45 +48,53 @@ const {
|
|
|
40
48
|
type = "website",
|
|
41
49
|
noindex = false,
|
|
42
50
|
jsonLd,
|
|
51
|
+
site,
|
|
43
52
|
} = Astro.props;
|
|
44
53
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
const siteUrl = site.url
|
|
55
|
+
? new URL(site.url)
|
|
56
|
+
: (Astro.site ?? new URL("https://grove.dev"));
|
|
57
|
+
const canonical = new URL(Astro.url.pathname, siteUrl).toString();
|
|
58
|
+
const ogImage = new URL(image, siteUrl).toString();
|
|
48
59
|
|
|
49
|
-
// Truncate description for OG/Twitter where longer is worse (160
|
|
60
|
+
// Truncate description for OG/Twitter where longer is worse (160-200 chars).
|
|
50
61
|
function truncate(s: string, max: number): string {
|
|
51
62
|
if (s.length <= max) return s;
|
|
52
63
|
return s.slice(0, max - 1).trimEnd() + "\u2026";
|
|
53
64
|
}
|
|
54
65
|
const shortDescription = truncate(description, 200);
|
|
66
|
+
const siteDescription =
|
|
67
|
+
site.description ??
|
|
68
|
+
"A growing community knowledge site powered by Grove.";
|
|
55
69
|
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
const websiteLd = {
|
|
70
|
+
// JSON-LD: site-wide identity. Google uses these for site links
|
|
71
|
+
// and the knowledge panel.
|
|
72
|
+
const searchPath = site.searchPath ?? "/projects";
|
|
73
|
+
const websiteLd: Record<string, unknown> = {
|
|
60
74
|
"@context": "https://schema.org",
|
|
61
75
|
"@type": "WebSite",
|
|
62
|
-
name:
|
|
63
|
-
description:
|
|
64
|
-
|
|
65
|
-
url: site.toString().replace(/\/$/, ""),
|
|
76
|
+
name: site.name,
|
|
77
|
+
description: siteDescription,
|
|
78
|
+
url: siteUrl.toString().replace(/\/$/, ""),
|
|
66
79
|
inLanguage: "en",
|
|
67
80
|
potentialAction: {
|
|
68
81
|
"@type": "SearchAction",
|
|
69
|
-
target: `${
|
|
82
|
+
target: `${siteUrl.toString().replace(/\/$/, "")}${searchPath}?q={search_term_string}`,
|
|
70
83
|
"query-input": "required name=search_term_string",
|
|
71
84
|
},
|
|
72
85
|
};
|
|
73
86
|
|
|
74
|
-
const organizationLd = {
|
|
87
|
+
const organizationLd: Record<string, unknown> = {
|
|
75
88
|
"@context": "https://schema.org",
|
|
76
89
|
"@type": "Organization",
|
|
77
|
-
name:
|
|
78
|
-
url:
|
|
79
|
-
logo: new URL("/og-image.svg",
|
|
80
|
-
sameAs: ["https://github.com/tortuvshin/open-apps"],
|
|
90
|
+
name: site.name,
|
|
91
|
+
url: siteUrl.toString().replace(/\/$/, ""),
|
|
92
|
+
logo: new URL("/og-image.svg", siteUrl).toString(),
|
|
81
93
|
};
|
|
94
|
+
|
|
95
|
+
if (site.repoUrl) {
|
|
96
|
+
organizationLd.sameAs = [site.repoUrl];
|
|
97
|
+
}
|
|
82
98
|
---
|
|
83
99
|
|
|
84
100
|
<!-- Primary meta -->
|
|
@@ -95,8 +111,8 @@ const organizationLd = {
|
|
|
95
111
|
<meta property="og:image" content={ogImage} />
|
|
96
112
|
<meta property="og:image:width" content="1200" />
|
|
97
113
|
<meta property="og:image:height" content="630" />
|
|
98
|
-
<meta property="og:image:alt" content=
|
|
99
|
-
<meta property="og:site_name" content=
|
|
114
|
+
<meta property="og:image:alt" content={`${site.name} — ${siteDescription}`} />
|
|
115
|
+
<meta property="og:site_name" content={site.name} />
|
|
100
116
|
<meta property="og:locale" content="en" />
|
|
101
117
|
|
|
102
118
|
<!-- Twitter Card -->
|
|
@@ -104,13 +120,21 @@ const organizationLd = {
|
|
|
104
120
|
<meta name="twitter:title" content={title} />
|
|
105
121
|
<meta name="twitter:description" content={shortDescription} />
|
|
106
122
|
<meta name="twitter:image" content={ogImage} />
|
|
107
|
-
<meta name="twitter:image:alt" content=
|
|
123
|
+
<meta name="twitter:image:alt" content={`${site.name} — ${siteDescription}`} />
|
|
108
124
|
|
|
109
125
|
<!-- JSON-LD: site-wide identity -->
|
|
110
|
-
<script
|
|
111
|
-
|
|
126
|
+
<script
|
|
127
|
+
is:inline
|
|
128
|
+
type="application/ld+json"
|
|
129
|
+
set:html={JSON.stringify(websiteLd)}
|
|
130
|
+
></script>
|
|
131
|
+
<script
|
|
132
|
+
is:inline
|
|
133
|
+
type="application/ld+json"
|
|
134
|
+
set:html={JSON.stringify(organizationLd)}
|
|
135
|
+
></script>
|
|
112
136
|
|
|
113
|
-
<!-- JSON-LD: per-page (SoftwareSourceCode for
|
|
137
|
+
<!-- JSON-LD: per-page (SoftwareSourceCode for [slug], etc.) -->
|
|
114
138
|
{jsonLd && (
|
|
115
139
|
<script
|
|
116
140
|
is:inline
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* ThemeToggle — a three-position theme switcher (light / dark / system).
|
|
4
|
+
*
|
|
5
|
+
* State is persisted in `localStorage` under the key "grove-theme"
|
|
6
|
+
* (matching `STORAGE_KEY` in `BaseLayout.astro`'s THEME_INIT script).
|
|
7
|
+
*
|
|
8
|
+
* The actual `<html class="dark">` toggle happens in the inline
|
|
9
|
+
* THEME_INIT script in BaseLayout — that script runs before paint
|
|
10
|
+
* so the user never sees a flash of the wrong theme.
|
|
11
|
+
*
|
|
12
|
+
* This button just rotates the stored mode and dispatches a
|
|
13
|
+
* `theme:set` event that the THEME_INIT listener picks up.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { class: className = "" } = Astro.props;
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
data-theme-toggle
|
|
26
|
+
aria-label="Toggle theme"
|
|
27
|
+
title="Toggle theme"
|
|
28
|
+
class:list={[
|
|
29
|
+
"relative inline-flex h-8 w-8 items-center justify-center rounded-md border border-ink-200 bg-white text-ink-700 transition-colors hover:border-ink-300 hover:bg-ink-50 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-200 dark:hover:border-ink-700 dark:hover:bg-ink-900/40",
|
|
30
|
+
className,
|
|
31
|
+
]}
|
|
32
|
+
>
|
|
33
|
+
{/* Sun — shown in dark mode (click to go light) */}
|
|
34
|
+
<svg
|
|
35
|
+
class="hidden h-4 w-4 dark:block"
|
|
36
|
+
viewBox="0 0 24 24"
|
|
37
|
+
fill="none"
|
|
38
|
+
stroke="currentColor"
|
|
39
|
+
stroke-width="1.8"
|
|
40
|
+
stroke-linecap="round"
|
|
41
|
+
stroke-linejoin="round"
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
>
|
|
44
|
+
<circle cx="12" cy="12" r="4"></circle>
|
|
45
|
+
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path>
|
|
46
|
+
</svg>
|
|
47
|
+
{/* Moon — shown in light mode (click to go dark) */}
|
|
48
|
+
<svg
|
|
49
|
+
class="block h-4 w-4 dark:hidden"
|
|
50
|
+
viewBox="0 0 24 24"
|
|
51
|
+
fill="none"
|
|
52
|
+
stroke="currentColor"
|
|
53
|
+
stroke-width="1.8"
|
|
54
|
+
stroke-linecap="round"
|
|
55
|
+
stroke-linejoin="round"
|
|
56
|
+
aria-hidden="true"
|
|
57
|
+
>
|
|
58
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"></path>
|
|
59
|
+
</svg>
|
|
60
|
+
</button>
|
|
61
|
+
|
|
62
|
+
<script>
|
|
63
|
+
// The toggle button simply rotates between the three modes on
|
|
64
|
+
// click. The actual class application lives in the page-level
|
|
65
|
+
// THEME_INIT script so behaviour stays consistent.
|
|
66
|
+
const STORAGE_KEY = "grove-theme";
|
|
67
|
+
type Mode = "light" | "dark" | "system";
|
|
68
|
+
|
|
69
|
+
function currentMode(): Mode {
|
|
70
|
+
try {
|
|
71
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
72
|
+
return stored === "light" || stored === "dark" || stored === "system"
|
|
73
|
+
? stored
|
|
74
|
+
: "system";
|
|
75
|
+
} catch {
|
|
76
|
+
return "system";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function rotate(): Mode {
|
|
81
|
+
const order: Mode[] = ["light", "dark", "system"];
|
|
82
|
+
const cur = currentMode();
|
|
83
|
+
const next = order[(order.indexOf(cur) + 1) % order.length];
|
|
84
|
+
try {
|
|
85
|
+
localStorage.setItem(STORAGE_KEY, next);
|
|
86
|
+
} catch {
|
|
87
|
+
// localStorage may be blocked (private mode, etc.) — we still
|
|
88
|
+
// dispatch the event so the UI updates for the current page.
|
|
89
|
+
}
|
|
90
|
+
// Notify the page-level listener (THEME_INIT).
|
|
91
|
+
window.dispatchEvent(new CustomEvent("theme:set", { detail: next }));
|
|
92
|
+
return next;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function updateLabel(btn: HTMLButtonElement) {
|
|
96
|
+
btn.title = `Theme: ${currentMode()} (click to switch)`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
document
|
|
100
|
+
.querySelectorAll<HTMLButtonElement>("[data-theme-toggle]")
|
|
101
|
+
.forEach((btn) => {
|
|
102
|
+
updateLabel(btn);
|
|
103
|
+
btn.addEventListener("click", () => {
|
|
104
|
+
rotate();
|
|
105
|
+
updateLabel(btn);
|
|
106
|
+
});
|
|
107
|
+
btn.addEventListener("mouseenter", () => updateLabel(btn));
|
|
108
|
+
btn.addEventListener("focus", () => updateLabel(btn));
|
|
109
|
+
});
|
|
110
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for the .astro layout components.
|
|
3
|
+
*
|
|
4
|
+
* The `.astro` files are excluded from `tsc` (the build only emits
|
|
5
|
+
* TypeScript helpers, not compiled components), so we declare the
|
|
6
|
+
* module shape here for the rare consumer that wants to write:
|
|
7
|
+
*
|
|
8
|
+
* import { BaseLayout } from "@grove-dev/astro/layouts";
|
|
9
|
+
*
|
|
10
|
+
* Consumers that just want the components should import them by
|
|
11
|
+
* file:
|
|
12
|
+
*
|
|
13
|
+
* import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
|
|
14
|
+
*
|
|
15
|
+
* The `AstroComponentFactory` is the runtime type Astro uses for
|
|
16
|
+
* every `.astro` module's default export. In a normal Astro project
|
|
17
|
+
* this is declared by the generated `.astro/types.d.ts` (produced
|
|
18
|
+
* by `astro sync`); the `@grove-dev/astro` package itself doesn't
|
|
19
|
+
* run an Astro build, so we re-declare it here.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
declare module "*.astro" {
|
|
23
|
+
// Pull the factory type from Astro's server runtime. The path
|
|
24
|
+
// `astro/runtime/server` is a documented deep import that has
|
|
25
|
+
// shipped across Astro 4 and 5.
|
|
26
|
+
import type { AstroComponentFactory } from "astro/runtime/server";
|
|
27
|
+
const component: AstroComponentFactory;
|
|
28
|
+
export default component;
|
|
29
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @grove-dev/astro — generic layout components.
|
|
3
|
+
*
|
|
4
|
+
* These layouts are the public, framework-agnostic shell used by
|
|
5
|
+
* every consumer page. They:
|
|
6
|
+
*
|
|
7
|
+
* - take a `site` prop (name, tagline, nav, repoUrl) instead of
|
|
8
|
+
* importing a `siteConfig` from a project-local path;
|
|
9
|
+
* - compose the standard Header / Footer / Seo / Container /
|
|
10
|
+
* SectionHeader / ThemeToggle components;
|
|
11
|
+
* - share the `grove-theme` localStorage key for theme state.
|
|
12
|
+
*
|
|
13
|
+
* Re-exported as ESM module names. Astro resolves the
|
|
14
|
+
* `@grove-dev/astro/layouts/*` subpath import from `src/layouts/`
|
|
15
|
+
* (the package's `dist/` only contains TypeScript helpers, not the
|
|
16
|
+
* `.astro` files themselves).
|
|
17
|
+
*/
|
|
18
|
+
export { default as BaseLayout } from "./BaseLayout.astro";
|
|
19
|
+
export { default as Container } from "./Container.astro";
|
|
20
|
+
export { default as Footer } from "./Footer.astro";
|
|
21
|
+
export { default as Header } from "./Header.astro";
|
|
22
|
+
export { default as SectionHeader } from "./SectionHeader.astro";
|
|
23
|
+
export { default as Seo } from "./Seo.astro";
|
|
24
|
+
export { default as ThemeToggle } from "./ThemeToggle.astro";
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display-name helpers for curation labels, statuses, lenses, and
|
|
3
|
+
* sort options. Internal ids (e.g. "hot", "stale", "production-like")
|
|
4
|
+
* stay stable for URL state and data files; this module owns the
|
|
5
|
+
* mapping from id → user-facing string.
|
|
6
|
+
*
|
|
7
|
+
* Every export is dependency-free so it can be imported by both
|
|
8
|
+
* server-rendered Astro components and any client-side script.
|
|
9
|
+
*
|
|
10
|
+
* Status ids accept the `HealthStatus` union from `@grove-dev/core`
|
|
11
|
+
* plus a couple of curated composites (e.g. "needs-maintainer")
|
|
12
|
+
* that appear in URL state.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
AppLabel,
|
|
17
|
+
HealthStatus,
|
|
18
|
+
} from "@grove-dev/core";
|
|
19
|
+
|
|
20
|
+
// ── Label ids (the `curation.labels` array on a record) ──────────
|
|
21
|
+
export type LabelId = AppLabel;
|
|
22
|
+
|
|
23
|
+
export const LABEL_DISPLAY: Record<LabelId, string> = {
|
|
24
|
+
new: "Recently added",
|
|
25
|
+
hot: "Trending",
|
|
26
|
+
mature: "Established",
|
|
27
|
+
featured: "Featured",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Pretty-print a slug to Title Case. Used for arbitrary slugs. */
|
|
31
|
+
export function prettySlug(slug: string | null | undefined): string {
|
|
32
|
+
if (!slug) return "";
|
|
33
|
+
return slug
|
|
34
|
+
.replace(/[-_]+/g, " ")
|
|
35
|
+
.replace(/\s+/g, " ")
|
|
36
|
+
.trim()
|
|
37
|
+
.split(" ")
|
|
38
|
+
.map((w) => (w ? w[0]!.toUpperCase() + w.slice(1) : ""))
|
|
39
|
+
.join(" ");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Returns a human-readable name for a label id, or null if unknown. */
|
|
43
|
+
export function labelDisplay(id: string | null | undefined): string | null {
|
|
44
|
+
if (!id) return null;
|
|
45
|
+
if (id in LABEL_DISPLAY) {
|
|
46
|
+
return LABEL_DISPLAY[id as LabelId];
|
|
47
|
+
}
|
|
48
|
+
return prettySlug(id);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Status ids (the `health.status` field on a record) ───────────
|
|
52
|
+
/**
|
|
53
|
+
* Canonical status display map. Includes the curated
|
|
54
|
+
* "needs-maintainer" composite that's emitted by the
|
|
55
|
+
* `needs-maintainer` lens.
|
|
56
|
+
*/
|
|
57
|
+
export const STATUS_DISPLAY: Record<string, string> = {
|
|
58
|
+
active: "Active",
|
|
59
|
+
mature: "Mature",
|
|
60
|
+
stale: "Not recently active",
|
|
61
|
+
inactive: "Inactive",
|
|
62
|
+
archived: "Archived",
|
|
63
|
+
unknown: "Unknown",
|
|
64
|
+
historical: "Historical",
|
|
65
|
+
needs_review: "Needs review",
|
|
66
|
+
quiet: "Quiet",
|
|
67
|
+
unavailable: "Unavailable",
|
|
68
|
+
"needs-maintainer": "Needs maintainer",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function statusDisplay(
|
|
72
|
+
s: HealthStatus | string | null | undefined,
|
|
73
|
+
): string {
|
|
74
|
+
if (!s) return STATUS_DISPLAY.unknown;
|
|
75
|
+
return STATUS_DISPLAY[s] ?? prettySlug(s);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Lens ids (the curated tabs on /projects) ─────────────────────
|
|
79
|
+
export const LENS_DISPLAY: Record<string, string> = {
|
|
80
|
+
all: "All",
|
|
81
|
+
hot: "Trending",
|
|
82
|
+
new: "Recently added",
|
|
83
|
+
mature: "Established",
|
|
84
|
+
featured: "Featured",
|
|
85
|
+
"needs-review": "Needs review",
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export function lensDisplay(id: string | null | undefined): string {
|
|
89
|
+
if (!id) return LENS_DISPLAY.all;
|
|
90
|
+
return LENS_DISPLAY[id] ?? prettySlug(id);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Sort ids (the /projects sort dropdown) ───────────────────────
|
|
94
|
+
export const SORT_DISPLAY: Record<string, string> = {
|
|
95
|
+
"recently-updated": "Recently updated",
|
|
96
|
+
"most-starred": "Most starred",
|
|
97
|
+
"recently-added": "Recently added",
|
|
98
|
+
"best-overall": "Best overall",
|
|
99
|
+
alphabetical: "Alphabetical",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export function sortDisplay(id: string | null | undefined): string {
|
|
103
|
+
if (!id) return SORT_DISPLAY["recently-updated"];
|
|
104
|
+
return SORT_DISPLAY[id] ?? prettySlug(id);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── Status option list (used by filter dropdowns) ───────────────
|
|
108
|
+
// "needs-maintainer" is included even though it's a derived/composite
|
|
109
|
+
// status — filter dropdowns and chip rendering need to offer it
|
|
110
|
+
// alongside the raw HealthStatus values.
|
|
111
|
+
export const STATUS_OPTIONS: string[] = [
|
|
112
|
+
"active",
|
|
113
|
+
"quiet",
|
|
114
|
+
"stale",
|
|
115
|
+
"needs-maintainer",
|
|
116
|
+
"archived",
|
|
117
|
+
"unknown",
|
|
118
|
+
];
|