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 = `