diff --git a/.env.example b/.env.example index 9b23136..ad9d65d 100644 --- a/.env.example +++ b/.env.example @@ -24,3 +24,11 @@ GITHUB_TOKEN= # Production and the deploy workflow use `/` (the site is served from the apex # domain). Override only when previewing under a sub-path, e.g. `/my-fork/`. ASTRO_BASE=/ + +# Public analytics identifiers injected at build time. These values are visible +# in the generated HTML/browser and are identifiers, not secrets. +PUBLIC_CLOUDFLARE_WEB_ANALYTICS_TOKEN= +PUBLIC_GOOGLE_ANALYTICS_ID= + +# Optional Google Search Console HTML-tag verification value. +PUBLIC_GOOGLE_SITE_VERIFICATION= diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 85ffb02..841b490 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,7 +4,14 @@ ## Validation +- [ ] `npm test` - [ ] `npm run build` +- [ ] Cloudflare branch preview checked on desktop and mobile + +## Content checks + +- [ ] Claims, organizations, sponsors, and metrics have a verifiable source +- [ ] New internal links work with `ASTRO_BASE` ## Notes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f701b9..125625e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,13 +6,13 @@ Thanks for helping improve IndopenSource.org. ```bash npm install -npm run sync:projects npm run dev ``` Before opening a pull request: ```bash +npm test npm run build ``` @@ -35,13 +35,41 @@ Use `GITHUB_TOKEN` or `GH_TOKEN` to avoid low GitHub API rate limits. `main` commit to production. - Keep changes focused. - Prefer Astro components in `src/components` over large page-only markup. -- Keep internal links compatible with GitHub Pages base paths by using `withBase()`. -- Run `npm run build` and include the result in the PR description. +- Use `withBase()` for internal links that must remain valid when `ASTRO_BASE` + is set by CI or a preview environment. +- Run `npm test` and `npm run build`, then include the results in the PR description. - For content or roadmap changes, link the relevant issue or discussion. +## Community Directory + +The Community page reads `src/data/communities.json`. To propose one community, +add one object containing: + +- `name` +- `description` +- `url` pointing to an official channel +- optional `focus` +- optional `location` + +Keep one community per pull request and explain your relationship to it or add a +source that reviewers can verify. Do not add inferred or inactive communities. + +## Content Quality + +- Lead with a direct summary before background detail. +- Cite primary sources for factual claims and include the relevant date. +- Use descriptive headings and stable internal links to related pages. +- Identify authors and keep publication and update dates traceable. +- Do not add speculative facts, generated statistics, or special AI-search + markup that is not supported by search-engine documentation. + ## Production Release The `release` branch is reserved for Cloudflare production deployments. Only IndopenSource organization members may update it, through a pull request from `main` that passes CI. Merge production promotion pull requests with a merge commit so `main` remains in the `release` history. + +Cloudflare creates a branch preview for contribution pull requests. Review that +preview before promoting `main` to `release`; use +[`docs/release-checklist.md`](docs/release-checklist.md) for the production pass. diff --git a/README.md b/README.md index ddba074..4956d18 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,40 @@ # IndopenSource.org -[![Deploy to GitHub Pages](https://github.com/IndopenSource/indopensource.org/actions/workflows/deploy-pages.yml/badge.svg)](https://github.com/IndopenSource/indopensource.org/actions/workflows/deploy-pages.yml) +[![CI](https://github.com/IndopenSource/indopensource.org/actions/workflows/ci.yml/badge.svg)](https://github.com/IndopenSource/indopensource.org/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) -Website roadmap untuk `https://indopensource.org`. +Website komunitas dan direktori open source Indonesia di `https://indopensource.org`. ## Tech Stack - Astro - Tailwind CSS - TypeScript -- GitHub Pages - GitHub Actions +- Cloudflare Workers Static Assets ## Pages -- Home: ringkasan roadmap dan arah website. +- Home: pintu masuk ke direktori, roadmap, pembelajaran, dan kontribusi. - Falsafah: prinsip komunitas dan kurasi open source Indonesia. -- Projects: direktori dari `IndopenSource/awesome-indonesia`. -- Blog: placeholder untuk repo khusus `IndopenSource/Blog-IndopenSource`. -- Forum: rencana integrasi `https://github.com/orgs/IndopenSource/discussions`. -- Contact: kanal GitHub organization, projects, dan discussions. +- Projects: direktori proyek dari `IndopenSource/awesome-indonesia`. +- Top User: peringkat pemilik repository berdasarkan akumulasi stars. +- Komunitas: direktori komunitas yang diusulkan melalui pull request. +- Roadmap: rencana kerja publik 2026–2027. +- Belajar dan Blog: materi serta artikel yang disinkronkan dari repository sumber. +- Donasi: informasi OpenSawer, sponsorship, CSR, dan dukungan non-dana. +- Forum dan Kontak: GitHub Discussions serta kanal resmi IndopenSource. ## Development ```bash npm install -npm run sync:projects npm run dev ``` +Sinkronisasi data bersifat opsional untuk pengembangan UI. File hasil sinkronisasi +yang diperlukan sudah tersedia di `src/data/`. + ## Project Structure ```txt @@ -45,38 +50,36 @@ src/ ## Checks ```bash +npm test npm run check npm run build ``` -## MVP Pre-release - -MVP awal berfokus pada homepage sebagai pintu masuk roadmap IndopenSource: - -- Home menjelaskan posisi `indopensource.org`. -- Projects sudah punya data awal dari `awesome-indonesia`. -- Falsafah, Blog, Forum, dan Contact tersedia sebagai halaman roadmap. -- Deployment memakai GitHub Pages bawaan repo lewat GitHub Actions. - -Rilis pre-release bisa dibuat dari tag `v0.1.0-mvp` setelah workflow Pages hijau. -URL produksi setelah custom domain aktif adalah `https://indopensource.org/`. - ## Deployment -Deployment saat ini masih memakai GitHub Pages melalui workflow -`.github/workflows/deploy-pages.yml`. Cloudflare production berikutnya akan -dibangun dari branch `release`. +Cloudflare terhubung langsung ke repository dan membuat preview untuk pull +request atau branch non-production. Production hanya dibangun dari branch +`release`. - Build command: `npm run build` +- Deploy command: `npx wrangler deploy` - Output directory: `dist` -- Source: GitHub Actions +- Konfigurasi Worker: `wrangler.jsonc` +- Production branch: `release` - Custom domain: `indopensource.org` -- Pages base path: `/` -Semua kontribusi tetap masuk ke `main`. Rilis produksi dipromosikan oleh anggota -organisasi melalui pull request dari `main` ke `release`; jangan membuka pull +Semua kontribusi masuk ke `main`. Anggota organisasi mempromosikan versi yang +sudah diuji melalui pull request dari `main` ke `release`; jangan membuka pull request kontribusi langsung ke `release`. +Workflow GitHub Pages lama masih berada di `.github/workflows/deploy-pages.yml` +dan bukan sumber deployment production Cloudflare. Putuskan penghapusan atau +perannya sebagai fallback sebelum release publik agar tidak ada dua jalur deploy +yang dianggap aktif. + +Lihat [checklist release](docs/release-checklist.md) sebelum mempromosikan +`main` ke `release`. + ## Project Sync `npm run sync:projects` membaca `repos.json` dari `IndopenSource/awesome-indonesia`, @@ -88,8 +91,14 @@ Gunakan `GITHUB_TOKEN` atau `GH_TOKEN` untuk rate limit yang lebih lega. ## Blog Sync `npm run sync:blog` membaca artikel Markdown dari -`IndopenSource/Blog-IndopenSource`, mengambil metadata commit penulis/rilis, -dan menulis hasilnya ke `src/data/blog-posts.json`. +`IndopenSource/Blog-IndopenSource`, mengambil tanggal commit pertama, commit +terbaru, dan seluruh kontributor unik setiap artikel, lalu menulis hasilnya ke +`src/data/blog-posts.json`. Halaman artikel menautkan commit terbaru tersebut +agar riwayat sumber dapat diperiksa langsung. + +Artikel berstatus `draft` tetap dibangun pada URL `/blog/preview/{slug}/`, +tetapi tidak ditampilkan di indeks blog, tidak masuk sitemap, dan memakai +`noindex, nofollow, noarchive` sampai statusnya diterbitkan. ## Auto Sync @@ -103,8 +112,20 @@ Workflow `.github/workflows/sync-content.yml` memperbarui data secara otomatis. - `sync-content` Jika data berubah, workflow membuat commit `Sync content data`. Setelah commit -masuk `main`, workflow yang sama akan upload artifact dan deploy ulang GitHub -Pages agar perubahan data langsung tayang. +masuk `main`, perubahan mengikuti validasi dan alur promosi `main` ke `release`. +Workflow ini masih memuat langkah deployment GitHub Pages lama; jangan menganggap +hasil tersebut sebagai production Cloudflare. + +## Analytics & Search Verification + +Analytics aktif saat build Cloudflare menerima environment variable berikut: + +- `PUBLIC_CLOUDFLARE_WEB_ANALYTICS_TOKEN` +- `PUBLIC_GOOGLE_ANALYTICS_ID` (format `G-XXXXXXXXXX`) +- `PUBLIC_GOOGLE_SITE_VERIFICATION` (opsional) + +Nilainya merupakan identifier publik dan tidak perlu dimasukkan ke Git. Isi di +pengaturan build Cloudflare untuk production dan preview sesuai kebutuhan. ## Contributing diff --git a/docs/release-checklist.md b/docs/release-checklist.md new file mode 100644 index 0000000..3bd76ba --- /dev/null +++ b/docs/release-checklist.md @@ -0,0 +1,34 @@ +# Release checklist + +Production is promoted from `main` to `release` through a pull request opened by +an IndopenSource organization member. + +## Before promotion + +- [ ] Contribution pull requests are merged into `main` and CI is green. +- [ ] Cloudflare branch preview has been checked on desktop and mobile. +- [ ] `npm test` and `npm run build` pass from the intended release commit. +- [ ] Navigation, project detail pages, `/donasi/`, `/komunitas/`, `/roadmap/`, + `/sitemap.xml`, and the `/sponsors/` redirect have been checked. +- [ ] `admin@`, `press@`, `sponsor@`, and `legal@indopensource.org` can receive + mail, or links to inactive addresses have been removed. +- [ ] DNS, custom domain, TLS, and the Cloudflare production branch are correct. +- [ ] Cloudflare build variables for the Web Analytics token and Google + Analytics Measurement ID are configured. +- [ ] The role of the legacy GitHub Pages workflows has been decided. + +## Promote + +1. Open a pull request from `main` to `release`. +2. Require green CI and review the diff for unexpected generated data. +3. Merge with a merge commit so `main` remains in the `release` history. +4. Wait for the Cloudflare production deployment to pass. + +## After deployment + +- [ ] Smoke-test `https://indopensource.org/` and the primary navigation. +- [ ] Confirm canonical URLs, robots, sitemap, favicon, and social previews. +- [ ] Confirm Cloudflare Web Analytics and Google Analytics receive a test page + view without CSP errors. +- [ ] Send a test message to each published email address. +- [ ] Record the production commit or tag as the rollback point. diff --git a/public/_redirects b/public/_redirects new file mode 100644 index 0000000..54b9661 --- /dev/null +++ b/public/_redirects @@ -0,0 +1 @@ +/sponsors/ /donasi/ 301 diff --git a/public/brand/apple-touch-icon.png b/public/brand/apple-touch-icon.png index e36db39..b50d69a 100644 Binary files a/public/brand/apple-touch-icon.png and b/public/brand/apple-touch-icon.png differ diff --git a/public/brand/favicon-96.png b/public/brand/favicon-96.png new file mode 100644 index 0000000..4c9183a Binary files /dev/null and b/public/brand/favicon-96.png differ diff --git a/public/brand/favicon.ico b/public/brand/favicon.ico index 815d094..29eb739 100644 Binary files a/public/brand/favicon.ico and b/public/brand/favicon.ico differ diff --git a/public/brand/tokengratis-icon.png b/public/brand/tokengratis-icon.png new file mode 100644 index 0000000..bfebf7c Binary files /dev/null and b/public/brand/tokengratis-icon.png differ diff --git a/scripts/sync-blog-posts.mjs b/scripts/sync-blog-posts.mjs index 661eaac..cc2953d 100644 --- a/scripts/sync-blog-posts.mjs +++ b/scripts/sync-blog-posts.mjs @@ -161,6 +161,20 @@ async function getCommitMeta(path) { const firstCommit = commits.at(-1); const latestCommit = commits.at(0); const author = firstCommit?.author; + const authors = []; + const seenAuthors = new Set(); + + for (const item of commits.toReversed()) { + const name = item.author?.login || item.commit?.author?.name; + if (!name || seenAuthors.has(name.toLowerCase())) continue; + seenAuthors.add(name.toLowerCase()); + authors.push({ + name, + avatarUrl: item.author?.avatar_url || '', + url: item.author?.html_url || '', + committedAt: item.commit?.author?.date || '' + }); + } return { author: { @@ -169,6 +183,9 @@ async function getCommitMeta(path) { url: author?.html_url || firstCommit?.html_url || `https://github.com/${BLOG_REPO}`, committedAt: firstCommit?.commit?.author?.date || '' }, + authors, + latestCommitSha: latestCommit?.sha || '', + latestCommitUrl: latestCommit?.html_url || '', // The latest commit timestamp is the date of the most recent EDIT, not a // publication/release event (CC-9). Name it honestly as "last modified" so // the rendered label can stop calling an edit timestamp a release date. @@ -244,6 +261,12 @@ for (const path of articleFiles) { const commitMeta = await getCommitMeta(path); const editorialDate = (data.date || '').trim(); const resolvedAuthor = resolveAuthor(commitMeta.author, data.authors); + const resolvedAuthors = resolvedAuthor.fromFrontmatter + ? [ + resolvedAuthor.author, + ...commitMeta.authors.filter((author) => author.name.toLowerCase() !== resolvedAuthor.author.name.toLowerCase()) + ] + : commitMeta.authors.length ? commitMeta.authors : [resolvedAuthor.author]; posts.push({ slug: slugFromPath(path), @@ -264,7 +287,10 @@ for (const path of articleFiles) { // edit timestamp separately for provenance (CC-9). releasedAt: editorialDate || commitMeta.author.committedAt || commitMeta.lastModifiedAt, lastModifiedAt: commitMeta.lastModifiedAt, + latestCommitSha: commitMeta.latestCommitSha, + latestCommitUrl: commitMeta.latestCommitUrl, author: resolvedAuthor.author, + authors: resolvedAuthors, authorFromFrontmatter: resolvedAuthor.fromFrontmatter }); } diff --git a/src/components/DonationPage.astro b/src/components/DonationPage.astro new file mode 100644 index 0000000..752b9db --- /dev/null +++ b/src/components/DonationPage.astro @@ -0,0 +1,254 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; +import BaseButton from './BaseButton.astro'; +import PageHeader from './PageHeader.astro'; +import { withBase } from '../lib/urls'; + +const tiers = [ + { + name: 'Utama', + min: 'Rp 5.000.000/tahun', + benefits: [ + 'Logo di homepage & footer website', + 'Profil organisasi di halaman sponsor', + 'Kolaborasi satu program atau event', + 'Laporan penggunaan dan dampak berkala' + ], + highlight: true + }, + { + name: 'Partner', + min: 'Rp 2.500.000/tahun', + benefits: [ + 'Logo di halaman sponsor & footer', + 'Apresiasi di kanal komunitas', + 'Laporan penggunaan dana berkala' + ], + highlight: false + }, + { + name: 'Pendukung', + min: 'Rp 1.000.000/tahun', + benefits: [ + 'Logo di halaman sponsor', + 'Apresiasi publik', + 'Akses laporan terbuka' + ], + highlight: false + }, + { + name: 'Sahabat', + min: 'Rp 250.000/tahun', + benefits: [ + 'Nama di halaman sponsor', + 'Apresiasi publik', + 'Akses laporan terbuka' + ], + highlight: false + } +]; + +const currentSponsors = [ + { name: 'Tokengratis.id', since: '2026', url: 'https://tokengratis.id/', logo: withBase('/brand/tokengratis-icon.png') } +]; + +const openSawerIcon = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='64'%20height='64'%20viewBox='0%200%2040%2040'%3e%3ctitle%3eOpenSawer%3c/title%3e%3ccircle%20cx='20'%20cy='20'%20r='20'%20fill='%230f694e'/%3e%3ccircle%20cx='20'%20cy='11.5'%20r='3.2'%20fill='%23e8b83f'/%3e%3cpath%20d='M10.5%2019.5c1.4%207.2%205%2010.8%209.5%2010.8s8.1-3.6%209.5-10.8M12%2019.5h16'%20fill='none'%20stroke='%23fff'%20stroke-width='3.2'%20stroke-linecap='round'/%3e%3c/svg%3e"; +--- + + + +

IndopenSource dijalankan oleh komunitas sukarela. Dukungan individu, sponsorship, CSR, dan kolaborasi membantu kami merawat infrastruktur, mengadakan event, dan mendukung proyek-proyek potensial.

+
+ +
+
+
+ Donasi per proyek +

Dukung proyek yang Anda gunakan.

+

OpenSawer akan dihubungkan ke halaman setiap proyek yang terdaftar di direktori IndopenSource. Setelah integrasinya siap, siapa pun dapat memilih proyek lalu berdonasi langsung melalui kanal OpenSawer proyek tersebut.

+

Fitur ini masih dalam pengembangan dan belum tersedia di seluruh halaman proyek.

+
+ + +

Buka OpenSawer

+

Lihat kanal donasi terbuka yang sedang disiapkan untuk komunitas dan proyek.

+ sawer.indopensource.org ↗ +
+
+
+ +
+
+ Dampak bersama +

Setiap kontribusi membantu menjaga open source Indonesia tetap hidup dan berkembang.

+
+
+ +
+
+
+
+ Program CSR +

Kami menerima dukungan CSR.

+

+ Dana CSR dapat diarahkan untuk infrastruktur komunitas, dokumentasi dan materi belajar, + event, serta dukungan bagi proyek open source Indonesia yang membutuhkan ruang tumbuh. + Program dan indikator hasil disepakati bersama perusahaan sebelum pelaksanaan. +

+
+ Diskusikan program CSR +
+
+ +
+

Dasar hukum dan cakupannya

+
+

+ Pasal 74 UU No. 40 Tahun 2007 + mewajibkan Perseroan yang menjalankan usaha di bidang dan/atau berkaitan dengan sumber daya alam melaksanakan Tanggung Jawab Sosial dan Lingkungan. Anggarannya diperhitungkan sebagai biaya Perseroan dengan memperhatikan kepatutan dan kewajaran. +

+

+ PP No. 47 Tahun 2012 + mengatur pelaksanaannya melalui rencana kerja tahunan dan pemuatan hasil pelaksanaan dalam laporan tahunan Perseroan. +

+

+ Pasal 15 huruf b UU No. 25 Tahun 2007 + juga mewajibkan setiap penanam modal melaksanakan tanggung jawab sosial perusahaan. Penerapan kewajiban tetap mengikuti status, bidang usaha, dan aturan sektoral perusahaan masing-masing. +

+
+

Informasi umum · perusahaan tetap perlu menilai kewajibannya bersama penasihat hukum.

+
+
+
+
+ +
+
+
+
+ Transparansi terbuka +

Dana masuk, penggunaan, dan dampaknya dapat dilihat.

+

+ Penerimaan dan penggunaan dana CSR direncanakan dipublikasikan melalui OpenSawer. Laporan program, + rincian penyaluran, bukti kegiatan, dan pembaruan dampak akan dibuka setelah kanal pelaporan siap. +

+
+ +
+ + + +
+

OpenSawer

+

Kanal transparansi yang sedang disiapkan

+
+
+

Lihat aplikasi open source yang akan digunakan untuk membuka catatan dana dan laporan program.

+ Buka OpenSawer → +
+
+
+
+ +
+
+
+ Pendukung kami +

Organisasi yang mendukung.

+

Terima kasih kepada organisasi yang telah mendukung misi IndopenSource.

+
+
+ {currentSponsors.map((s) => ( + +
+
+ +
+
+

{s.name}

+

Mendukung sejak {s.since}

+
+
+
+ ))} +
+
+
+ +
+ +
+ +
+
+
+ Dukungan organisasi +

Pilih tingkat kemitraan.

+

Semua paket memberikan akses ke laporan dampak dan apresiasi publik.

+
+
+ {tiers.map((tier) => ( +
+ {tier.highlight && Populer} +

{tier.name}

+

{tier.min}

+
    + {tier.benefits.map((b) => ( +
  • + + {b} +
  • + ))} +
+
+ ))} +
+
+
+ +
+
+
+ Tertarik bermitra? +

Mari diskusikan kemitraan Anda.

+

Kami terbuka untuk paket kustom, sponsorship event, atau bentuk kolaborasi lain yang sejalan dengan misi open source.

+
+ Hubungi kami + Media Kit +
+
+
+
+
diff --git a/src/components/HomeHero.astro b/src/components/HomeHero.astro index 5a96446..9f287a5 100644 --- a/src/components/HomeHero.astro +++ b/src/components/HomeHero.astro @@ -19,25 +19,23 @@ import { heroImage, HERO_PRELOAD_WIDTH, HERO_QUALITY } from '../lib/hero';

- Rumah terbuka untuk merawat daftar proyek, mendokumentasikan falsafah, - menulis pembelajaran, dan menghubungkan kontributor open source Indonesia. + Rumah terbuka untuk merawat daftar proyek, menulis pembelajaran, + dan menghubungkan kontributor open source Indonesia.

Lihat proyek - Roadmap GitHub
-
-
-
Direktori
-
Awesome ID
-
-
-
Lisensi
-
Open / Publik
-
-
+ + Lisensi + Open / Publik ↗ +
diff --git a/src/components/ProjectsDirectory.astro b/src/components/ProjectsDirectory.astro index 665bf52..9fff852 100644 --- a/src/components/ProjectsDirectory.astro +++ b/src/components/ProjectsDirectory.astro @@ -10,6 +10,7 @@ interface Props { const { projects } = Astro.props; const languages = [...new Set(projects.map((project) => project.language).filter((language): language is string => Boolean(language)))].sort(); const topics = [...new Set(projects.flatMap((project) => project.topics || []))].sort().slice(0, 80); +const repoName = (fullName: string) => fullName.split('/').at(-1) || fullName; const formatDate = (value?: string) => value ? new Intl.DateTimeFormat('id-ID', { dateStyle: 'medium' }).format(new Date(value)) : '-'; @@ -110,7 +111,8 @@ const formatDate = (value?: string) => value ? : } - {project.fullName} + {repoName(project.fullName)} + {project.owner && oleh {project.owner}} {project.description} {!project.syncFailed && ( diff --git a/src/components/SiteFooter.astro b/src/components/SiteFooter.astro index 17f1dbd..5b14a7c 100644 --- a/src/components/SiteFooter.astro +++ b/src/components/SiteFooter.astro @@ -2,8 +2,14 @@ import { withBase } from '../lib/urls'; const links: { label: string; href: string; external?: boolean }[] = [ + { label: 'Proyek', href: '/projects/' }, + { label: 'Belajar', href: '/belajar/' }, { label: 'Tentang', href: '/tentang/' }, + { label: 'Falsafah', href: '/falsafah/' }, { label: 'Komunitas', href: '/komunitas/' }, + { label: 'Top User', href: '/users/' }, + { label: 'Blog', href: '/blog/' }, + { label: 'Forum', href: '/forum/' }, { label: 'Cara Berkontribusi', href: '/cara-berkontribusi/' }, { label: 'Kode Etik', href: '/kode-etik/' }, { label: 'Donasi', href: '/donasi/' }, @@ -11,13 +17,13 @@ const links: { label: string; href: string; external?: boolean }[] = [ { label: 'Program', href: '/program/' }, { label: 'Tata Kelola', href: '/tata-kelola/' }, { label: 'Event', href: '/events/' }, - { label: 'Sponsors', href: '/sponsors/' }, + { label: 'Kontak', href: '/contact/' }, { label: 'Press', href: '/press/' }, { label: 'Legal', href: '/legal/' }, { label: 'Security', href: '/security/' }, + { label: 'Roadmap', href: '/roadmap/' }, { label: 'GitHub', href: 'https://github.com/IndopenSource', external: true }, { label: 'Discussions', href: 'https://github.com/orgs/IndopenSource/discussions', external: true }, - { label: 'Roadmap', href: 'https://github.com/orgs/IndopenSource/projects', external: true } ]; const year = new Date().getFullYear(); @@ -44,7 +50,7 @@ const year = new Date().getFullYear(); bareng, dirawat bareng.

- Open Source ID + Open source · Indonesia {/* Thick rule between mission and the colophon grid */} diff --git a/src/components/SiteHeader.astro b/src/components/SiteHeader.astro index 372f2f1..2653ce3 100644 --- a/src/components/SiteHeader.astro +++ b/src/components/SiteHeader.astro @@ -4,24 +4,20 @@ import { withBase } from '../lib/urls'; import brandIcon from '../assets/brand/icon-192.png'; const navItems = [ - ['Home', '/'], - ['Falsafah', '/falsafah/'], - ['Proyek', '/projects/'], - ['Belajar', '/belajar/'], - ['Kontribusi', '/cara-berkontribusi/'], - ['Blog', '/blog/'], - ['Forum', '/forum/'], - ['FAQ', '/faq/'], - ['Donasi', '/donasi/'], - ['Kontak', '/contact/'] + ['Home', '/', 'ph-house'], + ['Proyek', '/projects/', 'ph-grid-four'], + ['Blog', '/blog/', 'ph-article'], + ['Belajar', '/belajar/', 'ph-books'], + ['FAQ', '/faq/', 'ph-question'], + ['Donasi', '/donasi/', 'ph-heart'] ]; const mobileNavItems = [ ['Home', '/', 'ph-house'], ['Proyek', '/projects/', 'ph-grid-four'], - ['Belajar', '/belajar/', 'ph-books'], ['Blog', '/blog/', 'ph-article'], - ['Forum', '/forum/', 'ph-chats-circle'] + ['Belajar', '/belajar/', 'ph-books'], + ['Top User', '/users/', 'ph-ranking'] ]; const currentPath = Astro.url.pathname; @@ -65,26 +61,44 @@ const currentPathWithoutBase = currentPath.replace(new RegExp(`^${baseUrl.replac {/* Desktop nav — mono uppercase labels, riso-underline reveal, sticker active state */}
{ - navItems.map(([label, href]) => { + navItems.map(([label, href, icon]) => { const isCurrent = currentPathWithoutBase === href; + const isDonation = href === '/donasi/'; return isCurrent ? ( + {label} ) : ( + {label} ); }) } + + +
diff --git a/src/data/blog-posts.json b/src/data/blog-posts.json index b39a31f..82752bc 100644 --- a/src/data/blog-posts.json +++ b/src/data/blog-posts.json @@ -19,12 +19,22 @@ "sourceUrl": "https://github.com/IndopenSource/Blog-IndopenSource/blob/main/content/2026/07/bagaimana-open-source-mampu-bertahan.md", "releasedAt": "2026-07-16", "lastModifiedAt": "2026-07-16T11:08:44Z", + "latestCommitSha": "95d3147d541c05b1ab6f683ccddd6b84d816aabd", + "latestCommitUrl": "https://github.com/IndopenSource/Blog-IndopenSource/commit/95d3147d541c05b1ab6f683ccddd6b84d816aabd", "author": { "name": "wauputr4", "avatarUrl": "https://avatars.githubusercontent.com/u/103489788?v=4", "url": "https://github.com/wauputr4", "committedAt": "2026-07-16T11:02:36Z" }, + "authors": [ + { + "name": "wauputr4", + "avatarUrl": "https://avatars.githubusercontent.com/u/103489788?v=4", + "url": "https://github.com/wauputr4", + "committedAt": "2026-07-16T11:02:36Z" + } + ], "authorFromFrontmatter": false }, { @@ -45,12 +55,22 @@ "sourceUrl": "https://github.com/IndopenSource/Blog-IndopenSource/blob/main/content/2026/06/memperkenalkan-blog-indopensource.md", "releasedAt": "2026-06-14", "lastModifiedAt": "2026-07-16T11:02:50Z", + "latestCommitSha": "a411deee259a2d8b9a8b1150d17f5dd5e2d6aab1", + "latestCommitUrl": "https://github.com/IndopenSource/Blog-IndopenSource/commit/a411deee259a2d8b9a8b1150d17f5dd5e2d6aab1", "author": { "name": "wauputr4", "avatarUrl": "https://avatars.githubusercontent.com/u/103489788?v=4", "url": "https://github.com/wauputr4", "committedAt": "2026-06-13T17:37:55Z" }, + "authors": [ + { + "name": "wauputr4", + "avatarUrl": "https://avatars.githubusercontent.com/u/103489788?v=4", + "url": "https://github.com/wauputr4", + "committedAt": "2026-06-13T17:37:55Z" + } + ], "authorFromFrontmatter": false } ] diff --git a/src/data/communities.json b/src/data/communities.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/src/data/communities.json @@ -0,0 +1 @@ +[] diff --git a/src/data/roadmap.ts b/src/data/roadmap.ts new file mode 100644 index 0000000..f2e0551 --- /dev/null +++ b/src/data/roadmap.ts @@ -0,0 +1,37 @@ +export const roadmapPeriods = [ + { + period: 'Jul–Sep 2026', + title: 'Rapikan fondasi data', + items: [ + 'Jaga sinkronisasi direktori proyek', + 'Publikasikan roadmap dan kanal transparansi', + 'Rapikan jalur kontribusi komunitas', + 'Uji OpenSawer bersama proyek terpilih', + 'Rekrut penggerak komunitas', + 'Adakan event perdana' + ] + }, + { + period: 'Okt–Des 2026', + title: 'Buka dukungan komunitas', + items: [ + 'Kurasi kebutuhan proyek potensial', + 'Mulai laporan sponsor dan CSR terbuka', + 'Perluas artikel dan materi belajar' + ] + }, + { + period: 'Jan–Mar 2027', + title: 'Hubungkan proyek dan organisasi', + items: [ + 'Adakan konferensi perangkat terbuka', + 'Buka kolaborasi program dengan komunitas', + 'Dokumentasikan hasil program' + ] + }, + { + period: 'Apr–Jun 2027', + title: 'Ukur dan tentukan langkah berikutnya', + items: ['Terbitkan laporan satu tahun', 'Evaluasi program yang berdampak', 'Susun roadmap periode selanjutnya'] + } +]; diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 31dc3e4..229e20b 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -10,6 +10,11 @@ interface Props { image?: string; /** Open Graph `og:type` (e.g. `website`, `article`). */ type?: string; + /** Search-engine indexing policy for this page. */ + robots?: string; + /** ISO timestamps used by article Open Graph metadata. */ + publishedAt?: string; + modifiedAt?: string; /** * Structured data injected as a JSON-LD `` inside synced data (repo description, post // title) can't terminate the inline block. Escaped before hashing, so the CSP @@ -82,15 +123,16 @@ for (const byte of new Uint8Array(jsonLdDigest)) { jsonLdBinary += String.fromCharCode(byte); } const jsonLdHash = btoa(jsonLdBinary); +const styleSrc = import.meta.env.DEV ? "style-src 'self' 'unsafe-inline'" : "style-src 'self'"; const csp = [ "default-src 'self'", "base-uri 'self'", - `script-src 'self' 'sha256-${jsonLdHash}'`, - "style-src 'self'", + `script-src 'self' 'sha256-${jsonLdHash}'${cloudflareBeaconToken ? ' https://static.cloudflareinsights.com' : ''}${googleAnalyticsId ? ' https://www.googletagmanager.com' : ''}`, + styleSrc, // First-party + GitHub avatar/raw image hosts only — no third-party beacons. - "img-src 'self' data: https://raw.githubusercontent.com https://avatars.githubusercontent.com", + `img-src 'self' data: https://raw.githubusercontent.com https://avatars.githubusercontent.com${googleAnalyticsId ? ' https://*.google-analytics.com https://*.googletagmanager.com' : ''}`, "font-src 'self' data:", - "connect-src 'self'", + `connect-src 'self'${cloudflareBeaconToken ? ' https://cloudflareinsights.com' : ''}${googleAnalyticsId ? ' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com' : ''}`, "frame-src https://drive.google.com", "form-action 'self'", "frame-ancestors 'none'", @@ -108,7 +150,8 @@ const csp = [ - + + {googleSiteVerification && } @@ -124,15 +167,18 @@ const csp = [ ) } - + + + {type === 'article' && publishedAt && } + {type === 'article' && modifiedAt && } @@ -202,6 +248,29 @@ const csp = [ observer.observe(el); } } + + const cloudflareBeaconToken = import.meta.env.PUBLIC_CLOUDFLARE_WEB_ANALYTICS_TOKEN; + if (cloudflareBeaconToken) { + const beacon = document.createElement('script'); + beacon.defer = true; + beacon.src = 'https://static.cloudflareinsights.com/beacon.min.js'; + beacon.dataset.cfBeacon = JSON.stringify({ token: cloudflareBeaconToken }); + document.head.append(beacon); + } + + const googleAnalyticsId = import.meta.env.PUBLIC_GOOGLE_ANALYTICS_ID; + if (googleAnalyticsId) { + const tag = document.createElement('script'); + tag.async = true; + tag.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(googleAnalyticsId)}`; + document.head.append(tag); + + const analyticsWindow = window as typeof window & { dataLayer?: unknown[][] }; + const dataLayer = analyticsWindow.dataLayer ||= []; + const gtag = (...args: unknown[]) => dataLayer.push(args); + gtag('js', new Date()); + gtag('config', googleAnalyticsId); + } diff --git a/src/lib/content.ts b/src/lib/content.ts index 62724f9..afbaece 100644 --- a/src/lib/content.ts +++ b/src/lib/content.ts @@ -45,7 +45,11 @@ export interface BlogPost { releasedAt: string; /** ISO timestamp of the most recent edit commit; distinct from `releasedAt`. */ lastModifiedAt?: string; + latestCommitSha?: string; + latestCommitUrl?: string; author: PostAuthor; + /** Unique contributors derived from the Markdown file's commit history. */ + authors?: PostAuthor[]; /** * Whether `author` was taken from frontmatter `authors[]` (true) or * derived from git commit metadata (false/undefined). Lets the UI label diff --git a/src/lib/learning.ts b/src/lib/learning.ts index 5774571..e79c615 100644 --- a/src/lib/learning.ts +++ b/src/lib/learning.ts @@ -9,6 +9,7 @@ import smartDigitalEnergyOg from '../assets/og/learning/smart-digital-energy.jpg export interface LearningResource { slug: string; title: string; + seoTitle?: string; year: number; topic: string; description: string; @@ -38,6 +39,7 @@ export const learningResources: LearningResource[] = [ { slug: 'ai-yang-proaktif', title: 'AI yang Proaktif: Memahami Agentic AI dengan Contoh Sederhana', + seoTitle: 'Agentic AI Proaktif dengan Contoh Sederhana', year: 2025, topic: 'Agentic AI', description: 'Pengenalan agentic AI melalui contoh yang mudah diikuti.', @@ -47,6 +49,7 @@ export const learningResources: LearningResource[] = [ { slug: 'menulis-jurnal-dibantu-ai', title: 'Menulis Jurnal Dibantu AI: Data-First Anti-Halusinasi', + seoTitle: 'Menulis Jurnal dengan AI Tanpa Halusinasi', year: 2025, topic: 'Literasi AI', description: 'Panduan menggunakan AI secara berbasis data untuk penulisan jurnal.', @@ -56,6 +59,7 @@ export const learningResources: LearningResource[] = [ { slug: 'roadmap-ai-gotong-royong', title: 'Roadmap AI Gotong Royong bagi Bangsa Indonesia', + seoTitle: 'Roadmap AI Gotong Royong Indonesia', year: 2025, topic: 'Transformasi Digital', description: 'Pandangan tentang arah pengembangan AI yang kolaboratif di Indonesia.', @@ -65,6 +69,7 @@ export const learningResources: LearningResource[] = [ { slug: 'smart-digital-energy', title: 'Smart Digital Energy: Inovasi Teknologi Open Source untuk Pencapaian Target Produksi Migas', + seoTitle: 'Smart Digital Energy dengan Open Source', year: 2025, topic: 'Energi', description: 'Contoh pemanfaatan teknologi open source untuk sektor energi.', diff --git a/src/lib/projects.ts b/src/lib/projects.ts index 43c52ec..77e5937 100644 Binary files a/src/lib/projects.ts and b/src/lib/projects.ts differ diff --git a/src/pages/belajar/[slug].astro b/src/pages/belajar/[slug].astro index 6485125..9d8ef6c 100644 --- a/src/pages/belajar/[slug].astro +++ b/src/pages/belajar/[slug].astro @@ -19,7 +19,7 @@ const jsonLd = { }; --- - +
Ruang Belajar
diff --git a/src/pages/blog.astro b/src/pages/blog.astro index 38da69d..a0087f7 100644 --- a/src/pages/blog.astro +++ b/src/pages/blog.astro @@ -185,7 +185,9 @@ const statusLegend = [

Artikel disimpan per tahun dan bulan supaya arsip tetap mudah dipindai, - dipreview, dan diintegrasikan ke halaman blog nanti. + dipreview, dan disinkronkan ke website. Draf dapat dibuka melalui + /blog/preview/slug-artikel/ + tanpa ditampilkan di daftar blog atau sitemap.

content/
   2026/
diff --git a/src/pages/blog/[slug].astro b/src/pages/blog/[...slug].astro
similarity index 52%
rename from src/pages/blog/[slug].astro
rename to src/pages/blog/[...slug].astro
index e44748a..8717ff2 100644
--- a/src/pages/blog/[slug].astro
+++ b/src/pages/blog/[...slug].astro
@@ -6,21 +6,29 @@ import { withBase, safeHref } from '../../lib/urls';
 import { isPublished, renderArticle, type BlogPost } from '../../lib/content';
 
 export function getStaticPaths() {
-  // Only build pages for published posts so drafts are never reachable.
-  return (blogPosts as BlogPost[]).filter(isPublished).map((post) => ({
-    params: { slug: post.slug },
+  // Drafts use a distinct preview route and stay out of listings and the sitemap.
+  return (blogPosts as BlogPost[]).map((post) => ({
+    params: { slug: isPublished(post) ? post.slug : `preview/${post.slug}` },
     props: { post }
   }));
 }
 
 const { post } = Astro.props;
+const published = isPublished(post);
+const articlePath = published ? `/blog/${post.slug}` : `/blog/preview/${post.slug}`;
 // Render Markdown through the shared sanitizing pipeline (no raw `set:html`).
 const html = renderArticle(post.content);
-// Author link comes from frontmatter/git — allowlist the scheme before href.
-const authorUrl = safeHref(post.author?.url);
+const authors = post.authors?.length ? post.authors : post.author ? [post.author] : [];
 const authorDate = post.author?.committedAt
   ? new Intl.DateTimeFormat('id-ID', { dateStyle: 'long' }).format(new Date(post.author.committedAt))
   : '';
+const modifiedDate = post.lastModifiedAt
+  ? new Intl.DateTimeFormat('id-ID', { dateStyle: 'long' }).format(new Date(post.lastModifiedAt))
+  : '';
+const latestCommitUrl = safeHref(post.latestCommitUrl);
+const seoTitle = post.slug === 'bagaimana-open-source-mampu-bertahan'
+  ? 'Mengapa Open Source Bertahan Puluhan Tahun - IndopenSource'
+  : `${post.title} - IndopenSource`;
 const releaseDate = post.releasedAt
   ? new Intl.DateTimeFormat('id-ID', { dateStyle: 'long' }).format(new Date(post.releasedAt))
   : '';
@@ -30,7 +38,7 @@ const heroAlt = `Thumbnail artikel: ${post.title}`;
 // Provenance-aware author byline: frontmatter `authors[]` vs git commit metadata.
 const authorByline = post.authorFromFrontmatter
   ? `Disebutkan di frontmatter artikel${authorDate ? ` (commit pertama ${authorDate})` : ''}.`
-  : `Diambil otomatis dari commit pertama file artikel${authorDate ? ` pada ${authorDate}` : ''}.`;
+  : `Kontributor diambil dari riwayat commit sumber Markdown${authorDate ? published ? `. Terbit ${authorDate}` : `. Commit pertama ${authorDate}` : ''}${modifiedDate ? ` · diperbarui ${modifiedDate}` : ''}.`;
 
 // BlogPosting structured data (passed to BaseLayout's hashed jsonLd prop so it
 // is CSP-allow-listed in , not a separate inline script).
@@ -40,22 +48,27 @@ const jsonLd = {
   headline: post.title,
   description: post.description,
   image: thumbnailUrl,
-  ...(post.releasedAt ? { datePublished: post.releasedAt } : {}),
+  ...(published && post.releasedAt ? { datePublished: post.releasedAt } : {}),
   ...(post.lastModifiedAt ? { dateModified: post.lastModifiedAt } : {}),
-  ...(post.author?.name
-    ? { author: { '@type': 'Person', name: post.author.name, ...(authorUrl ? { url: authorUrl } : {}) } }
+  ...(authors.length
+    ? { author: authors.map((author) => ({ '@type': 'Person', name: author.name, ...(safeHref(author.url) ? { url: safeHref(author.url) } : {}) })) }
     : {}),
   publisher: { '@type': 'Organization', name: 'IndopenSource' }
 };
 ---
 
-
+
   
+    {!published && (
+      
+ Preview draf. Artikel ini masih ditinjau, belum diterbitkan, dan tidak diindeks mesin pencari. +
+ )}

{post.description}

- /blog/{post.slug} - {releaseDate && <>Rilis {releaseDate}} - {post.author?.name && <>oleh {post.author.name}} + {articlePath} + {releaseDate && <>{published ? 'Rilis' : 'Tanggal draf'} {releaseDate}} + {authors.length > 0 && <>oleh {authors.map((author) => author.name).join(' & ')}}
{post.status} @@ -74,19 +87,29 @@ const jsonLd = { Penulis
-
- { - post.author?.avatarUrl && ( - {`${post.author.name} - ) - } -
- {authorUrl - ? {post.author?.name} - : {post.author?.name}} -

{authorByline}

-
+
+ {authors.map((author) => { + const authorUrl = safeHref(author.url); + return ( +
+ {author.avatarUrl && ( + + )} +
+ {authorUrl + ? {author.name} + : {author.name}} +
+
+ ); + })}
+

{authorByline}

+ {latestCommitUrl && post.latestCommitSha && ( + + Commit terbaru {post.latestCommitSha.slice(0, 7)} ↗ + + )}
{c.tag} @@ -102,7 +103,7 @@ const channels = [ }`} > {c.label} - (situs eksternal) + {c.href.startsWith('http') && (situs eksternal)}
diff --git a/src/pages/donasi.astro b/src/pages/donasi.astro index 9c3b6be..b1ab0de 100644 --- a/src/pages/donasi.astro +++ b/src/pages/donasi.astro @@ -1,173 +1,5 @@ --- -import BaseLayout from '../layouts/BaseLayout.astro'; -import BaseButton from '../components/BaseButton.astro'; -import PageHeader from '../components/PageHeader.astro'; -import InfoCard from '../components/InfoCard.astro'; -import { withBase } from '../lib/urls'; - -const highlights = [ - { - title: 'Open Source', - desc: 'Sawer App adalah perangkat donasi yang source code-nya terbuka — bisa diaudit, difork, dan dipasang mandiri oleh proyek mana pun.' - }, - { - title: 'Untuk Komunitas', - desc: 'Dirancang khusus untuk kebutuhan ekosistem open source Indonesia. Ringan, transparan, tanpa iklan.' - }, - { - title: 'Bisa Dipasang Mandiri', - desc: 'Setiap proyek yang listing di IndopenSource nantinya bisa menggunakan Sawer App sebagai kanal donasi resmi proyek mereka.' - } -]; +import DonationPage from '../components/DonationPage.astro'; --- - - -

- IndopenSource adalah inisiatif komunitas yang dijalankan secara sukarela. - Ada dua cara untuk mendukung — donasi dari individu lewat Sawer App, - atau sponsor dari organisasi yang sejalan dengan misi open source Indonesia. -

-
- - {/* PREVIEW — banner mencolok, semua fitur masih preview */} -
-
-
-
- Preview -

Halaman dukungan masih tahap awal.

-

- Baik kanal donasi lewat Sawer App maupun paket sponsor - organisasi masih dalam persiapan. Yang bisa kamu lihat sekarang adalah - preview awal — pantau terus untuk info selanjutnya. -

-
- - Kunjungi Sawer App → - -
-
-
- - {/* DUA JALUR — donasi individu ⬄ sponsor organisasi */} -
-
-
- Dua jalur dukungan - // donasi & sponsor -

- Individu sawer, organisasi sponsor. -

-

- Baik kamu kontributor individu yang ingin menyawer proyek favorit, - maupun organisasi yang ingin berpartner secara resmi — ada jalurnya. - Dua bentuk dukungan ini menjaga IndopenSource tetap independen dan terbuka. -

-
- -
-
- -

Donasi individu

-

- Lewat Sawer App, kamu bisa mendukung proyek open source - secara langsung. Sawer App adalah platform donasi terbuka — source code-nya - publik, bisa dipasang mandiri, dan dirancang untuk ekosistem Indonesia. -

-
- - Coba Sawer App - -
-
- -
- -

Sponsor organisasi

-

- Organisasi bisa menjadi sponsor resmi IndopenSource — dukungan finansial - untuk infrastruktur, event komunitas, dan program pengembangan. - Tersedia beberapa tingkat sponsorship dengan benefit masing-masing. -

-
- - Lihat paket sponsor - -
-
-
-
-
- - {/* TENTANG SAWER — app open source untuk proyek yang listing */} -
-
-
- Sawer App - // platform donasi terbuka -

- Sawer dulu, baru saweran. -

-

- Sawer App adalah aplikasi donasi yang source code-nya terbuka untuk umum. - Bukan sekadar platform — ini adalah perangkat yang bisa dipakai ulang - oleh proyek open source Indonesia untuk menerima dukungan dari komunitas. -

-
- -
- {highlights.map((h) => ( - -

{h.desc}

-
- ))} -
- -
-
-

- Sawer App — open source, MIT license. - Setiap proyek yang listing di IndopenSource nantinya bisa menggunakan Sawer - sebagai kanal donasi resmi langsung dari halaman proyek mereka. -

-

- Repositori sedang disiapkan · pantau GitHub IndopenSource -

-
- - Daftarkan proyekmu - -
-
-
- - {/* CLOSING — coming soon dengan tiga aksi */} -
-
- Coming soon -

- Siap-siap. Sawer & Sponsor segera hadir. -

-

- Halaman ini masih preview — baik donasi individu lewat Sawer App - maupun paket sponsor organisasi sedang disiapkan. - Pantau GitHub dan website untuk info selanjutnya. -

-
- - Coba Sawer App → - - - Info sponsor - - - Ikuti blog - -
-
-
-
+ diff --git a/src/pages/falsafah.astro b/src/pages/falsafah.astro index af91978..7f69b0b 100644 --- a/src/pages/falsafah.astro +++ b/src/pages/falsafah.astro @@ -3,32 +3,43 @@ import BaseLayout from '../layouts/BaseLayout.astro'; import PageHeader from '../components/PageHeader.astro'; import LightSvgMotion from '../components/LightSvgMotion.astro'; -// Three pillars, numbered editorial-style. The page logic is purely -// presentational (static content) so the restyle is the whole job; an array -// keeps the numbered rhythm consistent and easy to read. const prinsip = [ { no: '01', - tag: 'Terbuka', - title: 'Kode, jejak, dan keputusan dibuka.', - body: 'Kode, diskusi, keputusan, dan roadmap dibuat mudah ditemukan oleh publik — bukan rahasia tim, tapi catatan bersama yang bisa dibaca siapa saja.', - meta: '// transparansi default' + tag: 'Kebebasan', + title: 'Boleh dipelajari, diubah, dan dibagikan.', + body: 'Akses ke kode sumber bukan tujuan akhir. Orang perlu punya ruang nyata untuk memahami cara kerja perangkat lunak, menyesuaikannya dengan kebutuhan, dan meneruskan perbaikan kepada orang lain.', + meta: '// pengguna juga pelaku' }, { no: '02', - tag: 'Terawat', - title: 'Daftar yang dijaga, bukan ditumpuk.', - body: 'Daftar proyek perlu kurasi, metadata yang jelas, dan ritme pembaruan yang sehat. Yang dirawat tetap berguna; yang ditinggalkan jadi beban.', - meta: '// kurasi > akumulasi' + tag: 'Terbuka', + title: 'Kerja terlihat, agar orang bisa ikut.', + body: 'Kode, diskusi, keputusan, dokumentasi, dan roadmap sebaiknya meninggalkan jejak publik. Keterbukaan membuat masalah lebih mudah ditemukan, pengetahuan tidak berhenti pada satu orang, dan kontribusi dapat diperiksa bersama.', + meta: '// catatan adalah infrastruktur' }, { no: '03', - tag: 'Bermanfaat', - title: 'Dipakai orang Indonesia, hari ini.', - body: 'Fokus pada perangkat, pustaka, data, dan praktik yang membantu pengguna Indonesia secara nyata — bukan demo yang mengkilap lalu terlupakan.', - meta: '// manfaat lokal nyata' + tag: 'Terawat', + title: 'Rilis bukan garis akhir.', + body: 'Proyek yang berguna memerlukan kurasi, dokumentasi, review, keamanan, dan regenerasi pengelola. Merawat pekerjaan lama sama berharganya dengan memulai repository baru.', + meta: '// keberlanjutan adalah kerja' + }, + { + no: '04', + tag: 'Berakar', + title: 'Terbuka untuk kebutuhan Indonesia.', + body: 'Open source memberi ruang untuk membangun kemampuan sendiri, memakai ulang pengetahuan, dan menyesuaikan teknologi dengan bahasa serta kebutuhan lokal. Kemandirian tidak berarti bekerja sendirian; ia tumbuh lewat gotong royong lintas proyek dan komunitas.', + meta: '// lokal, bukan terisolasi' } ]; + +const references = [ + { title: 'History of the Open Source Initiative', note: 'sejarah istilah dan gerakan open source', href: 'https://opensource.org/about/history-of-the-open-source-initiative' }, + { title: 'GNU Manifesto', note: 'kebebasan mempelajari, mengubah, dan membagikan perangkat lunak', href: 'https://web.cs.ucdavis.edu/~rogaway/classes/188/materials/gnu-manifesto.pdf' }, + { title: 'The Cathedral and the Bazaar', note: 'pengembangan terbuka, umpan balik, dan kolaborasi publik', href: 'https://firstmonday.org/ojs/index.php/fm/article/download/578/499?inline=1' }, + { title: 'Indonesia, Go Open Source', note: 'jejak gerakan dan kemandirian teknologi di Indonesia', href: 'https://id.wikipedia.org/wiki/Indonesia,_Go_Open_Source' } +]; ---
- {/* Three numbered principles — editorial grid, asymmetric: oversized index + + {/* Numbered principles — editorial grid, asymmetric: oversized index + sticker tag on the left rail, the statement and body on the right. Thick ink rule separates each entry. */}
- Tiga prinsip + Empat prinsip - +
@@ -97,6 +108,39 @@ const prinsip = [
+
+
+ Bacaan dasar +

Falsafah yang bisa ditelusuri.

+

Prinsip di atas dirumuskan untuk konteks IndopenSource dari sejarah dan praktik gerakan open source. Ini bukan kutipan atau definisi hukum lisensi, melainkan pegangan kerja komunitas.

+
+ {references.map((reference) => ( + +

{reference.title}

+

{reference.note}

+ Baca sumber ↗ +
+ ))} +
+
+
+ +
+
+
+ +
+ Segera hadir +

Buku Manifesto Perangkat Terbuka.

+

Sebuah buku yang sedang disiapkan untuk merangkum gagasan, praktik, dan arah gerakan perangkat terbuka dalam konteks Indonesia.

+

COMING SOON · belum ada tanggal terbit

+
+
+
+
+ {/* Closing stamp — a halftone poster band with a rubber-stamp sign-off, so the manifesto ends on a printed, community-made note. ink/brand on canvas all AA. */} diff --git a/src/pages/faq.astro b/src/pages/faq.astro index d30d979..087a573 100644 --- a/src/pages/faq.astro +++ b/src/pages/faq.astro @@ -17,28 +17,28 @@ const faqs = [ { category: 'Proyek', items: [ - { q: 'Bagaimana cara mendaftarkan proyek saya?', a: 'Buka issue di repository awesome-indonesia dengan template yang tersedia. Tim kurasi akan mereview dan membantu menambahkan proyek Anda ke direktori.' }, - { q: 'Apa syarat proyek bisa masuk direktori?', a: 'Proyek harus open source (lisensi OSI-approved), dibuat atau dipelihara oleh orang Indonesia, dan memiliki dokumentasi dasar (README, cara install). Tidak harus besar atau populer.' }, - { q: 'Proyek saya tidak aktif, apakah bisa dihapus?', a: 'Kami memiliki kategori "revival" untuk proyek potensial yang perlu dihidupkan kembali. Jika proyek benar-benar tidak relevan, kami bisa memindahkannya ke arsip.' }, - { q: 'Bolehkah proyek komersial masuk?', a: 'Boleh, selama ada komponen open source yang jelas dan lisensinya transparan. Kami juga menerima proyek dengan model open-core.' } + { q: 'Bagaimana cara mendaftarkan proyek saya?', a: 'Tambahkan nama repository ke repos.json di repository awesome-indonesia, jalankan validasi, lalu kirim pull request dengan ringkasan proyek.' }, + { q: 'Apa syarat proyek bisa masuk direktori?', a: 'Repository harus publik, relevan dengan ekosistem Indonesia, aktif atau fresh pada 2026, dan bukan duplikasi entri yang sudah ada.' }, + { q: 'Di mana resource dan proyek lama ditempatkan?', a: 'Resource non-app dan materi belajar masuk ke Awesome Indonesia Legacy. Proyek lama atau kandidat yang potensial dihidupkan kembali masuk ke Awesome Indonesia Revival.' }, + { q: 'Apakah metadata README harus diedit manual?', a: 'Tidak. Contributor cukup mengubah repos.json. Metadata README akan dibuat dari GitHub API melalui workflow setelah perubahan digabungkan.' } ] }, { category: 'Kontribusi', items: [ { q: 'Saya pemula, bisa mulai dari mana?', a: 'Cari issue berlabel "good first issue" di repository yang menarik. Atau mulai dari kontribusi non-kode: perbaiki dokumentasi, laporkan bug, atau bantu terjemahan.' }, - { q: 'Bagaimana cara menjadi maintainer?', a: 'Mulai dengan kontribusi konsisten di satu proyek. Setelah beberapa PR di-merge dan Anda memahami kodebase, maintainer existing bisa mengundang Anda menjadi co-maintainer.' }, - { q: 'PR saya tidak di-review, harus bagaimana?', a: 'Bisa mention maintainer di PR atau tanya di Discussions. Kadang review tertunda karena maintainer sibuk. Bersabar dan sopan selalu membantu.' }, - { q: 'Apakah ada mentoring untuk kontributor baru?', a: 'Ya! Kami memiliki program mentoring melalui Discussions. Anda bisa meminta guidance dari maintainer berpengalaman atau kontributor senior.' } + { q: 'Bagaimana cara membantu repository lain?', a: 'Baca panduan kontribusi repository tersebut, pilih issue yang sesuai, lalu kirim perubahan kecil yang mudah ditinjau.' }, + { q: 'PR saya belum ditinjau, harus bagaimana?', a: 'Tambahkan konteks yang kurang di PR lalu tanyakan dengan sopan kepada maintainer. Setiap repository memiliki ritme review berbeda.' }, + { q: 'Apakah ada program mentoring?', a: 'Belum ada program mentoring resmi. Pertanyaan dan usulan bantuan dapat dibuka melalui GitHub Discussions.' } ] }, { category: 'Teknis', items: [ - { q: 'Lisensi apa yang direkomendasikan?', a: 'Untuk proyek software: MIT, Apache 2.0, atau GPL. Untuk dokumentasi: CC-BY-SA. Pilih yang sesuai dengan tujuan dan komunitas target Anda.' }, + { q: 'Lisensi apa yang sebaiknya dipilih?', a: 'Pilih lisensi yang sesuai dengan tujuan proyek dan pahami kewajibannya. Direktori menampilkan lisensi dari metadata repository, tetapi tidak memberikan nasihat hukum.' }, { q: 'Bagaimana cara menangani security vulnerability?', a: 'Laporkan secara privat ke maintainer atau via halaman Security kami. Jangan buka issue publik untuk vulnerability sebelum ada patch.' }, - { q: 'Apakah ada CI/CD yang bisa dipakai?', a: 'GitHub Actions tersedia gratis untuk repository publik. Kami juga menyediakan template workflow untuk testing, linting, dan deployment.' }, - { q: 'Bagaimana cara hosting dokumentasi?', a: 'GitHub Pages, Netlify, atau Vercel menyediakan hosting gratis untuk static site. Astro (yang kami pakai) bisa di-deploy ke mana saja.' } + { q: 'Apakah IndopenSource menyediakan template CI/CD?', a: 'Belum ada template CI/CD resmi. Lihat workflow repository IndopenSource sebagai referensi, lalu sesuaikan dengan kebutuhan proyek.' }, + { q: 'Bagaimana website ini dibuat?', a: 'Website ini menggunakan Astro. Kode dan workflow deployment tersedia di repository indopensource.org.' } ] } ]; diff --git a/src/pages/forum.astro b/src/pages/forum.astro index 27ba0bb..6c2f78c 100644 --- a/src/pages/forum.astro +++ b/src/pages/forum.astro @@ -15,7 +15,7 @@ import PageHeader from '../components/PageHeader.astro';

Buka discussions - Buka project board + Lihat roadmap
diff --git a/src/pages/index.astro b/src/pages/index.astro index 6fa4bbb..cd47e24 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -4,15 +4,20 @@ import BaseLayout from '../layouts/BaseLayout.astro'; import HomeHero from '../components/HomeHero.astro'; import InfoCard from '../components/InfoCard.astro'; import ProjectCard from '../components/ProjectCard.astro'; -import RoadmapTimeline from '../components/RoadmapTimeline.astro'; import SectionHeader from '../components/SectionHeader.astro'; import BaseButton from '../components/BaseButton.astro'; import LightSvgMotion from '../components/LightSvgMotion.astro'; -import projects from '../data/projects.json'; +import projectsData from '../data/projects.json'; +import { roadmapPeriods } from '../data/roadmap'; import { heroImage, HERO_PRELOAD_WIDTH, HERO_QUALITY } from '../lib/hero'; +import { rankProjectOwners, type Project } from '../lib/projects'; +import { withBase } from '../lib/urls'; +const projects = projectsData as Project[]; const totalProjects = projects.length; const topProjects = projects.slice(0, 5); +const roadmapPreview = roadmapPeriods.slice(0, 2); +const topUsers = rankProjectOwners(projects).slice(0, 5); // Pre-generate the AVIF the hero will paint as its LCP candidate so we // can the exact same /_astro/* file from . Same src, @@ -69,11 +74,13 @@ const heroPreload = await getImage({ // kenapa kami ada
- Open source bukan cuma kode di GitHub — ini soal merawat catatan, - membuka pintu kontribusi, dan menulis ulang cerita teknologi Indonesia - secara terbuka. + Open source Indonesia itu soal merawat catatan, membuka pintu kontribusi, + kolaborasi dan menulis ulang cerita teknologi Indonesia secara terbuka.
-

— IndopenSource · komunitas terbuka

+
+

— IndopenSource · komunitas terbuka

+ Baca falsafah +
@@ -101,7 +108,7 @@ const heroPreload = await getImage({ Semua orang boleh ikut menulis catatannya
- Mulai kontribusi + Mulai kontribusi

@@ -119,13 +126,26 @@ const heroPreload = await getImage({

- +
- Gabung diskusi + Lihat selengkapnya
- +
+ {roadmapPreview.map((item, index) => ( +
+
+ {item.period} + +
+

{item.title}

+
    + {item.items.map((entry) =>
  • {entry}
  • )} +
+
+ ))} +
@@ -149,7 +169,9 @@ const heroPreload = await getImage({
- + + Pilihan proyek dari katalog awesome-indonesia. +
Buka semua @@ -169,6 +191,59 @@ const heroPreload = await getImage({
+
+
+
+
+ + Peringkat berdasarkan akumulasi stars pada repository yang tercatat di direktori. + +
+
Lihat semua
+
+ +
+
+ +
+ +
+
diff --git a/src/pages/kode-etik.astro b/src/pages/kode-etik.astro index f0f1d3b..49b8483 100644 --- a/src/pages/kode-etik.astro +++ b/src/pages/kode-etik.astro @@ -20,11 +20,6 @@ const unacceptable = [ 'Perilaku lain yang secara wajar dapat dianggap tidak pantas dalam lingkungan profesional' ]; -const enforcement = [ - { level: 'Koreksi', desc: 'Peringatan tertulis dari maintainer. Permintaan untuk menghentikan perilaku.', action: 'Peringatan verbal/written' }, - { level: 'Peringatan', desc: 'Pelanggaran serius atau berulang. Dilarang berpartisipasi di ruang komunitas untuk periode tertentu.', action: 'Suspend sementara' }, - { level: 'Larangan', desc: 'Pelanggaran berat atau berkelanjutan. Dilarang permanen dari semua ruang komunitas.', action: 'Ban permanen' } -]; --- @@ -85,15 +80,9 @@ const enforcement = [

Tindakan untuk pelanggaran.

Maintainer komunitas berhak mengambil tindakan yang sesuai untuk menegakkan kode etik ini.

-
- {enforcement.map((e, i) => ( -
- Level {i + 1} -

{e.level}

-

{e.desc}

-

{e.action}

-
- ))} +
+

Maintainer dapat memoderasi issue, Discussions, pull request, dan ruang komunitas lain agar kolaborasi tetap sehat dan berguna. Tindakan disesuaikan dengan konteks pelanggaran.

+ Baca dokumen sumber →
@@ -103,7 +92,7 @@ const enforcement = [
Laporan

Melaporkan pelanggaran.

-

Jika Anda mengalami atau menyaksikan pelanggaran kode etik, silakan laporkan kepada tim maintainer. Semua laporan akan ditangani secara rahasia dan serius.

+

Jika Anda mengalami atau menyaksikan pelanggaran kode etik, laporkan melalui kanal privat atau kepada maintainer IndopenSource yang Anda percaya.

Laporkan via Email Diskusi publik diff --git a/src/pages/komunitas.astro b/src/pages/komunitas.astro index e92d00f..b7fbb38 100644 --- a/src/pages/komunitas.astro +++ b/src/pages/komunitas.astro @@ -2,77 +2,73 @@ import BaseLayout from '../layouts/BaseLayout.astro'; import BaseButton from '../components/BaseButton.astro'; import PageHeader from '../components/PageHeader.astro'; -import LightSvgMotion from '../components/LightSvgMotion.astro'; +import communitiesData from '../data/communities.json'; +import { safeHref } from '../lib/urls'; -const paths = [ - { no: '01', tag: 'Temukan', title: 'Kurasi satu proyek.', body: 'Tambahkan proyek Indonesia yang layak dipakai, atau rapikan informasi proyek yang sudah ada.', href: '/projects/', action: 'Buka direktori' }, - { no: '02', tag: 'Tulis', title: 'Bagikan yang dipelajari.', body: 'Tulis catatan teknis, pengalaman merawat proyek, atau panduan praktis untuk pembaca berikutnya.', href: '/blog/', action: 'Baca dan kirim artikel' }, - { no: '03', tag: 'Diskusikan', title: 'Bawa pertanyaan ke ruang terbuka.', body: 'Mulai dari ide kecil, kendala kontribusi, atau usulan kolaborasi lintas komunitas.', href: 'https://github.com/orgs/IndopenSource/discussions', action: 'Buka Discussions' }, - { no: '04', tag: 'Rawat', title: 'Bantu pekerjaan yang sering tak terlihat.', body: 'Dokumentasi, terjemahan, pengujian, desain, dan review sama pentingnya dengan menulis kode.', href: 'https://github.com/IndopenSource', action: 'Lihat repository' } -]; +interface Community { + name: string; + description: string; + focus?: string; + location?: string; + url: string; +} -const spaces = [ - { label: 'Proyek', note: 'direktori dan metadata', href: '/projects/' }, - { label: 'Blog', note: 'catatan dan pembelajaran', href: '/blog/' }, - { label: 'Discussions', note: 'tanya, usul, koordinasi', href: 'https://github.com/orgs/IndopenSource/discussions' }, - { label: 'Roadmap', note: 'pekerjaan yang sedang dibuka', href: 'https://github.com/orgs/IndopenSource/projects' } -]; +const communities = communitiesData as Community[]; --- - -

IndopenSource adalah ruang kerja terbuka. Pilih kontribusi yang sesuai dengan waktu, pengalaman, dan rasa ingin tahu Anda—lalu tinggalkan jejak yang bisa diteruskan orang lain.

+ +

Tempat menemukan komunitas, kelompok pengguna, dan ruang belajar terbuka dari berbagai daerah dan bidang. Daftarnya sedang kami siapkan bersama.

-
- -
- Mulai di sini -

Kontribusi kecil yang terbuka lebih berguna daripada ide besar yang tidak pernah dibagikan.

-
- Mulai diskusi - Cari proyek -
-
-
- -
+
-
-
Empat jalur

Pilih pintu yang paling dekat.

- tidak perlu menunggu undangan -
-
- {paths.map((path) => ( - - ))} -
+ {communities.length === 0 ? ( +
+ Segera hadir +

Belum ada komunitas yang ditampilkan.

+

Kami sengaja memulai dari daftar kosong agar setiap entri punya sumber, tautan aktif, dan diajukan secara terbuka—bukan hasil asumsi.

+
+ ) : ( + + )}
-
-
+
+
- Ritme sederhana -

Baca, bicara, lalu buat perubahan.

+ Tambahkan komunitas +

Buka pull request di repository website.

+

Pengurus atau anggota komunitas boleh mengusulkan entri. Sertakan informasi yang bisa diverifikasi dan pastikan Anda memang berwenang membagikan profil tersebut.

    -
  1. 01 — Pilih satu hal. Temukan proyek, tulisan, atau percakapan yang benar-benar ingin Anda bantu.
  2. -
  3. 02 — Tinggalkan konteks. Ajukan pertanyaan atau usulan secara publik agar orang lain dapat menambahkan pengetahuan.
  4. -
  5. 03 — Kirim kontribusi kecil. Mulai dari koreksi, tautan, contoh penggunaan, atau pull request yang mudah ditinjau.
  6. +
  7. 01 — Fork repository. Buka IndopenSource/indopensource.org lalu buat branch baru.
  8. +
  9. 02 — Tambahkan data. Edit src/data/communities.json dengan nama, deskripsi, wilayah atau fokus, dan tautan resmi.
  10. +
  11. 03 — Verifikasi lokal. Jalankan npm test dan npm run build.
  12. +
  13. 04 — Buka PR ke main. Jelaskan hubungan Anda dengan komunitas serta sumber yang dapat dipakai reviewer untuk memeriksa entri.
diff --git a/src/pages/legal.astro b/src/pages/legal.astro index 617b979..643722a 100644 --- a/src/pages/legal.astro +++ b/src/pages/legal.astro @@ -5,56 +5,34 @@ import PageHeader from '../components/PageHeader.astro'; import { withBase } from '../lib/urls'; const policies = [ - { - icon: 'ph-copyright', - title: 'Lisensi Konten', - desc: 'Konten di website IndopenSource (artikel, dokumentasi, kurasi) dilisensikan di bawah Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0). Anda bebas membagikan dan mengadaptasi konten dengan syarat atribusi dan lisensi yang sama.', - link: 'https://creativecommons.org/licenses/by-sa/4.0/', - isExternal: true - }, { icon: 'ph-code', title: 'Lisensi Kode', - desc: 'Kode sumber website ini dan repository komunitas dilisensikan di bawah MIT License. Proyek yang terkurasi di direktori memiliki lisensi masing-masing yang tercantum di metadata proyek.', + desc: 'Kode sumber website ini dilisensikan di bawah MIT License. Setiap proyek dalam direktori memiliki lisensinya sendiri seperti tercantum pada metadata repository asal.', link: 'https://github.com/IndopenSource/indopensource.org/blob/main/LICENSE', isExternal: true }, { icon: 'ph-shield-warning', title: 'Pelaporan Kerentanan', - desc: 'Jika Anda menemukan kerentanan keamanan di website atau proyek komunitas, laporkan secara bertanggung jawab melalui halaman Security atau email ke security@indopensource.org. Jangan buka issue publik.', + desc: 'Jangan membuka issue publik untuk laporan sensitif. Ikuti halaman Security atau kirim detail melalui kanal privat yang tersedia.', link: '/security/', isExternal: false }, - { - icon: 'ph-gavel', - title: 'DMCA & Hak Cipta', - desc: 'Kami menghormati hak kekayaan intelektual. Jika Anda yakin konten di platform kami melanggar hak cipta, kirimkan notifikasi DMCA ke legal@indopensource.org dengan informasi lengkap.', - link: 'mailto:legal@indopensource.org', - isExternal: true - }, - { - icon: 'ph-trademark', - title: 'Merek Dagang', - desc: 'Nama "IndopenSource", logo, dan identitas visual adalah merek komunitas. Penggunaan harus mendapat izin tertulis. Proyek yang terkurasi tetap memegang merek mereka sendiri.', - link: 'mailto:admin@indopensource.org', - isExternal: true - }, { icon: 'ph-user-circle', title: 'Privasi & Data', - desc: 'Kami tidak mengumpulkan data pribadi beyond yang diperlukan untuk operasional (GitHub username untuk kontribusi). Tidak ada tracking analytics pihak ketiga. Cookie hanya untuk fungsionalitas dasar.', - link: '#', - isExternal: false + desc: 'Website publik ini dapat dibaca tanpa akun atau formulir data pribadi. Saat mengikuti tautan ke GitHub atau layanan lain, kebijakan layanan tersebut berlaku.', + link: 'https://github.com/IndopenSource/indopensource.org', + isExternal: true } ]; const disclaimers = [ - 'IndopenSource adalah inisiatif komunitas, bukan entitas hukum formal.', 'Konten yang dikurasi adalah tanggung jawab masing-masing maintainer proyek.', 'Kami tidak memberikan nasihat hukum atau lisensi profesional.', 'Tautan ke proyek eksternal tidak merupakan endorsement.', - 'Komunitas berhak menolak atau menghapus konten yang melanggar kode etik.' + 'Entri direktori dapat diperbarui atau dihapus jika tidak lagi memenuhi aturan kontribusi.' ]; --- @@ -89,7 +67,7 @@ const disclaimers = [

{p.title}

{p.desc}

- Selengkapnya → + Selengkapnya →
diff --git a/src/pages/press.astro b/src/pages/press.astro index febd715..598c4a9 100644 --- a/src/pages/press.astro +++ b/src/pages/press.astro @@ -1,89 +1,46 @@ --- import BaseLayout from '../layouts/BaseLayout.astro'; -import BaseButton from '../components/BaseButton.astro'; import PageHeader from '../components/PageHeader.astro'; import { withBase } from '../lib/urls'; import projectsData from '../data/projects.json'; -import legacyData from '../data/legacy.json'; -import revivalData from '../data/revival.json'; - -const allProjects = [...(projectsData as any[]), ...(legacyData as any[]), ...(revivalData as any[])]; -const totalProjects = allProjects.length; -const totalStars = allProjects.reduce((sum, p) => sum + (p.stars ?? 0), 0); -const totalForks = allProjects.reduce((sum, p) => sum + (p.forks ?? 0), 0); - -const brandAssets = [ - { name: 'Logo Utama (SVG)', desc: 'Format vektor untuk print & digital', icon: 'ph-file-svg', link: '/brand/indopensource-logo.svg', available: false }, - { name: 'Logo Utama (PNG)', desc: 'Resolusi tinggi, background transparan', icon: 'ph-file-png', link: '/brand/indopensource-logo.png', available: true }, - { name: 'Icon Only', desc: 'Mark tanpa teks untuk avatar & favicon', icon: 'ph-image', link: '/brand/indopensource-icon.svg', available: false }, - { name: 'Brand Guidelines', desc: 'Panduan penggunaan logo & warna', icon: 'ph-book-open', link: '/brand/brand-guidelines.pdf', available: false } -]; - -const colorPalette = [ - { name: 'Brand Green', hex: '#4d7c0f', tailwind: 'bg-[#4d7c0f]', usage: 'Primary brand color' }, - { name: 'Brand Dark', hex: '#365314', tailwind: 'bg-[#365314]', usage: 'Dark variant' }, - { name: 'Sun', hex: '#ecfccb', tailwind: 'bg-[#ecfccb]', usage: 'Highlight & accent' }, - { name: 'Ink', hex: '#101828', tailwind: 'bg-[#101828]', usage: 'Text & dark backgrounds' }, - { name: 'Paper', hex: '#ffffff', tailwind: 'bg-[#ffffff]', usage: 'Light backgrounds' }, - { name: 'Canvas', hex: '#f8fafc', tailwind: 'bg-[#f8fafc]', usage: 'Subtle backgrounds' } -]; - -const pressCoverage = [ - { title: 'IndopenSource: Menghidupkan Kembali Proyek Open Source Indonesia', source: 'Tech in Asia', date: '2025', link: '#' }, - { title: 'Kolaborasi Komunitas OSS Indonesia Melalui IndopenSource', source: 'Kompas Tech', date: '2025', link: '#' }, - { title: 'Direktori Proyek Open Source Buatan Anak Bangsa', source: 'IDN Times', date: '2025', link: '#' } -]; +const projects = (projectsData as any[]).filter((project) => !project.syncFailed); +const totalStars = projects.reduce((sum, project) => sum + (project.stars ?? 0), 0); +const totalForks = projects.reduce((sum, project) => sum + (project.forks ?? 0), 0); const keyFacts = [ - { label: 'Didirikan', value: 'Maret 2025' }, - { label: 'Proyek terkurasi', value: `${totalProjects}+` }, - { label: 'Total bintang GitHub', value: totalStars.toLocaleString('id-ID') }, - { label: 'Fork komunitas', value: totalForks.toLocaleString('id-ID') }, - { label: 'Kontributor aktif', value: '100+' }, - { label: 'Website', value: 'indopensource.org' } + { label: 'Proyek tersinkron', value: projects.length.toLocaleString('id-ID') }, + { label: 'Akumulasi stars', value: totalStars.toLocaleString('id-ID') }, + { label: 'Akumulasi forks', value: totalForks.toLocaleString('id-ID') } ]; - const contact = [ - { type: 'Umum', email: 'admin@indopensource.org', desc: 'Pertanyaan umum & kemitraan' }, - { type: 'Media', email: 'press@indopensource.org', desc: 'Wawancara, liputan, press release' }, - { type: 'Sponsor', email: 'sponsor@indopensource.org', desc: 'Sponsorship & dukungan finansial' } + { type: 'Umum', email: 'admin@indopensource.org', desc: 'Pertanyaan umum dan kemitraan' }, + { type: 'Media', email: 'press@indopensource.org', desc: 'Wawancara dan kebutuhan media' }, + { type: 'Sponsor', email: 'sponsor@indopensource.org', desc: 'Sponsorship dan dukungan finansial' } ]; --- - - -

Halaman ini menyediakan brand assets, fakta kunci, dan informasi kontak untuk jurnalis, blogger, dan organisasi yang ingin meliput atau bermitra dengan IndopenSource.

+ + +

Gunakan ringkasan, data direktori, dan logo yang tersedia di halaman ini. Statistik mengikuti snapshot data proyek terbaru.

- Media kit -

Semua yang Anda butuhkan untuk memberitakan IndopenSource.

+ Tentang IndopenSource +

Ruang terbuka untuk menemukan, mempelajari, dan merawat proyek open source Indonesia.

-
-
- Brand assets -

Logo & identitas visual.

-

Gunakan asset ini sesuai brand guidelines. Untuk penggunaan khusus, hubungi tim media.

+
+
+ Logo tersedia +

Logo IndopenSource.

+

Saat ini hanya logo PNG berikut yang disediakan sebagai aset resmi situs.

+ Download PNG →
-
- {brandAssets.map((asset) => ( -
-
- -
-

{asset.name}

-

{asset.desc}

- {asset.available ? ( - Download → - ) : ( - Segera hadir - )} -
- ))} +
+ Logo IndopenSource
@@ -91,33 +48,13 @@ const contact = [
- Color palette -

Warna brand IndopenSource.

-
-
- {colorPalette.map((color) => ( -
-
-
-

{color.name}

-

{color.hex}

-

{color.usage}

-
-
- ))} -
-
-
- -
-
-
- Fakta kunci -

Angka & informasi penting.

+ Snapshot direktori +

Angka yang dapat diperiksa.

+

Hanya menghitung repo utama yang berhasil disinkronkan; koleksi Legacy dan Revival tidak digabungkan.

-
+
{keyFacts.map((fact) => ( -
+

{fact.label}

{fact.value}

@@ -126,36 +63,15 @@ const contact = [
-
-
-
- Liputan media -

IndopenSource di berita.

-
-
- {pressCoverage.map((item) => ( - - ))} -
-
-
-
-
- Kontak media -

Hubungi tim kami.

-
+
Kontak

Hubungi kami.

- {contact.map((c) => ( + {contact.map((item) => ( ))}
diff --git a/src/pages/projects.astro b/src/pages/projects.astro index 2ce12db..296da95 100644 --- a/src/pages/projects.astro +++ b/src/pages/projects.astro @@ -2,7 +2,8 @@ import BaseLayout from '../layouts/BaseLayout.astro'; import PageHeader from '../components/PageHeader.astro'; import ProjectsDirectory from '../components/ProjectsDirectory.astro'; -import type { Project } from '../lib/projects'; +import { rankProjectOwners, type Project } from '../lib/projects'; +import { withBase } from '../lib/urls'; import projectsData from '../data/projects.json'; import legacyData from '../data/legacy.json'; import revivalData from '../data/revival.json'; @@ -27,6 +28,8 @@ const latestUpdate = refreshedProjects const latestUpdateLabel = latestUpdate ? new Intl.DateTimeFormat('id-ID', { dateStyle: 'medium' }).format(new Date(latestUpdate)) : ''; +const ownerAvatarUrl = (owner: string) => `https://avatars.githubusercontent.com/${encodeURIComponent(owner)}?size=80`; +const topUsers = rankProjectOwners(refreshedProjects).slice(0, 5); ---
+ +
+
+
+
+ Top user +

Lima penggerak teratas.

+
+ + Lihat semua + +
+ +
+
{legacyProjects.length > 0 && ( @@ -74,6 +105,7 @@ const latestUpdateLabel = latestUpdate

Resource dan proyek non-app open source Indonesia yang masih relevan sebagai referensi pembelajaran.

+ Buka repository Legacy
{legacyProjects.map((repo) => ( @@ -84,7 +116,13 @@ const latestUpdateLabel = latestUpdate rel="noopener noreferrer" >
-

{repo.name}

+
+ +
+

{repo.name}

+ oleh {repo.owner} +
+

{repo.description || 'Tidak ada deskripsi'}

@@ -109,6 +147,7 @@ const latestUpdateLabel = latestUpdate

Proyek open source Indonesia yang butuh kontribusi untuk aktif kembali.

+ Buka repository Revival
{revivalProjects.map((repo) => ( @@ -119,7 +158,13 @@ const latestUpdateLabel = latestUpdate rel="noopener noreferrer" >
-

{repo.name}

+
+ +
+

{repo.name}

+ oleh {repo.owner} +
+

{repo.description || 'Tidak ada deskripsi'}

diff --git a/src/pages/projects/[slug].astro b/src/pages/projects/[slug].astro index 55d2bcd..2a050e7 100644 --- a/src/pages/projects/[slug].astro +++ b/src/pages/projects/[slug].astro @@ -22,7 +22,10 @@ const repoName = project.fullName.split('/').at(-1) || project.fullName; // no longer fetches GitHub or parses Markdown. const readmeHtml = project.readmeHtml || ''; // Snippet-clamped description for /OG, falling back to the raw one (SEO-6). -const metaDescription = project.metaDescription || project.description; +const sourceDescription = project.metaDescription || project.description; +const metaDescription = sourceDescription === 'Proyek dari daftar awesome-indonesia.' + ? `${repoName} adalah proyek open source Indonesia yang terdaftar di direktori IndopenSource.` + : sourceDescription; const topics = project.topics ?? []; // SoftwareSourceCode structured data so search engines understand each project @@ -48,10 +51,11 @@ const jsonLd = { ---
diff --git a/src/pages/roadmap.astro b/src/pages/roadmap.astro new file mode 100644 index 0000000..18c3545 --- /dev/null +++ b/src/pages/roadmap.astro @@ -0,0 +1,38 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; +import BaseButton from '../components/BaseButton.astro'; +import PageHeader from '../components/PageHeader.astro'; +import { roadmapPeriods } from '../data/roadmap'; +--- + + + +

Rencana kerja publik untuk menjaga direktori, pengetahuan, dukungan proyek, dan transparansi komunitas. Urutan dapat berubah mengikuti kebutuhan dan kapasitas kontributor.

+
+ +
+
+
    + {roadmapPeriods.map((item, index) => ( +
  1. +
    + {item.period} + +
    +

    {item.title}

    +
      + {item.items.map((entry) =>
    • {entry}
    • )} +
    +
  2. + ))} +
+
+
+

Roadmap ini milik komunitas.

+

Usulkan perubahan, tawarkan bantuan, atau ceritakan kebutuhan proyekmu melalui kanal komunitas.

+
+
Buka diskusi
+
+
+
+
diff --git a/src/pages/robots.txt.ts b/src/pages/robots.txt.ts index 5be2f64..be84ffb 100644 --- a/src/pages/robots.txt.ts +++ b/src/pages/robots.txt.ts @@ -10,7 +10,16 @@ export const GET: APIRoute = ({ site }) => { const origin = site ?? new URL('https://indopensource.org'); const sitemapUrl = new URL('/sitemap.xml', origin).toString(); - return new Response(`User-agent: * + return new Response(`User-agent: OAI-SearchBot +Allow: / + +User-agent: ChatGPT-User +Allow: / + +User-agent: GPTBot +Allow: / + +User-agent: * Allow: / Sitemap: ${sitemapUrl} diff --git a/src/pages/sitemap.xml.ts b/src/pages/sitemap.xml.ts index 730929c..1368570 100644 --- a/src/pages/sitemap.xml.ts +++ b/src/pages/sitemap.xml.ts @@ -35,8 +35,23 @@ const staticPages: SitemapEntry[] = [ { path: '/' }, { path: '/falsafah/' }, { path: '/projects/' }, + { path: '/users/' }, + { path: '/belajar/' }, { path: '/blog/' }, { path: '/forum/' }, + { path: '/faq/' }, + { path: '/cara-berkontribusi/' }, + { path: '/kode-etik/' }, + { path: '/roadmap/' }, + { path: '/tentang/' }, + { path: '/komunitas/' }, + { path: '/program/' }, + { path: '/tata-kelola/' }, + { path: '/events/' }, + { path: '/donasi/' }, + { path: '/press/' }, + { path: '/legal/' }, + { path: '/security/' }, { path: '/contact/' } ]; @@ -44,7 +59,7 @@ const blogPages: SitemapEntry[] = blogPosts .filter((post) => isPublished(post)) .map((post) => ({ path: `/blog/${post.slug}/`, - lastmod: toLastmod(post.releasedAt) ?? toLastmod(post.date) + lastmod: toLastmod(post.lastModifiedAt) ?? toLastmod(post.releasedAt) ?? toLastmod(post.date) })); const projectPages: SitemapEntry[] = projects.map((project) => ({ diff --git a/src/pages/sponsors.astro b/src/pages/sponsors.astro index a55a42e..5d90dd7 100644 --- a/src/pages/sponsors.astro +++ b/src/pages/sponsors.astro @@ -1,165 +1,3 @@ --- -import BaseLayout from '../layouts/BaseLayout.astro'; -import BaseButton from '../components/BaseButton.astro'; -import PageHeader from '../components/PageHeader.astro'; -import { withBase } from '../lib/urls'; - -const tiers = [ - { - name: 'Platinum', - min: 'Rp 50.000.000+/tahun', - benefits: [ - 'Logo di homepage & footer website', - 'Sebutan di semua press release', - 'Booth di event komunitas', - 'Akses prioritas ke talent pool', - 'Co-branding di materi marketing' - ], - highlight: true - }, - { - name: 'Gold', - min: 'Rp 20.000.000/tahun', - benefits: [ - 'Logo di halaman sponsors', - 'Sebutan di newsletter bulanan', - 'Tiket VIP event komunitas', - 'Akses ke laporan tahunan' - ], - highlight: false - }, - { - name: 'Silver', - min: 'Rp 5.000.000/tahun', - benefits: [ - 'Logo di halaman sponsors', - 'Sebutan di media sosial', - 'Tiket event komunitas' - ], - highlight: false - }, - { - name: 'Supporter', - min: 'Rp 500.000/tahun', - benefits: [ - 'Nama di halaman sponsors', - 'Stiker eksklusif', - 'Badge "Supporter" di GitHub' - ], - highlight: false - } -]; - -const currentSponsors = [ - { name: 'Tokengratis.id', tier: 'Platinum', since: '2025', logo: '/brand/tokengratis.svg' }, - { name: 'IDCloudHost', tier: 'Gold', since: '2025', logo: '/brand/idcloudhost.svg' }, - { name: 'Komunitas OSS Indonesia', tier: 'Silver', since: '2025', logo: '/brand/komunitas.svg' } -]; - -const impact = [ - { label: 'Proyek terkurasi', value: '69+' }, - { label: 'Total bintang GitHub', value: '5.361' }, - { label: 'Kontributor aktif', value: '100+' }, - { label: 'Artikel & tutorial', value: '50+' } -]; +return Astro.redirect(`${import.meta.env.BASE_URL.replace(/\/?$/, '/')}donasi/`, 301); --- - - - -

IndopenSource dijalankan oleh komunitas sukarela. Sponsorship membantu kami merawat infrastruktur, mengadakan event, dan mendukung proyek-proyek potensial.

-
- -
-
- Dampak bersama -

Setiap kontribusi membantu menjaga open source Indonesia tetap hidup dan berkembang.

-
-
- -
-
-
- Dampak 2025 -

Apa yang sudah kita capai.

-
-
- {impact.map((item) => ( -
-

{item.value}

-

{item.label}

-
- ))} -
-
-
- -
-
-
- Pendukung kami -

Organisasi yang mendukung.

-

Terima kasih kepada organisasi yang telah mendukung misi IndopenSource.

-
-
- {currentSponsors.map((s) => ( -
-
-
- {s.logo ? ( - {s.name - ) : ( - - )} -
-
-

{s.name}

-

{s.tier} · Sejak {s.since}

-
-
-
- ))} -
-
-
- -
-
-
- Paket sponsorship -

Pilih tingkat dukungan Anda.

-

Semua paket memberikan akses ke laporan dampak dan apresiasi publik.

-
-
- {tiers.map((tier) => ( -
- {tier.highlight && Populer} -

{tier.name}

-

{tier.min}

-
    - {tier.benefits.map((b) => ( -
  • - - {b} -
  • - ))} -
-
- ))} -
-
-
- -
-
-
- Tertarik bermitra? -

Mari diskusikan kemitraan Anda.

-

Kami terbuka untuk paket kustom, sponsorship event, atau bentuk kolaborasi lain yang sejalan dengan misi open source.

-
- Hubungi Tim Sponsor - Media Kit -
-
-
-
-
diff --git a/src/pages/tentang.astro b/src/pages/tentang.astro index 031045b..3fa8346 100644 --- a/src/pages/tentang.astro +++ b/src/pages/tentang.astro @@ -17,5 +17,11 @@ const focus = [
Arah kerja

Open source menjadi lebih kuat ketika jejak kerjanya mudah ditemukan dan diteruskan.

-
01 / peta proyek02 / pengetahuan terbuka03 / kerja komunitas
{focus.map(([title, body], index) =>
0{index + 1}

{title}

{body}

)}
Masuk ke komunitas
+
+
+
01 / peta proyek02 / pengetahuan terbuka03 / kerja komunitas
+
{focus.map(([title, body], index) =>
0{index + 1}

{title}

{body}

)}
+
Lihat direktori komunitasLihat roadmapHubungi kami
+
+
diff --git a/src/pages/users.astro b/src/pages/users.astro new file mode 100644 index 0000000..bccb583 --- /dev/null +++ b/src/pages/users.astro @@ -0,0 +1,92 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro'; +import PageHeader from '../components/PageHeader.astro'; +import { rankProjectOwners, type Project } from '../lib/projects'; +import projectsData from '../data/projects.json'; + +const projects = (projectsData as Project[]).filter((project) => !project.syncFailed && project.owner); +const users = rankProjectOwners(projects); +const topUsers = users.slice(0, 3); +const totalStars = users.reduce((sum, user) => sum + user.stars, 0); +const formatNumber = (value: number) => value.toLocaleString('id-ID'); +--- + + + +

+ Peringkat pemilik dan organisasi berdasarkan akumulasi repo yang tercatat di direktori IndopenSource. + Angka ini bukan peringkat seluruh aktivitas GitHub. +

+
+
+ User & organisasi + {formatNumber(users.length)} +
+
+ Repo terhitung + {formatNumber(projects.length)} +
+
+ Akumulasi stars + {formatNumber(totalStars)} +
+
+
+ +
+
+
+ Podium +

Tiga teratas.

+
+ + +
+ + + + + + + + + + + + {users.map((user, index) => ( + + + + + + + + ))} + +
#User / organisasiRepoStarsForks
{index + 1} + + + {user.owner} + + {formatNumber(user.repositories)}{formatNumber(user.stars)}{formatNumber(user.forks)}
+
+

Dihitung dari snapshot data direktori terbaru · repo yang gagal disinkronkan tidak disertakan.

+
+
+
diff --git a/test/lib.test.mjs b/test/lib.test.mjs index d570eba..e24024a 100644 --- a/test/lib.test.mjs +++ b/test/lib.test.mjs @@ -12,7 +12,7 @@ import { strict as assert } from 'node:assert'; import { describe, it } from 'node:test'; -import { projectSlug } from '../src/lib/projects.ts'; +import { projectSlug, rankProjectOwners } from '../src/lib/projects.ts'; import { normalizeHref, safeHref } from '../src/lib/urls.ts'; import { isPublished, renderArticle, forbiddenTags } from '../src/lib/content.ts'; @@ -50,6 +50,22 @@ describe('projectSlug (slug)', () => { }); }); +describe('rankProjectOwners (project ranking)', () => { + it('groups case-insensitive owners and ignores failed sync records', () => { + const users = rankProjectOwners([ + { owner: 'Example', stars: 2, forks: 1, fullName: 'Example/a', description: '', url: '' }, + { owner: 'example', stars: 3, forks: 2, fullName: 'example/b', description: '', url: '' }, + { owner: 'Other', stars: 99, forks: 99, fullName: 'Other/c', description: '', url: '', syncFailed: true } + ]); + + assert.equal(users.length, 1); + assert.deepEqual( + { owner: users[0].owner, repositories: users[0].repositories, stars: users[0].stars, forks: users[0].forks }, + { owner: 'Example', repositories: 2, stars: 5, forks: 3 } + ); + }); +}); + describe('normalizeHref (urls)', () => { it('leaves absolute http(s) URLs untouched', () => { assert.equal(normalizeHref('https://example.com/x'), 'https://example.com/x');