1<?php
2require_once __DIR__ . '/../includes/bootstrap.php';
3
4$q = get('q');
5$type = get('type', 'topics');
6if (!in_array($type, ['topics','users','posts'])) $type = 'topics';
7
8$topicResults = [];
9$postResults = [];
10$userResults = [];
11
12if (mb_strlen($q) >= 2) {
13 $like = '%' . $q . '%';
14
15 if ($type === 'topics' || $type === 'posts') {
16
17 $topicResults = DB::rows("
18 SELECT t.*, u.username, c.name AS cat_name, c.slug AS cat_slug, c.color AS cat_color,
19 NULL AS match_post_id
20 FROM topics t
21 JOIN users u ON u.id = t.user_id
22 JOIN categories c ON c.id = t.category_id
23 WHERE t.title LIKE ?
24 ORDER BY t.last_post_at DESC LIMIT 20
25 ", [$like]);
26 }
27
28 if ($type === 'posts') {
29
30 $postResults = DB::rows("
31 SELECT p.id AS post_id, p.content, p.post_num, p.created_at AS post_date,
32 t.id AS topic_id, t.title, t.slug,
33 u.username, u.avatar,
34 c.name AS cat_name, c.slug AS cat_slug, c.color AS cat_color
35 FROM posts p
36 JOIN topics t ON t.id = p.topic_id
37 JOIN users u ON u.id = p.user_id
38 JOIN categories c ON c.id = t.category_id
39 WHERE p.content LIKE ? AND p.deleted = 0
40 ORDER BY p.created_at DESC LIMIT 30
41 ", [$like]);
42 }
43
44 if ($type === 'users') {
45 $userResults = DB::rows("
46 SELECT id, username, avatar, role, karma, post_count, bio, joined_at
47 FROM users
48 WHERE (username LIKE ? OR bio LIKE ?) AND suspended = 0
49 ORDER BY post_count DESC LIMIT 30
50 ", [$like, $like]);
51 }
52}
53
54$allCount = count($topicResults) + count($postResults) + count($userResults);
55$PAGE_TITLE = 'Search';
56include __DIR__ . '/../views/partials/layout.php';
57?>
58<div style="max-width:800px">
59
60 <!-- Search form with type tabs -->
61 <form method="GET" id="searchForm">
62 <div style="display:flex;gap:10px;margin-bottom:16px">
63 <div style="position:relative;flex:1;max-width:560px">
64 <svg style="position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--faint);pointer-events:none" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
65 <input type="text" name="q" value="<?= e($q) ?>" class="fi"
66 style="padding-left:34px" placeholder="Search topics, posts, usersβ¦"
67 autofocus autocomplete="off" id="mainSearchInput">
68 </div>
69 <button type="submit" class="btn-primary">Search</button>
70 </div>
71
72 <!-- Type tabs -->
73 <div style="display:flex;gap:0;margin-bottom:22px;border-bottom:2px solid var(--border)">
74 <?php foreach (['topics'=>'π Topics','posts'=>'π¬ Posts','users'=>'π₯ Users'] as $t => $lbl): ?>
75 <button type="submit" name="type" value="<?= $t ?>"
76 class="srch-tab <?= $type===$t?'active':'' ?>"><?= $lbl ?></button>
77 <?php endforeach; ?>
78 </div>
79 </form>
80
81 <?php if (mb_strlen($q) >= 2): ?>
82 <p class="search-info">
83 <?= count($type==='posts'?$postResults:($type==='users'?$userResults:$topicResults)) ?>
84 result<?= count($type==='posts'?$postResults:($type==='users'?$userResults:$topicResults))!==1?'s':'' ?>
85 for "<strong><?= e($q) ?></strong>"
86 </p>
87
88 <?php if ($type === 'topics'): ?>
89 <?php if (empty($topicResults)): ?>
90 <div class="empty-state"><p>No topics found matching "<?= e($q) ?>".</p></div>
91 <?php else: ?>
92 <?php foreach ($topicResults as $t): ?>
93 <div class="topic-row">
94 <div class="tr-body">
95 <a href="<?= u('forum/topic.php?slug=' . urlencode($t['slug'])) ?>" class="tr-title"><?= e($t['title']) ?></a>
96 <div class="tr-meta">
97 <a href="<?= u('forum/category.php?slug=' . urlencode($t['cat_slug'])) ?>"
98 class="cat-tag" style="--cc:<?= e($t['cat_color']) ?>"><?= e($t['cat_name']) ?></a>
99 by <a href="<?= u('users/profile.php?u=' . urlencode($t['username'])) ?>">@<?= e($t['username']) ?></a>
100 <span class="ago" data-ts="<?= e($t['created_at']) ?>"></span>
101 </div>
102 </div>
103 <div class="tr-counts"><span>π¬ <?= $t['reply_count'] ?></span><span>π <?= $t['views'] ?></span></div>
104 </div>
105 <?php endforeach; ?>
106 <?php endif; ?>
107
108 <?php elseif ($type === 'posts'): ?>
109 <?php if (empty($postResults)): ?>
110 <div class="empty-state"><p>No posts found matching "<?= e($q) ?>".</p></div>
111 <?php else: ?>
112 <?php foreach ($postResults as $p): ?>
113 <?php
114
115 $postUrl = u('forum/topic.php?slug=' . urlencode($p['slug']) . '&goto=' . $p['post_id']) . '#post-' . $p['post_id'];
116
117 $snippet = mb_substr($p['content'], 0, 200);
118 $pos = mb_stripos($p['content'], $q);
119 if ($pos !== false) {
120 $start = max(0, $pos - 60);
121 $snippet = ($start > 0 ? 'β¦' : '') . mb_substr($p['content'], $start, 200) . (mb_strlen($p['content']) > $start+200 ? 'β¦' : '');
122 }
123 ?>
124 <div class="post-search-card">
125 <div class="psc-head">
126 <div style="display:flex;align-items:center;gap:8px;flex:1;min-width:0">
127 <?php if ($p['avatar']): ?>
128 <img src="<?= e($p['avatar']) ?>" class="av-sm" alt="">
129 <?php else: ?>
130 <span class="av-sm av-init"><?= strtoupper($p['username'][0]) ?></span>
131 <?php endif; ?>
132 <div style="min-width:0">
133 <a href="<?= u('users/profile.php?u='.urlencode($p['username'])) ?>" style="font-weight:600;font-size:13px">@<?= e($p['username']) ?></a>
134 <span style="color:var(--faint);font-size:12px;margin-left:6px">
135 in <a href="<?= u('forum/topic.php?slug='.urlencode($p['slug'])) ?>" style="color:var(--muted)"><?= e(mb_substr($p['title'],0,60)) ?><?= mb_strlen($p['title'])>60?'β¦':'' ?></a>
136 </span>
137 </div>
138 </div>
139 <div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
140 <span class="ago" data-ts="<?= e($p['post_date']) ?>" style="font-size:12px;color:var(--faint)"></span>
141 <a href="<?= e($postUrl) ?>" class="btn-ghost btn-sm">View Post β</a>
142 </div>
143 </div>
144 <div class="psc-body"><?= e($snippet) ?></div>
145 </div>
146 <?php endforeach; ?>
147 <?php endif; ?>
148
149 <?php elseif ($type === 'users'): ?>
150 <?php if (empty($userResults)): ?>
151 <div class="empty-state"><p>No users found matching "<?= e($q) ?>".</p></div>
152 <?php else: ?>
153 <div class="user-search-grid">
154 <?php foreach ($userResults as $u):
155 $kt = karma_tier((int)$u['karma']); ?>
156 <a href="<?= u('users/profile.php?u='.urlencode($u['username'])) ?>" class="user-search-card">
157 <div class="usc-av">
158 <?php if ($u['avatar']): ?>
159 <img src="<?= e($u['avatar']) ?>" class="av-lg" alt="">
160 <?php else: ?>
161 <span class="av-lg av-init"><?= strtoupper($u['username'][0]) ?></span>
162 <?php endif; ?>
163 </div>
164 <div class="usc-body">
165 <div class="usc-name">@<?= e($u['username']) ?> <span class="role-tag role-<?= e($u['role']) ?>"><?= e($u['role']) ?></span></div>
166 <?php if ($u['bio']): ?><div class="usc-bio"><?= e(mb_substr($u['bio'],0,80)) ?></div><?php endif; ?>
167 <div class="usc-stats">
168 <span><?= number_format($u['post_count']) ?> posts</span>
169 <span>Β·</span>
170 <span style="color:<?= e($kt['color']) ?>"><?= $kt['icon'] ?> <?= number_format((int)$u['karma']) ?> karma</span>
171 </div>
172 </div>
173 </a>
174 <?php endforeach; ?>
175 </div>
176 <?php endif; ?>
177 <?php endif; ?>
178
179 <?php elseif ($q): ?>
180 <div class="alert warn">Please enter at least 2 characters to search.</div>
181 <?php endif; ?>
182</div>
183
184<script>
185
186document.querySelectorAll('.srch-tab').forEach(function(btn){
187 btn.addEventListener('click',function(){
188 document.getElementById('mainSearchInput').form.submit();
189 });
190});
191</script>
192<?php include __DIR__ . '/../views/partials/layout_end.php'; ?>