diff --git a/api/search.php b/api/search.php new file mode 100644 index 0000000..281cb99 --- /dev/null +++ b/api/search.php @@ -0,0 +1,109 @@ + 'Not authenticated']); + exit; +} + +$query = $_GET['q'] ?? ''; +$current_user_id = $_SESSION['user_id']; +$current_user_role = $_SESSION['role'] ?? null; + +if (strlen($query) < 2) { + echo json_encode(['results' => []]); + exit; +} + +try { + // Build query to get all visible bookmarks for the user + $sql = " + SELECT + b.id, + b.title, + b.url, + b.icon, + b.is_favorite, + c.name as category_name, + t.name as tab_name + FROM bookmarks b + JOIN categories c ON b.category_id = c.id + JOIN tabs t ON c.tab_id = t.id + WHERE ( + b.title LIKE :query + OR b.url LIKE :query + OR c.name LIKE :query + ) + "; + + $params = [':query' => '%' . $query . '%']; + + // Add visibility restrictions based on user role + if ($current_user_role === 'admin') { + // Admin sees all bookmarks except those explicitly hidden + $sql .= " AND NOT EXISTS ( + SELECT 1 FROM user_tab_visibility utv + WHERE utv.tab_id = t.id AND utv.user_id = :user_id + )"; + } else { + // Regular users see public tabs, their own tabs, or tabs they have access to + $sql .= " AND ( + t.user_id IS NULL + OR t.user_id = :user_id + OR EXISTS ( + SELECT 1 FROM tab_viewer_access tva + WHERE tva.tab_id = t.id AND tva.user_id = :user_id + ) + )"; + $sql .= " AND NOT EXISTS ( + SELECT 1 FROM user_tab_visibility utv + WHERE utv.tab_id = t.id AND utv.user_id = :user_id + )"; + } + + $params[':user_id'] = $current_user_id; + $sql .= " ORDER BY b.is_favorite DESC, c.name ASC, b.title ASC LIMIT 20"; + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + $results = $stmt->fetchAll(); + + // Format results + $formatted_results = []; + foreach ($results as $bookmark) { + // Build icon source URL + $icon_src = 'include/images/default-icon.png'; + if (!empty($bookmark['icon'])) { + if (file_exists($bookmark['icon']) && strpos($bookmark['icon'], 'data/images/custom/') === 0) { + $icon_src = $bookmark['icon']; + } elseif (filter_var($bookmark['icon'], FILTER_VALIDATE_URL)) { + $icon_src = $bookmark['icon']; + } elseif (!empty($settings['favicon_extractor_url'])) { + $base_url = rtrim($settings['favicon_extractor_url'], '/'); + $domain = $bookmark['icon']; + $icon_src = "{$base_url}/favicon/{$domain}?larger=true"; + } + } + + $formatted_results[] = [ + 'id' => $bookmark['id'], + 'title' => $bookmark['title'], + 'url' => $bookmark['url'], + 'icon' => $icon_src, + 'category' => $bookmark['category_name'], + 'tab' => $bookmark['tab_name'], + 'is_favorite' => (bool)$bookmark['is_favorite'] + ]; + } + + echo json_encode(['results' => $formatted_results]); + +} catch (Exception $e) { + echo json_encode(['error' => 'Database error: ' . $e->getMessage()]); +} +?> diff --git a/include/js/script.js b/include/js/script.js index 235393d..9a4221f 100644 --- a/include/js/script.js +++ b/include/js/script.js @@ -417,7 +417,7 @@ function initSearch() { searchTimeout = setTimeout(() => { performSearch(query); - }, 300); + }, 200); }); // Search button click @@ -492,13 +492,27 @@ function initSearch() { } function performSearch(query) { - const results = allBookmarks.filter(bookmark => - bookmark.title.toLowerCase().includes(query.toLowerCase()) || - bookmark.url.toLowerCase().includes(query.toLowerCase()) || - bookmark.category.toLowerCase().includes(query.toLowerCase()) - ); - - displaySearchResults(results, query); + // Use API to search all bookmarks + fetch(`api/search.php?q=${encodeURIComponent(query)}`) + .then(response => response.json()) + .then(data => { + if (data.error) { + console.error('Search error:', data.error); + displaySearchResults([], query); + return; + } + displaySearchResults(data.results || [], query); + }) + .catch(error => { + console.error('Search failed:', error); + // Fallback to local search + const results = allBookmarks.filter(bookmark => + bookmark.title.toLowerCase().includes(query.toLowerCase()) || + bookmark.url.toLowerCase().includes(query.toLowerCase()) || + bookmark.category.toLowerCase().includes(query.toLowerCase()) + ); + displaySearchResults(results, query); + }); } function displaySearchResults(results, query) { @@ -534,13 +548,17 @@ function initSearch() { hideSearchResults(); }; + const categoryText = bookmark.category ? bookmark.category : ''; + const tabText = bookmark.tab ? ` (${bookmark.tab})` : ''; + const favoriteIcon = bookmark.is_favorite ? ' ' : ''; + resultItem.innerHTML = `
-
${bookmark.title}
+
${bookmark.title}${favoriteIcon}
${bookmark.url}
- ${bookmark.category ? `${bookmark.category}` : ''} + ${categoryText ? `${categoryText}${tabText}` : ''} `; searchResults.appendChild(resultItem);