@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.
Files changed (246) hide show
  1. package/dist/index.d.ts +4 -25
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +56 -22
  4. package/dist/index.js.map +1 -1
  5. package/dist/layouts/index.d.ts +25 -0
  6. package/dist/layouts/index.d.ts.map +1 -0
  7. package/dist/layouts/index.js +25 -0
  8. package/dist/layouts/index.js.map +1 -0
  9. package/dist/lib/display.d.ts +33 -0
  10. package/dist/lib/display.d.ts.map +1 -0
  11. package/dist/lib/display.js +104 -0
  12. package/dist/lib/display.js.map +1 -0
  13. package/dist/lib/format.d.ts +30 -0
  14. package/dist/lib/format.d.ts.map +1 -0
  15. package/dist/lib/format.js +73 -0
  16. package/dist/lib/format.js.map +1 -0
  17. package/dist/lib/index.d.ts +26 -0
  18. package/dist/lib/index.d.ts.map +1 -0
  19. package/dist/lib/index.js +26 -0
  20. package/dist/lib/index.js.map +1 -0
  21. package/dist/lib/lenses.d.ts +96 -0
  22. package/dist/lib/lenses.d.ts.map +1 -0
  23. package/dist/lib/lenses.js +181 -0
  24. package/dist/lib/lenses.js.map +1 -0
  25. package/dist/lib/repo.d.ts +30 -0
  26. package/dist/lib/repo.d.ts.map +1 -0
  27. package/dist/lib/repo.js +44 -0
  28. package/dist/lib/repo.js.map +1 -0
  29. package/dist/lib/scores.d.ts +38 -0
  30. package/dist/lib/scores.d.ts.map +1 -0
  31. package/dist/lib/scores.js +83 -0
  32. package/dist/lib/scores.js.map +1 -0
  33. package/dist/lib/search.d.ts +199 -0
  34. package/dist/lib/search.d.ts.map +1 -0
  35. package/dist/lib/search.js +357 -0
  36. package/dist/lib/search.js.map +1 -0
  37. package/dist/lib/taxonomy-counts.d.ts +25 -0
  38. package/dist/lib/taxonomy-counts.d.ts.map +1 -0
  39. package/dist/lib/taxonomy-counts.js +41 -0
  40. package/dist/lib/taxonomy-counts.js.map +1 -0
  41. package/package.json +7 -7
  42. package/src/components/AppSection.astro +83 -0
  43. package/src/components/AppsIndexRow.astro +204 -0
  44. package/src/components/AppsPagination.astro +126 -0
  45. package/src/components/CategoryGrid.astro +86 -17
  46. package/src/components/ContributorsGrid.astro +149 -0
  47. package/src/components/DecisionRow.astro +89 -0
  48. package/src/components/ExploreByCategory.astro +67 -0
  49. package/src/components/ExploreByStack.astro +72 -0
  50. package/src/components/Hero.astro +246 -0
  51. package/src/components/Icon.astro +223 -0
  52. package/src/components/ItemCard.astro +289 -30
  53. package/src/components/MinimalAbout.astro +82 -0
  54. package/src/components/OriginalCollection.astro +116 -0
  55. package/src/components/RefinePanel.astro +197 -0
  56. package/src/components/ScoreBars.astro +92 -22
  57. package/src/components/SmartLensTabs.astro +71 -0
  58. package/src/components/StackGrid.astro +132 -0
  59. package/src/components/WhyThisExists.astro +116 -0
  60. package/src/index.ts +61 -14
  61. package/src/layouts/BaseLayout.astro +227 -15
  62. package/src/layouts/Container.astro +57 -0
  63. package/src/layouts/Footer.astro +235 -0
  64. package/src/layouts/Header.astro +159 -0
  65. package/src/layouts/SectionHeader.astro +58 -0
  66. package/{templates/default/src/components/layout → src/layouts}/Seo.astro +62 -38
  67. package/src/layouts/ThemeToggle.astro +110 -0
  68. package/src/layouts/astro-shim.d.ts +29 -0
  69. package/src/layouts/index.ts +24 -0
  70. package/src/lib/display.ts +118 -0
  71. package/src/lib/format.ts +65 -0
  72. package/src/lib/index.ts +26 -0
  73. package/src/lib/lenses.ts +241 -0
  74. package/src/lib/repo.ts +44 -0
  75. package/src/lib/scores.ts +94 -0
  76. package/src/lib/search.ts +481 -0
  77. package/src/lib/taxonomy-counts.ts +60 -0
  78. package/src/styles.css +366 -638
  79. package/templates/default/.astro/content.d.ts +155 -0
  80. package/templates/default/.astro/settings.json +5 -0
  81. package/templates/default/.astro/types.d.ts +2 -0
  82. package/templates/default/astro.config.mjs +8 -6
  83. package/templates/default/content/pages/about.md +66 -0
  84. package/templates/default/content/pages/methodology.md +103 -0
  85. package/templates/default/data/generated/records.full.json +8 -0
  86. package/templates/default/data/generated/records.index.json +7 -0
  87. package/templates/default/data/generated/records.json +8 -0
  88. package/templates/default/data/generated/site-config.json +31 -0
  89. package/templates/default/data/records/.gitkeep +0 -0
  90. package/templates/default/dist/_astro/BaseLayout.DfpwR3jc.css +1 -0
  91. package/templates/default/dist/about/index.html +165 -0
  92. package/templates/default/dist/icons/platforms/android.svg +1 -0
  93. package/templates/default/dist/icons/platforms/apple-dark.svg +1 -0
  94. package/templates/default/dist/icons/platforms/apple-light.svg +1 -0
  95. package/templates/default/dist/icons/platforms/chrome.svg +1 -0
  96. package/templates/default/dist/icons/platforms/chromeos.svg +5 -0
  97. package/templates/default/dist/icons/platforms/desktop.svg +4 -0
  98. package/templates/default/dist/icons/platforms/embedded.svg +5 -0
  99. package/templates/default/dist/icons/platforms/ios.svg +1 -0
  100. package/templates/default/dist/icons/platforms/linux.svg +1 -0
  101. package/templates/default/dist/icons/platforms/macos.svg +1 -0
  102. package/templates/default/dist/icons/platforms/ubuntu.svg +1 -0
  103. package/templates/default/dist/icons/platforms/web.svg +5 -0
  104. package/templates/default/dist/icons/platforms/windows.svg +6 -0
  105. package/templates/default/dist/icons/stacks/android.svg +1 -0
  106. package/templates/default/dist/icons/stacks/apple-dark.svg +1 -0
  107. package/templates/default/dist/icons/stacks/apple-light.svg +1 -0
  108. package/templates/default/dist/icons/stacks/capacitor.svg +14 -0
  109. package/templates/default/dist/icons/stacks/dart.svg +1 -0
  110. package/templates/default/dist/icons/stacks/firebase.svg +1 -0
  111. package/templates/default/dist/icons/stacks/flutter.svg +1 -0
  112. package/templates/default/dist/icons/stacks/graphql.svg +1 -0
  113. package/templates/default/dist/icons/stacks/ionic.svg +4 -0
  114. package/templates/default/dist/icons/stacks/java.svg +1 -0
  115. package/templates/default/dist/icons/stacks/javascript.svg +1 -0
  116. package/templates/default/dist/icons/stacks/kotlin.svg +1 -0
  117. package/templates/default/dist/icons/stacks/mongodb.svg +1 -0
  118. package/templates/default/dist/icons/stacks/nodejs.svg +1 -0
  119. package/templates/default/dist/icons/stacks/python.svg +1 -0
  120. package/templates/default/dist/icons/stacks/react-native.svg +1 -0
  121. package/templates/default/dist/icons/stacks/react.svg +1 -0
  122. package/templates/default/dist/icons/stacks/rust.svg +1 -0
  123. package/templates/default/dist/icons/stacks/solidity.svg +1 -0
  124. package/templates/default/dist/icons/stacks/swift.svg +1 -0
  125. package/templates/default/dist/icons/stacks/tensorflow.svg +1 -0
  126. package/templates/default/dist/icons/stacks/typescript.svg +1 -0
  127. package/templates/default/dist/index.html +55 -0
  128. package/templates/default/dist/llms-full.txt +14 -0
  129. package/templates/default/dist/llms.txt +11 -0
  130. package/templates/default/dist/og-image.svg +48 -0
  131. package/templates/default/dist/projects/index.html +61 -0
  132. package/templates/default/dist/robots.txt +53 -0
  133. package/templates/default/dist/sitemap.xml +21 -0
  134. package/templates/default/dist/submit/index.html +69 -0
  135. package/templates/default/grove.config.ts +36 -0
  136. package/templates/default/node_modules/.bin/astro +21 -0
  137. package/templates/default/node_modules/.bin/astro-check +21 -0
  138. package/templates/default/node_modules/.bin/grove +21 -0
  139. package/templates/default/node_modules/.bin/jiti +21 -0
  140. package/templates/default/node_modules/.bin/prettier +21 -0
  141. package/templates/default/node_modules/.bin/rollup +21 -0
  142. package/templates/default/node_modules/.bin/tsc +21 -0
  143. package/templates/default/node_modules/.bin/tsserver +21 -0
  144. package/templates/default/node_modules/.bin/tsx +21 -0
  145. package/templates/default/node_modules/.bin/vite +21 -0
  146. package/templates/default/node_modules/.bin/yaml +21 -0
  147. package/templates/default/node_modules/.vite/deps/_metadata.json +79 -0
  148. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +433 -0
  149. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +7 -0
  150. package/templates/default/node_modules/.vite/deps/astro___aria-query.js +5 -0
  151. package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
  152. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +5 -0
  153. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
  154. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +9 -0
  155. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +7 -0
  156. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +241 -0
  157. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +7 -0
  158. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +1592 -0
  159. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +7 -0
  160. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
  161. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
  162. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +186 -0
  163. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +7 -0
  164. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +48 -0
  165. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +7 -0
  166. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +6779 -0
  167. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +7 -0
  168. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +33 -0
  169. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +7 -0
  170. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +88 -0
  171. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +7 -0
  172. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +72 -0
  173. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +7 -0
  174. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +3757 -0
  175. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +7 -0
  176. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +54 -0
  177. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +7 -0
  178. package/templates/default/node_modules/.vite/deps/package.json +3 -0
  179. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +213 -0
  180. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +7 -0
  181. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +525 -0
  182. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +7 -0
  183. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +1020 -0
  184. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +7 -0
  185. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +147 -0
  186. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +7 -0
  187. package/templates/default/package.json +6 -5
  188. package/templates/default/public/llms-full.txt +14 -0
  189. package/templates/default/public/llms.txt +11 -0
  190. package/templates/default/src/components/layout/BaseLayout.astro +10 -57
  191. package/templates/default/src/components/layout/Footer.astro +16 -134
  192. package/templates/default/src/components/layout/Header.astro +25 -126
  193. package/templates/default/src/data/records.ts +161 -0
  194. package/templates/default/src/lib/markdown.ts +171 -0
  195. package/templates/default/src/pages/about.astro +95 -429
  196. package/templates/default/src/pages/index.astro +258 -61
  197. package/templates/default/src/pages/projects/[slug].astro +480 -0
  198. package/templates/default/src/pages/projects/index.astro +193 -0
  199. package/templates/default/src/pages/sitemap.xml.ts +73 -61
  200. package/templates/default/src/pages/submit.astro +122 -411
  201. package/templates/default/src/styles/global.css +42 -68
  202. package/src/components/ActiveFilterChips.astro +0 -20
  203. package/src/components/DirectoryFilters.astro +0 -100
  204. package/src/components/DirectoryHero.astro +0 -32
  205. package/src/components/HealthBadge.astro +0 -12
  206. package/src/components/LensTabs.astro +0 -26
  207. package/src/components/MethodologyPanel.astro +0 -20
  208. package/src/components/Pagination.astro +0 -24
  209. package/src/components/ProjectDetail.astro +0 -166
  210. package/src/components/SubmitDraft.astro +0 -137
  211. package/templates/default/src/components/AppCard.astro +0 -248
  212. package/templates/default/src/components/AppSection.astro +0 -58
  213. package/templates/default/src/components/AppsIndexRow.astro +0 -230
  214. package/templates/default/src/components/AppsPagination.astro +0 -147
  215. package/templates/default/src/components/CategoryGrid.astro +0 -80
  216. package/templates/default/src/components/ContributorsGrid.astro +0 -105
  217. package/templates/default/src/components/DecisionRow.astro +0 -219
  218. package/templates/default/src/components/ExploreByCategory.astro +0 -74
  219. package/templates/default/src/components/ExploreByStack.astro +0 -102
  220. package/templates/default/src/components/Hero.astro +0 -176
  221. package/templates/default/src/components/MinimalAbout.astro +0 -57
  222. package/templates/default/src/components/OriginalCollection.astro +0 -144
  223. package/templates/default/src/components/RefinePanel.astro +0 -272
  224. package/templates/default/src/components/ScoreBars.astro +0 -72
  225. package/templates/default/src/components/SmartLensTabs.astro +0 -65
  226. package/templates/default/src/components/StackGrid.astro +0 -95
  227. package/templates/default/src/components/WhyThisExists.astro +0 -81
  228. package/templates/default/src/components/icons/Icon.astro +0 -148
  229. package/templates/default/src/components/layout/SectionHeader.astro +0 -42
  230. package/templates/default/src/components/layout/ThemeToggle.astro +0 -97
  231. package/templates/default/src/data/apps.ts +0 -149
  232. package/templates/default/src/data/categories.ts +0 -27
  233. package/templates/default/src/data/config.ts +0 -28
  234. package/templates/default/src/data/contributors.ts +0 -96
  235. package/templates/default/src/data/stacks.ts +0 -47
  236. package/templates/default/src/data/stats.ts +0 -65
  237. package/templates/default/src/data/types.ts +0 -248
  238. package/templates/default/src/lib/apps-search.ts +0 -228
  239. package/templates/default/src/lib/format.ts +0 -36
  240. package/templates/default/src/lib/lenses.ts +0 -98
  241. package/templates/default/src/lib/repo.ts +0 -12
  242. package/templates/default/src/lib/scores.ts +0 -51
  243. package/templates/default/src/lib/taxonomy-counts.ts +0 -31
  244. package/templates/default/src/pages/apps/[slug].astro +0 -709
  245. package/templates/default/src/pages/apps/index.astro +0 -669
  246. package/templates/default/src/pages/contributors.astro +0 -142
@@ -1,20 +0,0 @@
1
- ---
2
- import type { DirectoryFilters } from "@grove-dev/ui";
3
- import { activeFilterChips } from "@grove-dev/ui";
4
-
5
- interface Props {
6
- filters: DirectoryFilters;
7
- }
8
-
9
- const { filters } = Astro.props;
10
- const chips = activeFilterChips(filters);
11
- ---
12
-
13
- {chips.length > 0 && (
14
- <div class="oc-active-chips" aria-label="Active filters">
15
- {chips.map((chip) => (
16
- <span data-filter-chip={chip.key}>{chip.label}</span>
17
- ))}
18
- <a href="/projects">Clear all</a>
19
- </div>
20
- )}
@@ -1,100 +0,0 @@
1
- ---
2
- import type { HealthStatus } from "@grove-dev/core";
3
-
4
- interface Props {
5
- q?: string;
6
- category?: string;
7
- tag?: string;
8
- language?: string;
9
- license?: string;
10
- health?: string;
11
- maintained?: boolean;
12
- hideArchived?: boolean;
13
- hasRecentRelease?: boolean;
14
- categories: string[];
15
- tags: string[];
16
- languages: string[];
17
- licenses: string[];
18
- action?: string;
19
- }
20
-
21
- const {
22
- q = "",
23
- category = "",
24
- tag = "",
25
- language = "",
26
- license = "",
27
- health = "all",
28
- maintained = false,
29
- hideArchived = false,
30
- hasRecentRelease = false,
31
- categories,
32
- tags,
33
- languages,
34
- licenses,
35
- action = "/projects",
36
- } = Astro.props;
37
-
38
- const healthOptions: Array<HealthStatus | "all"> = [
39
- "all",
40
- "mature",
41
- "active",
42
- "stale",
43
- "needs_review",
44
- "inactive",
45
- "archived",
46
- "unknown",
47
- ];
48
- ---
49
-
50
- <form class="oc-filters" action={action} method="get" role="search">
51
- <label class="oc-filter-search">
52
- <span>Search</span>
53
- <input type="search" name="q" value={q} placeholder="Name, description, tag, license..." />
54
- </label>
55
- <label>
56
- <span>Category</span>
57
- <select name="category">
58
- <option value="">All categories</option>
59
- {categories.map((value) => <option value={value} selected={value === category}>{value}</option>)}
60
- </select>
61
- </label>
62
- <label>
63
- <span>Health</span>
64
- <select name="health">
65
- {healthOptions.map((value) => (
66
- <option value={value} selected={value === health}>{value.replace(/_/g, " ")}</option>
67
- ))}
68
- </select>
69
- </label>
70
- <label>
71
- <span>Tag</span>
72
- <select name="tag">
73
- <option value="">All tags</option>
74
- {tags.map((value) => <option value={value} selected={value === tag}>{value}</option>)}
75
- </select>
76
- </label>
77
- <label>
78
- <span>Language</span>
79
- <select name="language">
80
- <option value="">All languages</option>
81
- {languages.map((value) => <option value={value} selected={value === language}>{value}</option>)}
82
- </select>
83
- </label>
84
- <label>
85
- <span>License</span>
86
- <select name="license">
87
- <option value="">All licenses</option>
88
- {licenses.map((value) => <option value={value} selected={value === license}>{value}</option>)}
89
- </select>
90
- </label>
91
- <div class="oc-filter-toggles">
92
- <label><input type="checkbox" name="maintained" value="1" checked={maintained} /> Maintained only</label>
93
- <label><input type="checkbox" name="hideArchived" value="1" checked={hideArchived} /> Hide archived</label>
94
- <label><input type="checkbox" name="hasRecentRelease" value="1" checked={hasRecentRelease} /> Recent release</label>
95
- </div>
96
- <div class="oc-filter-actions">
97
- <button type="submit">Apply</button>
98
- <a href={action}>Reset</a>
99
- </div>
100
- </form>
@@ -1,32 +0,0 @@
1
- ---
2
- import type { DirectoryStats } from "@grove-dev/ui";
3
-
4
- interface Props {
5
- name: string;
6
- tagline: string;
7
- stats: DirectoryStats;
8
- }
9
-
10
- const { name, tagline, stats } = Astro.props;
11
- ---
12
-
13
- <section class="oc-hero">
14
- <div>
15
- <p class="oc-eyebrow">Grove</p>
16
- <h1>{name}</h1>
17
- <p class="oc-lede">{tagline}</p>
18
- </div>
19
- <form class="oc-hero-search" action="/projects" role="search">
20
- <label>
21
- <span class="oc-sr-only">Search projects</span>
22
- <input type="search" name="q" placeholder="Search projects, tags, licenses..." />
23
- </label>
24
- <button type="submit">Search</button>
25
- </form>
26
- <dl class="oc-stats">
27
- <div><dt>Projects</dt><dd>{stats.total}</dd></div>
28
- <div><dt>Active</dt><dd>{stats.active}</dd></div>
29
- <div><dt>Mature</dt><dd>{stats.mature}</dd></div>
30
- <div><dt>Needs review</dt><dd>{stats.needsReview}</dd></div>
31
- </dl>
32
- </section>
@@ -1,12 +0,0 @@
1
- ---
2
- import type { HealthStatus } from "@grove-dev/core";
3
-
4
- interface Props {
5
- status?: HealthStatus;
6
- }
7
-
8
- const { status = "unknown" } = Astro.props;
9
- const label = status.replace(/_/g, " ");
10
- ---
11
-
12
- <span class={`oc-health oc-health-${status}`}>{label}</span>
@@ -1,26 +0,0 @@
1
- ---
2
- import { LENSES } from "@grove-dev/ui";
3
-
4
- interface Props {
5
- current?: string;
6
- action?: string;
7
- }
8
-
9
- const { current = "all", action = "/projects" } = Astro.props;
10
- ---
11
-
12
- <nav class="oc-lenses" aria-label="Curated lenses">
13
- {LENSES.map((lens) => {
14
- const params = new URLSearchParams();
15
- for (const [key, value] of Object.entries(lens.params)) {
16
- params.set(key, String(value));
17
- }
18
- const href = lens.id === "all" ? action : `${action}?${params.toString()}`;
19
- const active = current === lens.id || (current === "all" && lens.id === "all");
20
- return (
21
- <a href={href} data-lens-id={lens.id} aria-current={active ? "page" : undefined} title={lens.description}>
22
- {lens.label}
23
- </a>
24
- );
25
- })}
26
- </nav>
@@ -1,20 +0,0 @@
1
- ---
2
- ---
3
-
4
- <section class="oc-methodology">
5
- <div>
6
- <p class="oc-eyebrow">Methodology</p>
7
- <h2>Health is a signal, not an automatic deletion rule.</h2>
8
- <p>
9
- Grove checks repository activity, archived state, releases, license,
10
- adoption, and metadata freshness. Projects can still be kept, highlighted,
11
- hidden, or preserved as historical through human curation decisions.
12
- </p>
13
- </div>
14
- <ul>
15
- <li>Archived repositories are flagged clearly.</li>
16
- <li>Recent commits and releases increase confidence.</li>
17
- <li>Missing metadata becomes unknown or needs review.</li>
18
- <li>Humans control final visibility in decisions.yml.</li>
19
- </ul>
20
- </section>
@@ -1,24 +0,0 @@
1
- ---
2
- interface Props {
3
- page: number;
4
- pages: number;
5
- base?: string;
6
- }
7
-
8
- const { page, pages, base = "/projects" } = Astro.props;
9
-
10
- function href(target: number) {
11
- const current = new URL(Astro.url);
12
- current.searchParams.set("page", String(target));
13
- const query = current.searchParams.toString();
14
- return `${base}${query ? `?${query}` : ""}`;
15
- }
16
- ---
17
-
18
- {pages > 1 && (
19
- <nav class="oc-pagination" aria-label="Pagination">
20
- <a href={href(Math.max(1, page - 1))} aria-disabled={page <= 1 ? "true" : undefined}>Previous</a>
21
- <span>Page {page} of {pages}</span>
22
- <a href={href(Math.min(pages, page + 1))} aria-disabled={page >= pages ? "true" : undefined}>Next</a>
23
- </nav>
24
- )}
@@ -1,166 +0,0 @@
1
- ---
2
- import type { CuratedItem, HealthEntry, Decision } from "@grove-dev/core";
3
- import HealthBadge from "./HealthBadge.astro";
4
- import ScoreBars from "./ScoreBars.astro";
5
- import { SCORE_LABELS, SCORE_REASONING } from "@grove-dev/ui";
6
-
7
- interface Props {
8
- item: CuratedItem;
9
- health?: HealthEntry;
10
- decision?: Decision;
11
- }
12
-
13
- const { item, health, decision } = Astro.props;
14
- const links = Object.entries(item.links).filter(([, href]) => Boolean(href));
15
- const channels = item.distribution.channels ?? [];
16
- const languages = health?.github?.languages
17
- ? Object.entries(health.github.languages).sort((a, b) => b[1] - a[1]).slice(0, 5)
18
- : [];
19
- const totalLanguageBytes = Math.max(1, languages.reduce((sum, [, bytes]) => sum + bytes, 0));
20
- const files = health?.github?.files ?? {};
21
- const fileSignals = [
22
- ["README", files.readme],
23
- ["Contributing", files.contributing],
24
- ["Security", files.security],
25
- ["Issue templates", files.issueTemplates],
26
- ["PR template", files.pullRequestTemplate],
27
- ].filter(([, ok]) => ok !== undefined);
28
- const monthly = health?.github?.monthlyCommits ?? [];
29
- const maxCommits = Math.max(1, ...monthly.map((entry) => entry.commits));
30
- ---
31
-
32
- <article class="oc-detail">
33
- <header class="oc-detail-header">
34
- <div>
35
- <p class="oc-eyebrow">{item.taxonomy.category}</p>
36
- <h1>{item.name}</h1>
37
- <p class="oc-lede">{item.description}</p>
38
- </div>
39
- <HealthBadge status={health?.health.status} />
40
- </header>
41
-
42
- <div class="oc-detail-grid">
43
- <section class="oc-detail-wide">
44
- <h2>Curation Scores</h2>
45
- <ScoreBars scores={item.curation.scores} />
46
- <dl class="oc-score-reasons">
47
- {(Object.keys(SCORE_LABELS) as Array<keyof typeof SCORE_LABELS>).map((key) => (
48
- <div>
49
- <dt>{SCORE_LABELS[key]}</dt>
50
- <dd>{SCORE_REASONING[key]}</dd>
51
- </div>
52
- ))}
53
- </dl>
54
- </section>
55
- <section>
56
- <h2>Links</h2>
57
- <ul class="oc-link-list">
58
- {links.map(([label, href]) => <li><a href={href}>{label}</a></li>)}
59
- </ul>
60
- {channels.length > 0 && (
61
- <>
62
- <h3>Distribution</h3>
63
- <ul class="oc-link-list">
64
- {channels.map((channel) => (
65
- <li><a href={channel.url}>{channel.label ?? channel.type}</a>{channel.platform ? ` · ${channel.platform}` : ""}</li>
66
- ))}
67
- </ul>
68
- </>
69
- )}
70
- </section>
71
- <section>
72
- <h2>Metadata</h2>
73
- <dl class="oc-fact-list">
74
- <div><dt>Stars</dt><dd>{health?.github?.stars?.toLocaleString() ?? "Unknown"}</dd></div>
75
- <div><dt>Forks</dt><dd>{health?.github?.forks?.toLocaleString() ?? "Unknown"}</dd></div>
76
- <div><dt>License</dt><dd>{health?.github?.license ?? "Unknown"}</dd></div>
77
- <div><dt>Language</dt><dd>{item.taxonomy.language ?? health?.github?.language ?? "Unknown"}</dd></div>
78
- <div><dt>Last activity</dt><dd>{health?.github?.pushedAt?.slice(0, 10) ?? "Unknown"}</dd></div>
79
- <div><dt>Latest release</dt><dd>{health?.github?.latestReleaseAt?.slice(0, 10) ?? "Unknown"}</dd></div>
80
- <div><dt>Contributors</dt><dd>{health?.github?.contributorsKnown?.toLocaleString() ?? "Unknown"}</dd></div>
81
- <div><dt>Open PRs</dt><dd>{health?.github?.openPullRequests?.toLocaleString() ?? "Unknown"}</dd></div>
82
- </dl>
83
- </section>
84
- <section>
85
- <h2>Health Signals</h2>
86
- <ul class="oc-signal-list">
87
- {(health?.health.reasons ?? ["No health metadata available"]).map((reason) => <li>{reason}</li>)}
88
- </ul>
89
- </section>
90
- <section>
91
- <h2>Curation</h2>
92
- {decision ? (
93
- <p><strong>{decision.decision.visibility.replace(/_/g, " ")}</strong>: {decision.decision.reason}</p>
94
- ) : (
95
- <p>No manual curation decision has been recorded yet.</p>
96
- )}
97
- {item.curation.reviewed !== undefined && (
98
- <p>{item.curation.reviewed ? "Curator reviewed" : "Generated curation pending review"}{item.curation.reviewedBy ? ` by ${item.curation.reviewedBy}` : ""}.</p>
99
- )}
100
- {item.source.file && <p class="oc-source">Source: {item.source.file}{item.source.line ? `:${item.source.line}` : ""}</p>}
101
- </section>
102
- {(item.curation.bestFor.length > 0 || item.curation.whyListed.length > 0 || item.curation.caveats.length > 0) && (
103
- <section class="oc-detail-wide">
104
- <h2>Why Included</h2>
105
- <div class="oc-curation-columns">
106
- {item.curation.bestFor.length > 0 && (
107
- <div>
108
- <h3>Best for</h3>
109
- <ul class="oc-signal-list">{item.curation.bestFor.map((value) => <li>{value}</li>)}</ul>
110
- </div>
111
- )}
112
- {item.curation.whyListed.length > 0 && (
113
- <div>
114
- <h3>Why listed</h3>
115
- <ul class="oc-signal-list">{item.curation.whyListed.map((value) => <li>{value}</li>)}</ul>
116
- </div>
117
- )}
118
- {item.curation.caveats.length > 0 && (
119
- <div>
120
- <h3>Caveats</h3>
121
- <ul class="oc-signal-list">{item.curation.caveats.map((value) => <li>{value}</li>)}</ul>
122
- </div>
123
- )}
124
- </div>
125
- </section>
126
- )}
127
- {(languages.length > 0 || fileSignals.length > 0 || monthly.length > 0) && (
128
- <section class="oc-detail-wide">
129
- <h2>Repository Signals</h2>
130
- <div class="oc-repo-signals">
131
- {languages.length > 0 && (
132
- <div>
133
- <h3>Languages</h3>
134
- <dl class="oc-language-bars">
135
- {languages.map(([language, bytes]) => (
136
- <div>
137
- <dt>{language}</dt>
138
- <dd><span style={`width: ${(bytes / totalLanguageBytes) * 100}%`}></span></dd>
139
- </div>
140
- ))}
141
- </dl>
142
- </div>
143
- )}
144
- {fileSignals.length > 0 && (
145
- <div>
146
- <h3>Contribution files</h3>
147
- <ul class="oc-signal-list">
148
- {fileSignals.map(([label, ok]) => <li>{ok ? "Found" : "Missing"}: {label}</li>)}
149
- </ul>
150
- </div>
151
- )}
152
- {monthly.length > 0 && (
153
- <div>
154
- <h3>Monthly activity</h3>
155
- <div class="oc-activity-bars">
156
- {monthly.map((entry) => (
157
- <span title={`${entry.month}: ${entry.commits} commits`} style={`height: ${Math.max(8, (entry.commits / maxCommits) * 48)}px`}></span>
158
- ))}
159
- </div>
160
- </div>
161
- )}
162
- </div>
163
- </section>
164
- )}
165
- </div>
166
- </article>
@@ -1,137 +0,0 @@
1
- ---
2
- interface Props {
3
- repoUrl?: string;
4
- }
5
-
6
- const { repoUrl = "" } = Astro.props;
7
- ---
8
-
9
- <section class="oc-submit-grid">
10
- <form class="oc-submit-form" id="submit-form">
11
- <label>
12
- <span>GitHub URL</span>
13
- <input id="repo-url" type="url" required placeholder="https://github.com/owner/repo" value={repoUrl} />
14
- </label>
15
- <div class="oc-two-col">
16
- <label><span>Name</span><input id="project-name" type="text" /></label>
17
- <label><span>Slug</span><input id="project-slug" type="text" /></label>
18
- </div>
19
- <label><span>Description</span><textarea id="project-description" rows="3"></textarea></label>
20
- <div class="oc-two-col">
21
- <label><span>Category</span><input id="project-category" type="text" placeholder="Developer Tools" /></label>
22
- <label><span>Language</span><input id="project-language" type="text" placeholder="TypeScript" /></label>
23
- </div>
24
- <label><span>Tags</span><input id="project-tags" type="text" placeholder="cli, static-site, search" /></label>
25
- <label><span>Best for</span><textarea id="project-best-for" rows="3" placeholder="One line per use case"></textarea></label>
26
- </form>
27
- <aside class="oc-submit-preview">
28
- <div class="oc-section-header">
29
- <h2>YAML draft</h2>
30
- <button type="button" id="copy-yaml">Copy</button>
31
- </div>
32
- <pre><code id="yaml-preview">Paste a GitHub URL to generate a draft.</code></pre>
33
- <p id="submit-status" aria-live="polite"></p>
34
- </aside>
35
- </section>
36
-
37
- <script>
38
- type FormField = HTMLInputElement | HTMLTextAreaElement | HTMLElement | null;
39
-
40
- const fields: Record<string, FormField> = {
41
- repoUrl: document.querySelector("#repo-url"),
42
- name: document.querySelector("#project-name"),
43
- slug: document.querySelector("#project-slug"),
44
- description: document.querySelector("#project-description"),
45
- category: document.querySelector("#project-category"),
46
- language: document.querySelector("#project-language"),
47
- tags: document.querySelector("#project-tags"),
48
- bestFor: document.querySelector("#project-best-for"),
49
- preview: document.querySelector("#yaml-preview"),
50
- status: document.querySelector("#submit-status"),
51
- copy: document.querySelector("#copy-yaml"),
52
- };
53
-
54
- function valueOf(name: string): string {
55
- const el = fields[name];
56
- if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) return el.value;
57
- return "";
58
- }
59
-
60
- function setText(name: string, text: string): void {
61
- const el = fields[name];
62
- if (el) el.textContent = text;
63
- }
64
-
65
- function slugify(value: string): string {
66
- return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
67
- }
68
-
69
- function parseRepo(value: string): { owner: string; repo: string } | null {
70
- const match = String(value).match(/github\.com\/([^/]+)\/([^/?#]+)/);
71
- if (!match) return null;
72
- return { owner: match[1], repo: match[2].replace(/\.git$/, "") };
73
- }
74
-
75
- function list(values: string[], indent: number): string {
76
- return values.length
77
- ? values.map((v) => `${" ".repeat(indent)}- ${v}`).join("\n")
78
- : `${" ".repeat(indent)}[]`;
79
- }
80
-
81
- function quote(value: string): string {
82
- return `"${String(value ?? "").replace(/"/g, '\\"')}"`;
83
- }
84
-
85
- function generate(): void {
86
- const repo = parseRepo(valueOf("repoUrl"));
87
- if (!repo) {
88
- setText("preview", "Paste a valid GitHub repository URL.");
89
- return;
90
- }
91
- const name = valueOf("name") || repo.repo;
92
- const slug = valueOf("slug") || slugify(name);
93
- const tags = valueOf("tags")
94
- .split(",")
95
- .map((t) => t.trim())
96
- .filter(Boolean);
97
- const bestFor = valueOf("bestFor")
98
- .split("\n")
99
- .map((l) => l.trim())
100
- .filter(Boolean);
101
- const yaml = [
102
- `id: ${slug}`,
103
- `name: ${quote(name)}`,
104
- `description: ${quote(valueOf("description") || name)}`,
105
- "links:",
106
- ` github: https://github.com/${repo.owner}/${repo.repo}`,
107
- "source:",
108
- " type: manual",
109
- "taxonomy:",
110
- ` category: ${quote(valueOf("category") || "uncategorized")}`,
111
- " tags:",
112
- list(tags, 4),
113
- ...(valueOf("language") ? [` language: ${quote(valueOf("language"))}`] : []),
114
- "curation:",
115
- " bestFor:",
116
- list(bestFor, 4),
117
- " whyListed: []",
118
- " caveats: []",
119
- ].join("\n");
120
- setText("preview", yaml);
121
- }
122
-
123
- for (const input of Object.values(fields)) {
124
- if (input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement) {
125
- input.addEventListener("input", generate);
126
- }
127
- }
128
- const copyBtn = fields.copy;
129
- if (copyBtn) {
130
- copyBtn.addEventListener("click", async () => {
131
- const previewEl = fields.preview;
132
- await navigator.clipboard.writeText(previewEl?.textContent ?? "");
133
- setText("status", "Copied YAML draft.");
134
- });
135
- }
136
- generate();
137
- </script>