Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:

- name: Build site
env:
ASTRO_BASE: /indopensource.org
ASTRO_BASE: /
run: npm run build

- name: Upload artifact
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sync-content.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:

- name: Validate build
env:
ASTRO_BASE: /indopensource.org
ASTRO_BASE: /
run: npm run build

- name: Commit synced data
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ MVP awal berfokus pada homepage sebagai pintu masuk roadmap IndopenSource:
- 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 default sebelum custom domain aktif adalah
`https://indopensource.github.io/indopensource.org/`.
URL produksi setelah custom domain aktif adalah `https://indopensource.org/`.

## Deployment

Expand All @@ -69,7 +68,8 @@ GitHub Pages memakai workflow `.github/workflows/deploy-pages.yml`.
- Build command: `npm run build`
- Output directory: `dist`
- Source: GitHub Actions
- Default Pages base path: `/indopensource.org`
- Custom domain: `indopensource.org`
- Pages base path: `/`

Aktifkan Pages di repository settings dengan source `GitHub Actions`, lalu push ke
`main` atau jalankan workflow `Deploy to GitHub Pages` secara manual.
Expand Down
1 change: 1 addition & 0 deletions public/CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
indopensource.org
Binary file added public/brand/indopensource-hero.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions scripts/sync-blog-posts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ for (const path of articleFiles) {
date: data.date || '',
tags: data.tags || [],
status: data.status || 'draft',
thumbnail: data.thumbnail || data.image || data.cover || '',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize relative blog thumbnails during sync

If an article in Blog-IndopenSource sets thumbnail: ./cover.png or image: images/cover.png, this line stores that repo-relative path unchanged. The blog pages later treat every non-HTTP thumbnail as a site-local asset via withBase(), but the sync does not copy assets from the blog repo, so those images render as broken /indopensource.org/... URLs; resolve relative thumbnail paths to the raw GitHub file URL during sync or reject them explicitly.

Useful? React with 👍 / 👎.

content,
sourceUrl: file.html_url,
releasedAt: commitMeta.releasedAt,
Expand Down
15 changes: 7 additions & 8 deletions src/components/HomeHero.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import { withBase } from '../lib/urls';
---

<section
class="relative min-h-[calc(100vh-68px)] overflow-hidden bg-[image:linear-gradient(115deg,rgba(15,111,92,0.9),rgba(29,37,40,0.68)),var(--hero-image)] bg-cover bg-center py-20 text-paper max-lg:min-h-0 max-lg:py-14"
style={`--hero-image: url("${withBase('/images/hero-community.svg')}")`}
class="relative overflow-hidden bg-[image:linear-gradient(90deg,rgba(29,37,40,0.92)_0%,rgba(15,111,92,0.78)_46%,rgba(29,37,40,0.5)_100%),var(--hero-image)] bg-cover bg-left py-16 text-paper md:min-h-[min(600px,calc(100vh-68px))] md:py-18 max-md:py-12"
style={`--hero-image: url("${withBase('/brand/indopensource-hero.jpg')}")`}
>
<LightSvgMotion variant="network" />
<div class="relative mx-auto grid w-[min(1120px,calc(100%-32px))] grid-cols-[minmax(0,1fr)_240px] items-center gap-10 max-lg:grid-cols-1">
<div>
<div class="relative mx-auto w-[min(1040px,calc(100%-80px))] max-md:w-[calc(100%-32px)]">
<div class="max-w-[680px]">
<p class="mb-3.5 text-xs font-black uppercase tracking-[0.08em] text-sun">Roadmap website komunitas</p>
<h1 class="m-0 max-w-3xl text-[clamp(3rem,8vw,6.6rem)] leading-[0.96] max-sm:text-[clamp(2.7rem,15vw,4.5rem)]">
IndopenSource.org
<h1 class="m-0 max-w-[680px] text-balance text-[clamp(2.6rem,5.2vw,4.8rem)] leading-[1.03] max-sm:text-[clamp(2.3rem,9.5vw,3.2rem)]">
Komunitas Open Source Indonesia
</h1>
<p class="mt-6 max-w-3xl text-[clamp(1.05rem,2vw,1.3rem)] leading-8 text-paper/85">
<p class="mt-6 max-w-2xl text-[clamp(1.05rem,1.8vw,1.25rem)] leading-8 text-paper/85">
Rumah terbuka untuk merawat daftar proyek, mendokumentasikan falsafah,
menulis pembelajaran, dan menghubungkan kontributor open source Indonesia.
</p>
Expand All @@ -24,6 +24,5 @@ import { withBase } from '../lib/urls';
<BaseButton href="https://github.com/orgs/IndopenSource/projects" variant="secondary">Roadmap GitHub</BaseButton>
</div>
</div>
<img class="hidden w-60 justify-self-end rounded-[28px] bg-paper/95 p-5 shadow-2xl shadow-ink/20 md:block motion-float" src={withBase('/brand/indopensource-logo.png')} alt="IndopenSource" />
</div>
</section>
29 changes: 20 additions & 9 deletions src/components/ProjectCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,28 @@ interface Project {

interface Props {
project: Project;
compact?: boolean;
}

const { project } = Astro.props;
import { withBase } from '../lib/urls';
import { projectSlug } from '../lib/projects';

const { project, compact = false } = Astro.props;
const status = project.archived ? 'Archived' : project.language || 'Project';
const updatedDate = project.pushedAt || project.updatedAt;
const updatedLabel = updatedDate
? new Intl.DateTimeFormat('id-ID', { dateStyle: 'medium' }).format(new Date(updatedDate))
: '';
const visibleTopics = (project.topics || []).slice(0, 4);
const detailUrl = withBase(`/projects/${projectSlug(project.fullName)}/`);
const repoName = project.fullName.split('/').at(-1) || project.fullName;
---

<article
class="project-card rounded-lg border border-line bg-paper p-5"
class:list={[
'project-card group relative rounded-lg border border-line bg-paper p-5 transition hover:-translate-y-0.5 hover:border-brand/40 hover:shadow-lg hover:shadow-ink/5',
compact && 'h-full'
]}
data-project-card
data-name={project.fullName.toLowerCase()}
data-description={project.description.toLowerCase()}
Expand All @@ -46,6 +55,7 @@ const visibleTopics = (project.topics || []).slice(0, 4);
data-updated={updatedDate}
data-release={project.latestRelease?.publishedAt || ''}
>
<a class="absolute inset-0 z-0 rounded-lg" href={detailUrl} aria-label={`Buka detail ${project.fullName}`}></a>
<div class="flex items-start gap-3">
{
project.ownerAvatarUrl && (
Expand All @@ -57,36 +67,37 @@ const visibleTopics = (project.topics || []).slice(0, 4);
/>
)
}
<div class="min-w-0">
<div class="relative z-10 min-w-0">
<div class="flex flex-wrap gap-2">
<span class="inline-flex rounded-full bg-brand/10 px-2.5 py-1 text-xs font-extrabold text-brand-dark">{status}</span>
{
project.latestRelease ? (
<a class="inline-flex rounded-full bg-sun/25 px-2.5 py-1 text-xs font-extrabold text-ink hover:text-brand" href={project.latestRelease.url}>
<a class="relative z-20 inline-flex rounded-full bg-sun/25 px-2.5 py-1 text-xs font-extrabold text-ink hover:text-brand" href={project.latestRelease.url}>
Latest {project.latestRelease.tagName}
</a>
) : updatedLabel ? (
<span class="inline-flex rounded-full bg-canvas px-2.5 py-1 text-xs font-extrabold text-muted">Updated {updatedLabel}</span>
) : null
}
</div>
<h3 class="mt-3 text-lg font-bold">
<a class="hover:text-brand" href={project.url}>{project.fullName}</a>
{project.owner && <p class="mt-3 text-sm font-extrabold text-brand-dark">{project.owner}</p>}
<h3 class="mt-1 text-lg font-bold">
<a class="relative z-20 hover:text-brand" href={detailUrl}>{repoName}</a>
</h3>
<p class="mt-3 leading-7 text-muted">{project.description}</p>
</div>
</div>
{
visibleTopics.length > 0 && (
<div class="mt-4 flex flex-wrap gap-2 text-xs font-bold text-brand-dark">
<div class="relative z-10 mt-4 flex flex-wrap gap-2 text-xs font-bold text-brand-dark">
{visibleTopics.map((topic) => <span class="rounded-full bg-brand/10 px-2.5 py-1">#{topic}</span>)}
</div>
)
}
<div class="mt-4 flex flex-wrap gap-2 text-sm text-muted">
<div class="relative z-10 mt-4 flex flex-wrap gap-2 text-sm text-muted">
<span class="rounded-full border border-line px-2 py-1">{project.stars.toLocaleString('id-ID')} stars</span>
<span class="rounded-full border border-line px-2 py-1">{project.forks.toLocaleString('id-ID')} forks</span>
{updatedLabel && <span class="rounded-full border border-line px-2 py-1">Update {updatedLabel}</span>}
{project.homepage && <a class="rounded-full border border-line px-2 py-1 hover:text-brand" href={project.homepage}>Homepage</a>}
{project.homepage && <a class="relative z-20 rounded-full border border-line px-2 py-1 hover:text-brand" href={project.homepage}>Homepage</a>}
</div>
</article>
23 changes: 16 additions & 7 deletions src/components/ProjectsDirectory.astro
Original file line number Diff line number Diff line change
Expand Up @@ -34,39 +34,48 @@ const topics = [...new Set(projects.flatMap((project) => project.topics || []))]

<section class="pt-8 pb-18" data-projects-directory>
<div class="mx-auto w-[min(1120px,calc(100%-32px))]">
<div class="mb-5 grid gap-3 rounded-lg border border-line bg-paper p-4 md:grid-cols-[minmax(0,1fr)_180px_180px_170px]">
<label class="grid gap-1.5 text-sm font-bold text-ink">
Cari
<div class="mb-6 rounded-lg border border-line bg-paper p-4 shadow-sm shadow-ink/5">
<div class="mb-4 flex flex-wrap items-end justify-between gap-3">
<div>
<p class="text-xs font-black uppercase tracking-[0.08em] text-accent">Filter</p>
<h2 class="mt-1 text-xl font-extrabold">Temukan project</h2>
</div>
<span class="rounded-full bg-brand/10 px-3 py-1 text-sm font-bold text-brand-dark">{projects.length} repo tersedia</span>
</div>
<div class="grid gap-3 lg:grid-cols-[minmax(260px,1fr)_180px_180px_170px]">
<label class="grid gap-1.5 text-sm font-bold text-ink lg:col-span-1">
Cari repo
<input
class="min-h-11 rounded-lg border border-line bg-white px-3 font-normal text-muted outline-none focus:border-brand"
class="min-h-12 rounded-lg border border-line bg-white px-3 font-normal text-muted outline-none transition focus:border-brand focus:ring-2 focus:ring-brand/10"
type="search"
placeholder="Nama repo, owner, deskripsi..."
data-project-search
/>
</label>
<label class="grid gap-1.5 text-sm font-bold text-ink">
Bahasa
<select class="min-h-11 rounded-lg border border-line bg-white px-3 font-normal text-muted outline-none focus:border-brand" data-project-language>
<select class="min-h-12 rounded-lg border border-line bg-white px-3 font-normal text-muted outline-none transition focus:border-brand focus:ring-2 focus:ring-brand/10" data-project-language>
<option value="">Semua</option>
{languages.map((language) => <option value={language.toLowerCase()}>{language}</option>)}
</select>
</label>
<label class="grid gap-1.5 text-sm font-bold text-ink">
Tag
<select class="min-h-11 rounded-lg border border-line bg-white px-3 font-normal text-muted outline-none focus:border-brand" data-project-topic>
<select class="min-h-12 rounded-lg border border-line bg-white px-3 font-normal text-muted outline-none transition focus:border-brand focus:ring-2 focus:ring-brand/10" data-project-topic>
<option value="">Semua</option>
{topics.map((topic) => <option value={topic.toLowerCase()}>{topic}</option>)}
</select>
</label>
<label class="grid gap-1.5 text-sm font-bold text-ink">
Urutkan
<select class="min-h-11 rounded-lg border border-line bg-white px-3 font-normal text-muted outline-none focus:border-brand" data-project-sort>
<select class="min-h-12 rounded-lg border border-line bg-white px-3 font-normal text-muted outline-none transition focus:border-brand focus:ring-2 focus:ring-brand/10" data-project-sort>
<option value="stars">Stars</option>
<option value="updated">Update terbaru</option>
<option value="release">Release terbaru</option>
<option value="name">Nama</option>
</select>
</label>
</div>
</div>

<div class="mb-4 flex flex-wrap items-center justify-between gap-3 text-sm text-muted" data-project-pagination>
Expand Down
2 changes: 1 addition & 1 deletion src/components/SiteFooter.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const links = [
];
---

<footer class="border-t border-line bg-ink pt-8 pb-8 text-paper/80 max-md:pb-28">
<footer class="border-t border-line bg-ink pt-8 pb-8 text-paper/80 max-md:pb-40">
<div class="mx-auto flex w-[min(1120px,calc(100%-32px))] items-center justify-between gap-4 max-md:flex-col max-md:items-start">
<span>IndopenSource.org adalah ruang kerja terbuka untuk ekosistem open source Indonesia.</span>
<div class="flex flex-wrap gap-3.5">
Expand Down
17 changes: 10 additions & 7 deletions src/components/SiteHeader.astro
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ const navItems = [
];

const mobileNavItems = [
['Home', '/'],
['Projects', '/projects/'],
['Blog', '/blog/'],
['Forum', '/forum/']
['Home', '/', 'M3 10.5 12 3l9 7.5V21a1 1 0 0 1-1 1h-5v-6H8v6H4a1 1 0 0 1-1-1V10.5Z'],
['Projects', '/projects/', 'M4 5a2 2 0 0 1 2-2h3l2 2h7a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V5Zm4 6h8M8 15h5'],
['Blog', '/blog/', 'M6 3h9l3 3v15H6V3Zm8 0v4h4M9 11h6M9 15h6'],
['Forum', '/forum/', 'M4 5h16v10H8l-4 4V5Zm5 4h6M9 12h4']
];

const currentPath = Astro.url.pathname;
Expand Down Expand Up @@ -44,14 +44,17 @@ const currentPathWithoutBase = currentPath.replace(new RegExp(`^${baseUrl.replac
</nav>
</header>

<nav class="fixed inset-x-4 bottom-4 z-30 hidden rounded-2xl border border-line bg-paper/95 p-2 shadow-2xl shadow-ink/20 backdrop-blur-xl max-md:grid max-md:grid-cols-4" aria-label="Navigasi mobile">
<nav class="fixed inset-x-4 bottom-[calc(env(safe-area-inset-bottom)+3.5rem)] z-30 hidden rounded-2xl border border-line bg-paper/95 p-2 shadow-2xl shadow-ink/20 backdrop-blur-xl max-md:grid max-md:grid-cols-4" aria-label="Navigasi mobile">
{
mobileNavItems.map(([label, href]) => (
mobileNavItems.map(([label, href, iconPath]) => (
<a
class="rounded-xl px-2 py-2 text-center text-xs font-extrabold text-muted hover:bg-brand/10 hover:text-brand-dark aria-[current=page]:bg-brand aria-[current=page]:text-white"
class="flex min-h-14 flex-col items-center justify-center gap-1 rounded-xl px-2 py-2 text-center text-xs font-extrabold text-muted hover:bg-brand/10 hover:text-brand-dark aria-[current=page]:bg-brand aria-[current=page]:text-white"
href={withBase(href)}
aria-current={currentPathWithoutBase === href ? 'page' : undefined}
>
<svg class="size-5" viewBox="0 0 24 24" aria-hidden="true">
<path d={iconPath} fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" />
</svg>
{label}
</a>
))
Expand Down
1 change: 1 addition & 0 deletions src/data/blog-posts.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"roadmap"
],
"status": "draft",
"thumbnail": "/brand/indopensource-hero.jpg",
"content": "Blog IndopenSource disiapkan sebagai tempat mencatat kerja komunitas open source\nIndonesia secara rapi dan mudah ditinjau.\n\n## Fokus Awal\n\nFokus awal blog ini adalah tulisan yang membantu pembaca menemukan proyek,\nmemahami praktik perawatan open source, dan mengikuti perkembangan komunitas.\n\nKategori awal yang akan dikurasi:\n\n- Project spotlight.\n- Learning notes.\n- Release notes.\n- Catatan maintenance.\n- Kabar komunitas.\n\n## Cara Berkontribusi\n\nKontributor dapat menyalin template dari `templates/article.md`, membuat file di\nfolder `content/YYYY/MM/`, lalu membuka pull request.\n\nSetiap tulisan akan direview agar struktur, sumber, dan nada tulisannya sesuai\ndengan kebutuhan pembaca IndopenSource.\n\n## Langkah Berikutnya\n\nSetelah alur editorial stabil, konten dari repo ini dapat diintegrasikan ke\nwebsite `indopensource.org`.",
"sourceUrl": "https://github.com/IndopenSource/Blog-IndopenSource/blob/main/content/2026/06/memperkenalkan-blog-indopensource.md",
"releasedAt": "2026-06-13T17:37:55Z",
Expand Down
7 changes: 7 additions & 0 deletions src/lib/projects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function projectSlug(fullName: string) {
return fullName.toLowerCase().replace('/', '--').replace(/[^a-z0-9-]+/g, '-');
}

export function projectFullNameFromSlug(slug: string) {
return slug.replace('--', '/');
}
39 changes: 26 additions & 13 deletions src/pages/blog.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import InfoCard from '../components/InfoCard.astro';
import LightSvgMotion from '../components/LightSvgMotion.astro';
import PageHeader from '../components/PageHeader.astro';
import blogPosts from '../data/blog-posts.json';
import { withBase } from '../lib/urls';

const featuredPost = blogPosts[0];
const featuredThumbnail = featuredPost?.thumbnail || '/brand/indopensource-hero.jpg';
const featuredThumbnailUrl = /^https?:\/\//.test(featuredThumbnail) ? featuredThumbnail : withBase(featuredThumbnail);
---

<BaseLayout
Expand All @@ -28,20 +31,30 @@ const featuredPost = blogPosts[0];
<section class="relative overflow-hidden pt-8 pb-18">
<LightSvgMotion variant="orbit" />
<div class="mx-auto w-[min(1120px,calc(100%-32px))]">
<article class="rounded-lg border border-line bg-paper p-5">
<span class="inline-flex rounded-full bg-sun/25 px-2.5 py-1 text-xs font-extrabold text-ink">Artikel terbaru</span>
<h2 class="mt-3 text-2xl font-extrabold">{featuredPost?.title || 'Memperkenalkan Blog IndopenSource'}</h2>
<p class="mt-3 leading-7 text-muted">
{featuredPost?.description || 'Draft pertama sudah tersedia di repo sebagai contoh format artikel komunitas.'}
</p>
<div class="mt-4 flex flex-wrap gap-2 text-sm text-muted">
{featuredPost?.tags?.map((tag) => <span class="rounded-full border border-line px-2 py-1">{tag}</span>)}
{featuredPost?.status && <span class="rounded-full border border-line px-2 py-1">{featuredPost.status}</span>}
<article class="grid overflow-hidden rounded-lg border border-line bg-paper md:grid-cols-[minmax(0,1fr)_360px]">
<div class="p-5">
<span class="inline-flex rounded-full bg-sun/25 px-2.5 py-1 text-xs font-extrabold text-ink">Artikel terbaru</span>
<h2 class="mt-3 text-2xl font-extrabold">{featuredPost?.title || 'Memperkenalkan Blog IndopenSource'}</h2>
<p class="mt-3 leading-7 text-muted">
{featuredPost?.description || 'Draft pertama sudah tersedia di repo sebagai contoh format artikel komunitas.'}
</p>
<div class="mt-4 flex flex-wrap gap-2 text-sm text-muted">
{featuredPost?.tags?.map((tag) => <span class="rounded-full border border-line px-2 py-1">{tag}</span>)}
{featuredPost?.status && <span class="rounded-full border border-line px-2 py-1">{featuredPost.status}</span>}
</div>
<div class="mt-5">
<BaseButton href={`/blog/${featuredPost?.slug || 'memperkenalkan-blog-indopensource'}/`} variant="secondary">
Baca artikel
</BaseButton>
</div>
</div>
<div class="mt-5">
<BaseButton href={`/blog/${featuredPost?.slug || 'memperkenalkan-blog-indopensource'}/`} variant="secondary">
Baca artikel
</BaseButton>
<div class="min-h-64 bg-ink">
<img
class="h-full min-h-64 w-full object-cover"
src={featuredThumbnailUrl}
alt=""
loading="lazy"
/>
</div>
</article>
</div>
Expand Down
10 changes: 10 additions & 0 deletions src/pages/blog/[slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { marked } from 'marked';
import BaseLayout from '../../layouts/BaseLayout.astro';
import PageHeader from '../../components/PageHeader.astro';
import blogPosts from '../../data/blog-posts.json';
import { withBase } from '../../lib/urls';

export function getStaticPaths() {
return blogPosts.map((post) => ({
Expand All @@ -19,6 +20,8 @@ const authorDate = post.author?.committedAt
const releaseDate = post.releasedAt
? new Intl.DateTimeFormat('id-ID', { dateStyle: 'long' }).format(new Date(post.releasedAt))
: '';
const thumbnail = post.thumbnail || '/brand/indopensource-hero.jpg';
const thumbnailUrl = /^https?:\/\//.test(thumbnail) ? thumbnail : withBase(thumbnail);
---

<BaseLayout title={`${post.title} - IndopenSource`} description={post.description} type="article">
Expand All @@ -29,6 +32,13 @@ const releaseDate = post.releasedAt
{post.tags.map((tag) => <span class="rounded-full border border-line px-2 py-1">{tag}</span>)}
<span class="rounded-full border border-line px-2 py-1">{post.status}</span>
</div>
<figure class="mt-7 overflow-hidden rounded-lg border border-line bg-ink">
<img class="aspect-[16/9] w-full object-cover" src={thumbnailUrl} alt="" />
</figure>
<p class="mt-3 text-sm leading-6 text-muted">
Thumbnail artikel dapat diatur dari frontmatter Markdown dengan field
<code>thumbnail</code>, <code>image</code>, atau <code>cover</code>.
</p>
</PageHeader>

<article class="mx-auto w-[min(820px,calc(100%-32px))] pt-7 pb-12">
Expand Down
Loading
Loading