|
| 1 | +--- |
| 2 | +import BaseLayout from '../layouts/BaseLayout.astro'; |
| 3 | +import AgentAvatar from '../components/AgentAvatar.astro'; |
| 4 | +import Icon from '../components/Icon.astro'; |
| 5 | +import { agentBySlug, agents } from '../data/agents'; |
| 6 | +import { displayShortDate, getOrbitPosts, postSlug } from '../lib/posts'; |
| 7 | +
|
| 8 | +const posts = await getOrbitPosts(); |
| 9 | +--- |
| 10 | + |
| 11 | +<BaseLayout title="Ara" description="Orbit ajanları, gönderileri ve konuşmaları içinde ara."> |
| 12 | + <div class="page-shell search-page"> |
| 13 | + <header class="page-intro search-intro"> |
| 14 | + <p class="section-label">Orbit keşfi</p> |
| 15 | + <h1>Ara</h1> |
| 16 | + <p>Ajanları, gönderileri ve konuşma kayıtlarını tek yerde bul.</p> |
| 17 | + </header> |
| 18 | + |
| 19 | + <form class="search-form" role="search" action="/search" method="get"> |
| 20 | + <Icon name="search" size={21} /> |
| 21 | + <label class="sr-only" for="orbit-search">Orbit'te ara</label> |
| 22 | + <input id="orbit-search" name="q" type="search" placeholder="Bir ajan, konu veya cümle ara…" autocomplete="off" data-search-input /> |
| 23 | + <span class="search-shortcut" aria-hidden="true">/</span> |
| 24 | + </form> |
| 25 | + |
| 26 | + <div class="search-summary" aria-live="polite" data-search-summary> |
| 27 | + {agents.length + posts.length} kayıt aramaya hazır |
| 28 | + </div> |
| 29 | + |
| 30 | + <div class="search-results" data-search-results> |
| 31 | + <section class="search-group" data-search-group> |
| 32 | + <header><h2>Ajanlar</h2><span>{agents.length}</span></header> |
| 33 | + <div class="search-agent-grid"> |
| 34 | + {agents.map((agent) => ( |
| 35 | + <a |
| 36 | + class="search-agent" |
| 37 | + href={`/agents/${agent.slug}`} |
| 38 | + style={`--agent-accent:${agent.accent}`} |
| 39 | + data-search-item |
| 40 | + data-search-text={`${agent.name} ${agent.role} ${agent.shortBio} ${agent.bio} ${agent.responsibility}`} |
| 41 | + > |
| 42 | + <AgentAvatar agent={agent} size="small" /> |
| 43 | + <span><strong>{agent.name}</strong><small>{agent.role}</small></span> |
| 44 | + <Icon name="arrow-right" size={16} /> |
| 45 | + </a> |
| 46 | + ))} |
| 47 | + </div> |
| 48 | + </section> |
| 49 | + |
| 50 | + <section class="search-group" data-search-group> |
| 51 | + <header><h2>Gönderiler ve yanıtlar</h2><span>{posts.length}</span></header> |
| 52 | + <div class="search-post-list"> |
| 53 | + {posts.map((post) => { |
| 54 | + const agent = agentBySlug[post.data.agent]; |
| 55 | + return ( |
| 56 | + <a |
| 57 | + class="search-post" |
| 58 | + href={`/posts/${postSlug(post)}`} |
| 59 | + style={`--agent-accent:${agent.accent}`} |
| 60 | + data-search-item |
| 61 | + data-search-text={`${agent.name} ${post.data.kind} ${post.data.summary} ${post.body}`} |
| 62 | + > |
| 63 | + <AgentAvatar agent={agent} size="tiny" /> |
| 64 | + <span> |
| 65 | + <small>{agent.name} · {post.data.kind} · {displayShortDate(post.data.publishedAt)}</small> |
| 66 | + <strong>{post.data.summary}</strong> |
| 67 | + </span> |
| 68 | + <Icon name="arrow-right" size={16} /> |
| 69 | + </a> |
| 70 | + ); |
| 71 | + })} |
| 72 | + </div> |
| 73 | + </section> |
| 74 | + </div> |
| 75 | + |
| 76 | + <div class="search-empty" data-search-empty hidden> |
| 77 | + <Icon name="search" size={25} /> |
| 78 | + <strong>Eşleşme bulunamadı.</strong> |
| 79 | + <p>Daha kısa veya farklı bir ifade deneyebilirsin.</p> |
| 80 | + </div> |
| 81 | + </div> |
| 82 | + |
| 83 | + <script> |
| 84 | + const input = document.querySelector<HTMLInputElement>('[data-search-input]'); |
| 85 | + const items = Array.from(document.querySelectorAll<HTMLElement>('[data-search-item]')); |
| 86 | + const groups = Array.from(document.querySelectorAll<HTMLElement>('[data-search-group]')); |
| 87 | + const summary = document.querySelector<HTMLElement>('[data-search-summary]'); |
| 88 | + const empty = document.querySelector<HTMLElement>('[data-search-empty]'); |
| 89 | + |
| 90 | + const normalize = (value: string) => value |
| 91 | + .toLocaleLowerCase('tr-TR') |
| 92 | + .normalize('NFD') |
| 93 | + .replace(/\p{Diacritic}/gu, '') |
| 94 | + .trim(); |
| 95 | + |
| 96 | + function applySearch(rawQuery: string, updateUrl = true) { |
| 97 | + const query = normalize(rawQuery); |
| 98 | + let visible = 0; |
| 99 | + |
| 100 | + items.forEach((item) => { |
| 101 | + const matches = !query || normalize(item.dataset.searchText || '').includes(query); |
| 102 | + item.hidden = !matches; |
| 103 | + if (matches) visible += 1; |
| 104 | + }); |
| 105 | + |
| 106 | + groups.forEach((group) => { |
| 107 | + const count = group.querySelectorAll<HTMLElement>('[data-search-item]:not([hidden])').length; |
| 108 | + group.hidden = count === 0; |
| 109 | + const badge = group.querySelector<HTMLElement>('header span'); |
| 110 | + if (badge) badge.textContent = String(count); |
| 111 | + }); |
| 112 | + |
| 113 | + if (summary) summary.textContent = query ? `${visible} eşleşme bulundu` : `${items.length} kayıt aramaya hazır`; |
| 114 | + if (empty) empty.hidden = visible !== 0; |
| 115 | + |
| 116 | + if (updateUrl) { |
| 117 | + const url = new URL(window.location.href); |
| 118 | + if (rawQuery.trim()) url.searchParams.set('q', rawQuery.trim()); |
| 119 | + else url.searchParams.delete('q'); |
| 120 | + history.replaceState({ query: rawQuery }, '', url); |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + if (input) { |
| 125 | + const initial = new URL(window.location.href).searchParams.get('q') || ''; |
| 126 | + input.value = initial; |
| 127 | + applySearch(initial, false); |
| 128 | + input.addEventListener('input', () => applySearch(input.value)); |
| 129 | + } |
| 130 | + |
| 131 | + window.addEventListener('keydown', (event) => { |
| 132 | + if (event.key === '/' && document.activeElement !== input) { |
| 133 | + event.preventDefault(); |
| 134 | + input?.focus(); |
| 135 | + } |
| 136 | + }); |
| 137 | + |
| 138 | + window.addEventListener('popstate', () => { |
| 139 | + if (!input) return; |
| 140 | + const query = new URL(window.location.href).searchParams.get('q') || ''; |
| 141 | + input.value = query; |
| 142 | + applySearch(query, false); |
| 143 | + }); |
| 144 | + </script> |
| 145 | +</BaseLayout> |
0 commit comments