5
Beranda dan Pencarian Klien
Beranda freeStuffDev adalah satu halaman statis yang dibangun dari Content Collection tools, lalu dilengkapi pencarian yang berjalan sepenuhnya di browser. Tidak ada request pencarian ke server: penyaringan kartu dilakukan dengan menambah atau melepas kelas hidden pada elemen yang sudah tercetak di DOM.
Alur render beranda
Halaman mulai dengan getCollection('tools', ...) yang menyaring entri memakai predikat data.isPaid !== true. Karena predikatnya !== true dan bukan === false, entri yang tidak menuliskan field isPaid sama sekali tetap ikut tampil. src/pages/index.astro:8-141
Sebelum dirender, larik diacak dengan tools.sort(() => Math.random() - 0.5). Pengacakan itu berjalan di sisi server saat halaman dibangun; pada keluaran build statis urutan hasil acak ikut tercetak ke HTML, sehingga memuat ulang beranda tidak mengubah urutan kartu. src/pages/index.astro:8-141
graph LR
A["getCollection('tools')"] --> B["filter data.isPaid !== true"]
B --> C["tools.sort(Math.random() - 0.5)"]
C --> D["tools.map(Card.astro)"]
D --> E["document.querySelectorAll('.cards')"]
E --> F["liveSearch pada keyup"]
style B fill:#2d7d46,color:#fff
style C fill:#8b5cf6,color:#fff
style F fill:#f59e0b,color:#000
%% fig: Jalur data dari Content Collection ke pencarian klien
%% desc: Collection disaring isPaid, urutannya diacak saat build, dirender jadi kartu .cards, lalu disaring lagi di browser oleh liveSearch.Kartu dirender di dalam satu div dengan tools.map((tool) => <Card tool={tool} />), tanpa prop tambahan. src/pages/index.astro:69-752 Card.astro menaruh kelas cards pada elemen akar, menerima isHidden dengan nilai bawaan false, dan menyisipkan input type="hidden" name="tool-slug". src/components/Card.astro:33 src/components/Card.astro:14-164
Di atas daftar itu halaman memasang Header berisi judul dan deskripsi yang sama dengan meta halaman, lalu <Sponsors />. src/pages/index.astro:21-335 Sponsors.astro pada commit ini hanya menampilkan <details> berisi ajakan menjadi sponsor; grid banner sponsor sudah ditulis tetapi dinonaktifkan sebagai komentar. src/components/Sponsors.astro:1-136 src/components/Sponsors.astro:19-337
Filter isPaid dan badge yang tidak terpakai
Penyaringan isPaid terjadi di index.astro, jadi kartu tool berbayar tidak pernah mencapai browser. Card.astro sendiri masih memuat cabang yang mencetak badge Paid Tool ketika tool.data.isPaid bernilai benar, tetapi cabang itu tidak pernah aktif di beranda. src/components/Card.astro:18-258 Badge tersebut baru terlihat di halaman yang merender seluruh koleksi, yaitu halaman favorit yang memakai isHidden untuk menyembunyikan kartu; lihat Fitur Favorit di Local Storage.
Kotak pencarian dan kontrol Sort by
Markup kontrol terdiri dari input#searchbox dengan placeholder search anything here... dan select#sort berisi opsi random serta newest. src/pages/index.astro:36-429 src/pages/index.astro:46-5210
| Kontrol | Nilai | Perilaku | Rujukan |
|---|---|---|---|
input#searchbox | teks apa pun | memicu keyup, lalu liveSearch setelah jeda | src/pages/index.astro:86-8911 |
select#sort | random | location.href = '/' | src/pages/index.astro:54-6512 |
select#sort | newest | location.href = '/recent' | src/pages/index.astro:54-6512 |
Handler change pada #sort hanya memindahkan halaman; ia tidak menyusun ulang kartu di tempat. Opsi newest mengarah ke /recent, dan urutan di halaman itu dijelaskan di Halaman Recent dan Pengurutan. src/pages/index.astro:54-6512
liveSearch membaca textContent
initSearch didefinisikan pada skrip inline di akhir halaman. Komentar di kode menandai penempatan skrip setelah kontainer kartu, dan daftar kartu memang baru diambil saat initSearch dipanggil, bukan saat skrip diurai. src/pages/index.astro:77-7913
| Bagian skrip | Nilai | Rujukan |
|---|---|---|
| koleksi kartu | document.querySelectorAll('.cards') | src/pages/index.astro:8014 |
| jeda debounce | typingTimer, typeInterval = 500 | src/pages/index.astro:82-8415 |
| event pemicu | keyup pada #searchbox | src/pages/index.astro:86-8911 |
| perbandingan | cards[i].textContent.toLowerCase().includes(searchQ.toLowerCase()) | src/pages/index.astro:91-9516 |
| kelas toggle | remove("hidden") dan add("hidden") | src/pages/index.astro:96-9817 |
| titik mount | DOMContentLoaded, astro:after-swap | src/pages/index.astro:104-10518 |
Setiap keyup membatalkan timeout sebelumnya lewat clearTimeout(typingTimer) dan memasang setTimeout(liveSearch, 500), jadi penyaringan berjalan setengah detik setelah ketikan terakhir. src/pages/index.astro:82-8415 Karena yang dibandingkan adalah textContent seluruh kartu, semua teks yang tercetak di dalam kartu ikut menjadi bahan pencarian.
| Bagian kartu | Sumber di Card.astro | Masuk textContent |
|---|---|---|
| judul, snippet, tag | tool.data.title, tool.data.snippet, tool.data.tags.join(', ') | ya |
| isi penawaran | tool.body di dalam Free Offer: | ya |
| label tombol | Save, Edit, Visit, Alternatives | ya |
| slug | atribut value pada input name="tool-slug" | tidak, value bukan textContent |
⚠️ Label tombol dan teks
Free Offer:berada di dalam kartu, sehingga query sepertisave,visit, ataufreecocok untuk semua kartu sekaligus. Query kosong juga tidak menyembunyikan apa pun:includes('')selalutrue, jadi saat kotak dikosongkan seluruh kartu kembali tampil. src/pages/index.astro:91-9516 Selain itusearchInputdiambil tanpa pemeriksaannulllalu langsung dipanggiladdEventListener, sehingga menghapus#searchboxdari markup akan membuatinitSearchgagal. src/pages/index.astro:82-8415 src/pages/index.astro:86-8911
Perpindahan kartu antara tampil dan hidden
Satu-satunya status visual kartu adalah kehadiran kelas hidden, dan dua tempat di repository mengubahnya.
stateDiagram-v2
[*] --> Visible
Visible --> Hidden: "textContent tidak memuat searchQ"
Hidden --> Visible: "textContent memuat searchQ"
Visible --> Hidden: "unsave menambah hidden pada .cards"
Hidden --> [*]
style Visible fill:#2d7d46,color:#fff
style Hidden fill:#dc2626,color:#fff%% fig: Siklus hidup kartu beranda %% desc: liveSearch memindahkan kartu antara Visible dan Hidden berdasarkan textContent, sedangkan unsave memakai kelas yang sama di halaman favorit.
Transisi pertama dan kedua berasal dari liveSearch: kartu yang tidak cocok mendapat kelas hidden, kartu yang cocok melepasnya. src/pages/index.astro:96-9817 Transisi ketiga berasal dari unsave di layout, yang menambahkan hidden pada .cards terdekat di halaman favorit. src/layouts/Layout.astro:86-8722
initSearch dipasang pada DOMContentLoaded dan astro:after-swap karena layout memakai ViewTransitions; tanpa pemasangan ulang itu, pencarian berhenti bekerja setelah navigasi internal. src/pages/index.astro:104-10518 Detail mekanisme layout dan transisi halaman ada di Layout, Sidebar, dan Tema.