@grove-dev/astro 0.2.10 → 0.2.14
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/README.md +26 -18
- 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 +12 -22
- package/dist/lib/repo.d.ts.map +1 -1
- package/dist/lib/repo.js +13 -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 +44 -129
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +192 -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/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/IndexRow.astro +75 -0
- package/src/components/ItemCard.astro +266 -248
- package/src/components/Pagination.astro +63 -0
- package/src/components/RecordSection.astro +70 -34
- 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 +17 -28
- package/src/lib/scores.ts +15 -19
- package/src/lib/search.ts +218 -291
- package/src/lib/taxonomy-counts.ts +12 -46
- package/src/styles.css +209 -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 +138 -51
- package/templates/default/src/pages/404.astro +64 -0
- package/templates/default/src/pages/{projects/[slug].astro → [slug]/[recordSlug].astro} +236 -66
- package/templates/default/src/pages/[slug]/index.astro +477 -0
- package/templates/default/src/pages/about.astro +165 -82
- package/templates/default/src/pages/apps/[recordSlug].astro +33 -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/src/components/AppsIndexRow.astro +0 -204
- package/src/components/AppsPagination.astro +0 -126
- package/templates/default/.astro/content.d.ts +0 -155
- package/templates/default/.astro/settings.json +0 -5
- package/templates/default/.astro/types.d.ts +0 -1
- 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/dist/_astro/BaseLayout.Dz_HR-68.css +0 -1
- package/templates/default/dist/about/index.html +0 -165
- package/templates/default/dist/icons/platforms/android.svg +0 -1
- package/templates/default/dist/icons/platforms/apple-dark.svg +0 -1
- package/templates/default/dist/icons/platforms/apple-light.svg +0 -1
- package/templates/default/dist/icons/platforms/chrome.svg +0 -1
- package/templates/default/dist/icons/platforms/chromeos.svg +0 -5
- package/templates/default/dist/icons/platforms/desktop.svg +0 -4
- package/templates/default/dist/icons/platforms/embedded.svg +0 -5
- package/templates/default/dist/icons/platforms/ios.svg +0 -1
- package/templates/default/dist/icons/platforms/linux.svg +0 -1
- package/templates/default/dist/icons/platforms/macos.svg +0 -1
- package/templates/default/dist/icons/platforms/ubuntu.svg +0 -1
- package/templates/default/dist/icons/platforms/web.svg +0 -5
- package/templates/default/dist/icons/platforms/windows.svg +0 -6
- package/templates/default/dist/icons/stacks/android.svg +0 -1
- package/templates/default/dist/icons/stacks/apple-dark.svg +0 -1
- package/templates/default/dist/icons/stacks/apple-light.svg +0 -1
- package/templates/default/dist/icons/stacks/capacitor.svg +0 -14
- package/templates/default/dist/icons/stacks/dart.svg +0 -1
- package/templates/default/dist/icons/stacks/firebase.svg +0 -1
- package/templates/default/dist/icons/stacks/flutter.svg +0 -1
- package/templates/default/dist/icons/stacks/graphql.svg +0 -1
- package/templates/default/dist/icons/stacks/ionic.svg +0 -4
- package/templates/default/dist/icons/stacks/java.svg +0 -1
- package/templates/default/dist/icons/stacks/javascript.svg +0 -1
- package/templates/default/dist/icons/stacks/kotlin.svg +0 -1
- package/templates/default/dist/icons/stacks/mongodb.svg +0 -1
- package/templates/default/dist/icons/stacks/nodejs.svg +0 -1
- package/templates/default/dist/icons/stacks/python.svg +0 -1
- package/templates/default/dist/icons/stacks/react-native.svg +0 -1
- package/templates/default/dist/icons/stacks/react.svg +0 -1
- package/templates/default/dist/icons/stacks/rust.svg +0 -1
- package/templates/default/dist/icons/stacks/solidity.svg +0 -1
- package/templates/default/dist/icons/stacks/swift.svg +0 -1
- package/templates/default/dist/icons/stacks/tensorflow.svg +0 -1
- package/templates/default/dist/icons/stacks/typescript.svg +0 -1
- package/templates/default/dist/index.html +0 -55
- package/templates/default/dist/llms-full.txt +0 -14
- package/templates/default/dist/llms.txt +0 -11
- package/templates/default/dist/og-image.svg +0 -48
- package/templates/default/dist/projects/index.html +0 -61
- package/templates/default/dist/robots.txt +0 -53
- package/templates/default/dist/sitemap.xml +0 -21
- package/templates/default/dist/submit/index.html +0 -69
- package/templates/default/node_modules/.bin/astro +0 -21
- package/templates/default/node_modules/.bin/astro-check +0 -21
- package/templates/default/node_modules/.bin/grove +0 -21
- package/templates/default/node_modules/.bin/jiti +0 -21
- package/templates/default/node_modules/.bin/prettier +0 -21
- package/templates/default/node_modules/.bin/rollup +0 -21
- package/templates/default/node_modules/.bin/tsc +0 -21
- package/templates/default/node_modules/.bin/tsserver +0 -21
- package/templates/default/node_modules/.bin/tsx +0 -21
- package/templates/default/node_modules/.bin/vite +0 -21
- package/templates/default/node_modules/.bin/yaml +0 -21
- package/templates/default/node_modules/.vite/deps/_metadata.json +0 -79
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +0 -433
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js +0 -5
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +0 -5
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +0 -9
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -241
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +0 -1592
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +0 -30
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +0 -186
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +0 -48
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +0 -6779
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +0 -33
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +0 -88
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +0 -72
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +0 -3757
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +0 -54
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/package.json +0 -3
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +0 -213
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +0 -525
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +0 -1020
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +0 -147
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +0 -7
- 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
|
@@ -1,127 +1,289 @@
|
|
|
1
1
|
---
|
|
2
|
-
/**
|
|
3
|
-
* Submit page — text-based instructions for contributing a new
|
|
4
|
-
* record. No form: the openapps 19K form preview is overkill for
|
|
5
|
-
* the default template, and a static "open a PR / open an issue"
|
|
6
|
-
* workflow matches how the showcase actually accepts submissions.
|
|
7
|
-
*/
|
|
8
2
|
import siteConfig from "../../data/generated/site-config.json";
|
|
9
|
-
import {
|
|
3
|
+
import { fullItems, itemLabel } from "../data/records";
|
|
10
4
|
import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
|
|
11
5
|
import Container from "@grove-dev/astro/layouts/Container.astro";
|
|
12
6
|
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
7
|
+
const singular = itemLabel();
|
|
8
|
+
const title = `Submit ${singular} - ${siteConfig.name}`;
|
|
9
|
+
const description = `Submit a new ${singular} to ${siteConfig.name}.`;
|
|
10
|
+
const repoUrl = siteConfig.repoUrl ?? "https://github.com/tortuvshin/grove";
|
|
17
11
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
const values = (key: "category" | "stack") =>
|
|
13
|
+
[...new Set(fullItems.map((item) => String(item[key] ?? "")).filter(Boolean))]
|
|
14
|
+
.sort((a, b) => a.localeCompare(b));
|
|
15
|
+
const categories = values("category");
|
|
16
|
+
const stacks = values("stack");
|
|
17
|
+
const existingSlugs = fullItems.map((item) => item.slug).filter(Boolean);
|
|
18
|
+
const existingFullNames = fullItems
|
|
19
|
+
.map((item) => {
|
|
20
|
+
const url = String(item.repoUrl ?? item.links?.github ?? "");
|
|
21
|
+
const match = url.match(/github\.com\/([^/]+)\/([^/?#]+)/i);
|
|
22
|
+
return match ? `${match[1]}/${match[2].replace(/\.git$/, "")}`.toLowerCase() : null;
|
|
23
|
+
})
|
|
24
|
+
.filter(Boolean);
|
|
25
|
+
|
|
26
|
+
const platformOptions = ["iOS", "Android", "Web", "macOS", "Windows", "Linux"];
|
|
27
|
+
const goodSubmissions = [
|
|
28
|
+
`A real, usable ${singular}, not a tutorial or one-screen demo`,
|
|
29
|
+
"A public repository with a clear open-source license",
|
|
30
|
+
"Enough documentation to understand, run, or contribute",
|
|
31
|
+
"Recent maintenance, releases, or meaningful project history",
|
|
32
|
+
];
|
|
33
|
+
const pleaseAvoid = [
|
|
34
|
+
"Snippets, exercises, abandoned experiments, or duplicate entries",
|
|
35
|
+
"Private repositories or projects without source",
|
|
36
|
+
"Marketing pages with no usable project behind them",
|
|
37
|
+
"Records with no license or verifiable repository",
|
|
38
|
+
];
|
|
22
39
|
---
|
|
23
40
|
|
|
24
|
-
<BaseLayout
|
|
25
|
-
title={title}
|
|
26
|
-
description={description}
|
|
27
|
-
site={siteConfig}
|
|
28
|
-
noindex={noindex}
|
|
29
|
-
>
|
|
41
|
+
<BaseLayout title={title} description={description} site={siteConfig} noindex>
|
|
30
42
|
<Container>
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
</p>
|
|
44
|
-
|
|
45
|
-
<div class="mt-6 flex flex-wrap gap-2">
|
|
46
|
-
<a
|
|
47
|
-
href={issueUrl}
|
|
48
|
-
target="_blank"
|
|
49
|
-
rel="noopener noreferrer"
|
|
50
|
-
class="grove-btn grove-btn-primary"
|
|
51
|
-
>
|
|
52
|
-
Open a submission issue ↗
|
|
53
|
-
</a>
|
|
54
|
-
{repoPath && (
|
|
55
|
-
<a
|
|
56
|
-
href={`https://github.com/${repoPath}/blob/main/.github/ISSUE_TEMPLATE/record_submission.md`}
|
|
57
|
-
target="_blank"
|
|
58
|
-
rel="noopener noreferrer"
|
|
59
|
-
class="grove-btn grove-btn-outline"
|
|
60
|
-
>
|
|
61
|
-
See the issue template
|
|
62
|
-
</a>
|
|
63
|
-
)}
|
|
43
|
+
<section class="py-10 sm:py-14">
|
|
44
|
+
<div class="max-w-3xl">
|
|
45
|
+
<p class="m-0 text-2xs font-semibold uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
46
|
+
{singular} submission
|
|
47
|
+
</p>
|
|
48
|
+
<h1 class="mt-2 text-[2rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
49
|
+
Add {singular === "app" ? "an" : "a"} {singular} to the directory
|
|
50
|
+
</h1>
|
|
51
|
+
<p class="m-0 mt-3 max-w-[60ch] text-[0.9375rem] leading-relaxed text-ink-500 dark:text-ink-400">
|
|
52
|
+
Paste a GitHub URL to pull in the basics, edit the generated draft,
|
|
53
|
+
then open a pull request. The data stays in this repository.
|
|
54
|
+
</p>
|
|
64
55
|
</div>
|
|
65
56
|
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
57
|
+
<ol class="mt-6 grid grid-cols-1 gap-2 text-2xs sm:grid-cols-3 sm:gap-3" aria-label="Submission steps">
|
|
58
|
+
{[
|
|
59
|
+
["1", "Paste a GitHub URL", "We'll pull the metadata automatically."],
|
|
60
|
+
["2", "Edit the draft", "Fix the category, stack, platforms, and notes."],
|
|
61
|
+
["3", "Open a pull request", "Reviewers will validate and merge the record."],
|
|
62
|
+
].map(([number, heading, copy]) => (
|
|
63
|
+
<li class="card flex items-start gap-2 p-3">
|
|
64
|
+
<span class="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full border border-ink-300 font-mono font-semibold text-ink-700 dark:border-ink-700 dark:text-ink-300">{number}</span>
|
|
65
|
+
<div>
|
|
66
|
+
<div class="font-medium text-ink-900 dark:text-ink-100">{heading}</div>
|
|
67
|
+
<div class="text-ink-500 dark:text-ink-400">{copy}</div>
|
|
68
|
+
</div>
|
|
69
|
+
</li>
|
|
70
|
+
))}
|
|
71
|
+
</ol>
|
|
72
|
+
|
|
73
|
+
<div class="mt-8 grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(360px,0.8fr)]">
|
|
74
|
+
<form id="submit-form" class="card p-5">
|
|
75
|
+
<div class="grid gap-5">
|
|
76
|
+
<label class="grid gap-1.5">
|
|
77
|
+
<span class="label">GitHub URL</span>
|
|
78
|
+
<div class="flex flex-col gap-2 sm:flex-row">
|
|
79
|
+
<input id="repo-url" type="url" required placeholder="https://github.com/owner/repo" class="field flex-1" />
|
|
80
|
+
<button id="fetch-repo" type="button" class="btn-primary min-h-10 justify-center">Generate draft</button>
|
|
81
|
+
</div>
|
|
82
|
+
<span class="help">The repository must be public. We never write to it.</span>
|
|
83
|
+
</label>
|
|
84
|
+
|
|
85
|
+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
86
|
+
<label class="grid gap-1.5"><span class="label">Name</span><input id="item-name" type="text" class="field" /></label>
|
|
87
|
+
<label class="grid gap-1.5"><span class="label">Slug</span><input id="item-slug" type="text" class="field font-mono" /><span class="help">URL fragment, auto-filled from the repository name.</span></label>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<label class="grid gap-1.5">
|
|
91
|
+
<span class="label">Description</span>
|
|
92
|
+
<textarea id="item-description" rows="3" class="field resize-y"></textarea>
|
|
93
|
+
<span class="help">One or two sentences in plain language.</span>
|
|
94
|
+
</label>
|
|
95
|
+
|
|
96
|
+
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
97
|
+
<label class="grid gap-1.5">
|
|
98
|
+
<span class="label">Category</span>
|
|
99
|
+
<input id="item-category" list="category-options" class="field" />
|
|
100
|
+
<datalist id="category-options">{categories.map((value) => <option value={value} />)}</datalist>
|
|
101
|
+
</label>
|
|
102
|
+
<label class="grid gap-1.5">
|
|
103
|
+
<span class="label">Primary stack</span>
|
|
104
|
+
<input id="primary-stack" list="stack-options" class="field" />
|
|
105
|
+
<datalist id="stack-options">{stacks.map((value) => <option value={value} />)}</datalist>
|
|
106
|
+
</label>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<fieldset class="grid gap-2">
|
|
110
|
+
<legend class="label">Platforms</legend>
|
|
111
|
+
<div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
|
112
|
+
{platformOptions.map((platform) => (
|
|
113
|
+
<label class="flex min-h-9 items-center gap-2 rounded-md border border-ink-200 px-2 text-sm dark:border-ink-800">
|
|
114
|
+
<input type="checkbox" name="platform" value={platform.toLowerCase()} class="h-4 w-4" />
|
|
115
|
+
<span>{platform}</span>
|
|
116
|
+
</label>
|
|
117
|
+
))}
|
|
118
|
+
</div>
|
|
119
|
+
</fieldset>
|
|
120
|
+
|
|
121
|
+
<label class="grid gap-1.5"><span class="label">Tags</span><input id="item-tags" type="text" placeholder="self-hosted, developer-tools, offline-first" class="field" /></label>
|
|
122
|
+
<label class="grid gap-1.5"><span class="label">Website</span><input id="item-website" type="url" placeholder="https://example.com" class="field" /></label>
|
|
123
|
+
<label class="grid gap-1.5"><span class="label">Best for</span><textarea id="best-for" rows="3" class="field resize-y" placeholder="One use case per line"></textarea></label>
|
|
124
|
+
</div>
|
|
125
|
+
</form>
|
|
126
|
+
|
|
127
|
+
<aside class="flex flex-col gap-4">
|
|
128
|
+
<div class="card p-5">
|
|
129
|
+
<h2 class="m-0 text-[0.9375rem] font-semibold text-ink-900 dark:text-ink-100">Submission criteria</h2>
|
|
130
|
+
<p class="m-0 mt-1 text-2xs text-ink-500 dark:text-ink-400">Use this checklist before opening the draft.</p>
|
|
131
|
+
<div class="mt-4 grid grid-cols-1 gap-4 text-2xs sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2">
|
|
132
|
+
<div>
|
|
133
|
+
<div class="font-semibold uppercase tracking-wider text-emerald-700 dark:text-emerald-400">Good submissions</div>
|
|
134
|
+
<ul class="m-0 mt-2 list-none space-y-1 p-0 text-ink-700 dark:text-ink-200">{goodSubmissions.map((item) => <li class="flex gap-2"><span class="text-emerald-600">✓</span><span>{item}</span></li>)}</ul>
|
|
135
|
+
</div>
|
|
136
|
+
<div>
|
|
137
|
+
<div class="font-semibold uppercase tracking-wider text-amber-700 dark:text-amber-400">Please avoid</div>
|
|
138
|
+
<ul class="m-0 mt-2 list-none space-y-1 p-0 text-ink-700 dark:text-ink-200">{pleaseAvoid.map((item) => <li class="flex gap-2"><span class="text-amber-600">·</span><span>{item}</span></li>)}</ul>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="card flex min-h-[420px] flex-col p-5">
|
|
144
|
+
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
145
|
+
<h2 class="m-0 text-base font-semibold text-ink-900 dark:text-ink-100">YAML draft</h2>
|
|
146
|
+
<div class="flex items-center gap-2">
|
|
147
|
+
<button id="copy-yaml" type="button" class="btn-outline pointer-events-none opacity-50" disabled>Copy YAML</button>
|
|
148
|
+
<a id="open-pr-link" href="#" target="_blank" rel="noopener noreferrer" class="btn-outline pointer-events-none opacity-50">Open PR draft</a>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
<pre class="mt-4 min-h-0 flex-1 overflow-auto rounded-md border border-ink-200 bg-ink-50 p-3 text-xs leading-relaxed text-ink-800 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-200"><code id="yaml-preview">Paste a GitHub URL to generate a draft.</code></pre>
|
|
152
|
+
<p id="submit-status" class="mt-3 min-h-5 text-2xs text-ink-500 dark:text-ink-400" aria-live="polite"></p>
|
|
153
|
+
</div>
|
|
154
|
+
</aside>
|
|
155
|
+
</div>
|
|
156
|
+
</section>
|
|
126
157
|
</Container>
|
|
158
|
+
|
|
159
|
+
<style>
|
|
160
|
+
.field { min-height: 2.5rem; border-radius: .375rem; border: 1px solid var(--color-ink-200); background: white; padding: .5rem .75rem; color: var(--color-ink-900); font-size: .875rem; outline: none; }
|
|
161
|
+
.field:focus { border-color: var(--color-ink-500); }
|
|
162
|
+
:global(.dark) .field { border-color: var(--color-ink-800); background: var(--color-ink-950); color: var(--color-ink-100); }
|
|
163
|
+
.label { font-size: var(--text-2xs); font-weight: 500; text-transform: uppercase; letter-spacing: .05em; color: var(--color-ink-500); }
|
|
164
|
+
.help { font-size: var(--text-2xs); color: var(--color-ink-500); }
|
|
165
|
+
</style>
|
|
166
|
+
|
|
167
|
+
<script is:inline define:vars={{ existingSlugs, existingFullNames, repoUrl }}>
|
|
168
|
+
// @ts-nocheck
|
|
169
|
+
const $ = (selector) => document.querySelector(selector);
|
|
170
|
+
const inputs = {
|
|
171
|
+
repo: $("#repo-url"), name: $("#item-name"), slug: $("#item-slug"),
|
|
172
|
+
description: $("#item-description"), category: $("#item-category"),
|
|
173
|
+
stack: $("#primary-stack"), tags: $("#item-tags"),
|
|
174
|
+
website: $("#item-website"), bestFor: $("#best-for"),
|
|
175
|
+
};
|
|
176
|
+
const fetchButton = $("#fetch-repo");
|
|
177
|
+
const preview = $("#yaml-preview");
|
|
178
|
+
const status = $("#submit-status");
|
|
179
|
+
const copyButton = $("#copy-yaml");
|
|
180
|
+
const openPrLink = $("#open-pr-link");
|
|
181
|
+
const slugSet = new Set(existingSlugs);
|
|
182
|
+
const repoSet = new Set(existingFullNames);
|
|
183
|
+
let repository = null;
|
|
184
|
+
|
|
185
|
+
const slugify = (value) => String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
186
|
+
const parseRepo = (value) => {
|
|
187
|
+
const match = String(value).match(/github\.com\/([^/]+)\/([^/?#]+)/);
|
|
188
|
+
return match ? { owner: match[1], repo: match[2].replace(/\.git$/, "") } : null;
|
|
189
|
+
};
|
|
190
|
+
const quote = (value) => `"${String(value ?? "").replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
191
|
+
const lines = (values, indent = 2) => values.map((value) => `${" ".repeat(indent)}- ${value}`).join("\n");
|
|
192
|
+
const selectedPlatforms = () => [...document.querySelectorAll("input[name=platform]:checked")].map((input) => input.value);
|
|
193
|
+
const setStatus = (text, tone = "muted") => {
|
|
194
|
+
status.textContent = text;
|
|
195
|
+
status.className = `mt-3 min-h-5 text-2xs ${tone === "error" ? "text-red-600 dark:text-red-400" : tone === "warn" ? "text-amber-700 dark:text-amber-400" : tone === "success" ? "text-emerald-700 dark:text-emerald-400" : "text-ink-500 dark:text-ink-400"}`;
|
|
196
|
+
};
|
|
197
|
+
const inferStack = (repo) => {
|
|
198
|
+
const language = String(repo.language || "").toLowerCase();
|
|
199
|
+
const topics = (repo.topics || []).map((topic) => String(topic).toLowerCase());
|
|
200
|
+
if (topics.includes("flutter") || language === "dart") return "Flutter";
|
|
201
|
+
if (topics.includes("react-native")) return "React Native";
|
|
202
|
+
if (language === "typescript" || language === "javascript") return "TypeScript";
|
|
203
|
+
return repo.language || "";
|
|
204
|
+
};
|
|
205
|
+
const generateYaml = () => {
|
|
206
|
+
if (!repository) return "";
|
|
207
|
+
const parsed = parseRepo(inputs.repo.value);
|
|
208
|
+
if (!parsed) return "";
|
|
209
|
+
const tags = inputs.tags.value.split(",").map((tag) => tag.trim()).filter(Boolean);
|
|
210
|
+
const platforms = selectedPlatforms();
|
|
211
|
+
const bestFor = inputs.bestFor.value.split("\n").map((item) => item.trim()).filter(Boolean);
|
|
212
|
+
return [
|
|
213
|
+
"kind: project",
|
|
214
|
+
`slug: ${slugify(inputs.slug.value || repository.name)}`,
|
|
215
|
+
`name: ${quote(inputs.name.value)}`,
|
|
216
|
+
`description: ${quote(inputs.description.value)}`,
|
|
217
|
+
`category: ${inputs.category.value || "other"}`,
|
|
218
|
+
"projectType: real-app",
|
|
219
|
+
`stack: ${quote(inputs.stack.value)}`,
|
|
220
|
+
"platforms:", platforms.length ? lines(platforms) : " []",
|
|
221
|
+
"tags:", tags.length ? lines(tags) : " []",
|
|
222
|
+
`repoUrl: ${repository.html_url}`,
|
|
223
|
+
"links:",
|
|
224
|
+
` github: ${repository.html_url}`,
|
|
225
|
+
...(inputs.website.value ? [` website: ${inputs.website.value}`] : []),
|
|
226
|
+
"bestFor:", bestFor.length ? lines(bestFor) : " []",
|
|
227
|
+
"source:",
|
|
228
|
+
" type: github",
|
|
229
|
+
` owner: ${parsed.owner}`,
|
|
230
|
+
` repo: ${parsed.repo}`,
|
|
231
|
+
"curation:",
|
|
232
|
+
" reviewed: false",
|
|
233
|
+
" labels: []",
|
|
234
|
+
" lenses: []",
|
|
235
|
+
"",
|
|
236
|
+
].join("\n");
|
|
237
|
+
};
|
|
238
|
+
const refresh = () => {
|
|
239
|
+
const yaml = generateYaml();
|
|
240
|
+
preview.textContent = yaml || "Paste a GitHub URL to generate a draft.";
|
|
241
|
+
if (!yaml) return;
|
|
242
|
+
const params = new URLSearchParams({ filename: `data/records/${slugify(inputs.slug.value)}.yml`, value: yaml });
|
|
243
|
+
openPrLink.href = `${repoUrl}/new/main?${params}`;
|
|
244
|
+
openPrLink.classList.remove("pointer-events-none", "opacity-50");
|
|
245
|
+
copyButton.disabled = false;
|
|
246
|
+
copyButton.classList.remove("pointer-events-none", "opacity-50");
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
fetchButton.addEventListener("click", async () => {
|
|
250
|
+
const parsed = parseRepo(inputs.repo.value);
|
|
251
|
+
if (!parsed) return setStatus("Enter a valid GitHub repository URL.", "error");
|
|
252
|
+
const fullName = `${parsed.owner}/${parsed.repo}`.toLowerCase();
|
|
253
|
+
if (repoSet.has(fullName)) setStatus("This repository is already listed. The draft can update its record.", "warn");
|
|
254
|
+
fetchButton.disabled = true;
|
|
255
|
+
setStatus("Fetching GitHub metadata...");
|
|
256
|
+
try {
|
|
257
|
+
const response = await fetch(`https://api.github.com/repos/${parsed.owner}/${parsed.repo}`, { headers: { Accept: "application/vnd.github+json" } });
|
|
258
|
+
if (response.status === 404) throw new Error("Repository not found or not public.");
|
|
259
|
+
if (response.status === 403) throw new Error("GitHub rate limit reached. Try again shortly.");
|
|
260
|
+
if (!response.ok) throw new Error(`GitHub returned ${response.status}.`);
|
|
261
|
+
repository = await response.json();
|
|
262
|
+
if (repository.private) throw new Error("Private repositories cannot be submitted.");
|
|
263
|
+
inputs.name.value = repository.name || parsed.repo;
|
|
264
|
+
inputs.slug.value = slugify(repository.name || parsed.repo);
|
|
265
|
+
inputs.description.value = repository.description || "";
|
|
266
|
+
inputs.stack.value = inferStack(repository);
|
|
267
|
+
inputs.tags.value = (repository.topics || []).slice(0, 8).join(", ");
|
|
268
|
+
inputs.website.value = repository.homepage || "";
|
|
269
|
+
const duplicate = slugSet.has(inputs.slug.value);
|
|
270
|
+
setStatus(duplicate ? "That slug already exists. Choose another or update the existing record." : "Draft generated. Edit any field and the YAML will refresh.", duplicate ? "warn" : "success");
|
|
271
|
+
refresh();
|
|
272
|
+
} catch (error) {
|
|
273
|
+
repository = null;
|
|
274
|
+
setStatus(error.message || "Unable to fetch repository metadata.", "error");
|
|
275
|
+
} finally {
|
|
276
|
+
fetchButton.disabled = false;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
$("#submit-form").addEventListener("input", refresh);
|
|
280
|
+
copyButton.addEventListener("click", async () => {
|
|
281
|
+
try {
|
|
282
|
+
await navigator.clipboard.writeText(preview.textContent || "");
|
|
283
|
+
setStatus("Copied YAML to clipboard.", "success");
|
|
284
|
+
} catch {
|
|
285
|
+
setStatus("Couldn't copy automatically. Select the YAML and copy it.", "error");
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
</script>
|
|
127
289
|
</BaseLayout>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
//
|
|
4
|
+
// Cloudflare Pages configuration for the Grove Astro template.
|
|
5
|
+
//
|
|
6
|
+
// `name` is the Pages project name (`grove-site` by default —
|
|
7
|
+
// rename to match your Cloudflare account, or override via
|
|
8
|
+
// `wrangler pages deploy --project-name <name>`).
|
|
9
|
+
//
|
|
10
|
+
// `compatibility_date` pins the runtime API surface. Bump it
|
|
11
|
+
// when you want to opt into newer Pages features; older dates
|
|
12
|
+
// are fine for stable behavior.
|
|
13
|
+
//
|
|
14
|
+
// `pages_build_output_dir` is the directory that `astro build`
|
|
15
|
+
// writes to. Astro 6's default is `./dist`, which matches.
|
|
16
|
+
//
|
|
17
|
+
// The deploy workflow in `.github/workflows/build.yml` runs
|
|
18
|
+
// pnpm run build
|
|
19
|
+
// npx wrangler pages deploy dist --project-name <name>
|
|
20
|
+
// after this file is committed.
|
|
21
|
+
"$schema": "node_modules/wrangler/config-schema.json",
|
|
22
|
+
"name": "grove-site",
|
|
23
|
+
"compatibility_date": "2026-06-07",
|
|
24
|
+
"pages_build_output_dir": "./dist",
|
|
25
|
+
"observability": {
|
|
26
|
+
"enabled": true
|
|
27
|
+
}
|
|
28
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Turtuvshin Byambaa
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|