From 276b6cb81bd7c7690a9b5c8bc11a0ffc6166eabb Mon Sep 17 00:00:00 2001 From: Cory Dransfeldt Date: Tue, 10 Jun 2025 10:31:50 -0700 Subject: [PATCH] feat(search): design consistency with other feed/content aggregation lists --- api/search.php | 40 +++---- package-lock.json | 10 +- package.json | 2 +- queries/functions/search.sql | 16 +-- queries/views/feeds/search.sql | 57 +++++----- src/assets/styles/base/vars.css | 3 - src/assets/styles/components/forms.css | 4 + src/includes/blocks/tags.liquid | 8 +- src/pages/static/search.html | 141 +++++++++++++------------ 9 files changed, 152 insertions(+), 129 deletions(-) diff --git a/api/search.php b/api/search.php index bd8d1df..b3d1e8d 100644 --- a/api/search.php +++ b/api/search.php @@ -18,13 +18,13 @@ class SearchHandler extends BaseHandler { try { $query = $this->validateAndSanitizeQuery($_GET["q"] ?? null); - $types = $this->validateAndSanitizeTypes($_GET["type"] ?? ""); + $sections = $this->validateAndSanitizeSections($_GET["section"] ?? ""); $page = isset($_GET["page"]) ? intval($_GET["page"]) : 1; $pageSize = isset($_GET["pageSize"]) ? intval($_GET["pageSize"]) : 10; $offset = ($page - 1) * $pageSize; - $cacheKey = $this->generateCacheKey($query, $types, $page, $pageSize); + $cacheKey = $this->generateCacheKey($query, $sections, $page, $pageSize); $results = []; - $results = $this->getCachedResults($cacheKey) ?? $this->fetchSearchResults($query, $types, $pageSize, $offset); + $results = $this->getCachedResults($cacheKey) ?? $this->fetchSearchResults($query, $sections, $pageSize, $offset); if (empty($results) || empty($results["data"])) { $this->sendResponse(["results" => [], "total" => 0, "page" => $page, "pageSize" => $pageSize], 200); @@ -61,38 +61,38 @@ class SearchHandler extends BaseHandler return $query; } - private function validateAndSanitizeTypes(string $rawTypes): ?array + private function validateAndSanitizeSections(string $rawSections): ?array { - $allowedTypes = ["post", "artist", "genre", "book", "movie", "show"]; + $allowedSections = ["post", "artist", "genre", "book", "movie", "show"]; - if (empty($rawTypes)) return null; + if (empty($rawSections)) return null; - $types = array_map( - fn($type) => strtolower( - trim(htmlspecialchars($type, ENT_QUOTES, "UTF-8")) + $sections = array_map( + fn($section) => strtolower( + trim(htmlspecialchars($section, ENT_QUOTES, "UTF-8")) ), - explode(",", $rawTypes) + explode(",", $rawSections) ); - $invalidTypes = array_diff($types, $allowedTypes); + $invalidSections = array_diff($sections, $allowedSections); - if (!empty($invalidTypes)) throw new Exception("Invalid 'type' parameter. Unsupported types: " . implode(", ", $invalidTypes)); + if (!empty($invalidSections)) throw new Exception("Invalid 'section' parameter. Unsupported sections: " . implode(", ", $invalidSections)); - return $types; + return $sections; } private function fetchSearchResults( string $query, - ?array $types, + ?array $sections, int $pageSize, int $offset ): array { - $typesParam = $types && count($types) > 0 ? "%7B" . implode(",", $types) . "%7D" : ""; + $sectionsParam = $sections && count($sections) > 0 ? "%7B" . implode(",", $sections) . "%7D" : ""; $endpoint = "rpc/search_optimized_index"; $queryString = "search_query=" . urlencode($query) . "&page_size={$pageSize}&page_offset={$offset}" . - ($typesParam ? "&types={$typesParam}" : ""); + ($sectionsParam ? "§ions={$sectionsParam}" : ""); $data = $this->makeRequest("GET", "{$endpoint}?{$queryString}"); $total = count($data) > 0 ? $data[0]["total_count"] : 0; $results = array_map(function ($item) { @@ -105,16 +105,16 @@ class SearchHandler extends BaseHandler private function generateCacheKey( string $query, - ?array $types, + ?array $sections, int $page, int $pageSize ): string { - $typesKey = $types ? implode(",", $types) : "all"; + $sectionsKey = $sections ? implode(",", $sections) : "all"; return sprintf( - "search:%s:types:%s:page:%d:pageSize:%d", + "search:%s:sections:%s:page:%d:pageSize:%d", md5($query), - $typesKey, + $sectionsKey, $page, $pageSize ); diff --git a/package-lock.json b/package-lock.json index 590fdcf..4d73f2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "coryd.dev", - "version": "9.1.8", + "version": "9.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "coryd.dev", - "version": "9.1.8", + "version": "9.2.2", "license": "MIT", "dependencies": { "minisearch": "^7.1.2", @@ -1577,9 +1577,9 @@ } }, "node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { diff --git a/package.json b/package.json index 6f8a4e8..3469717 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coryd.dev", - "version": "9.1.8", + "version": "9.2.2", "description": "The source for my personal site. Built using 11ty (and other tools).", "type": "module", "engines": { diff --git a/queries/functions/search.sql b/queries/functions/search.sql index 4192803..0e07833 100644 --- a/queries/functions/search.sql +++ b/queries/functions/search.sql @@ -1,12 +1,15 @@ -CREATE OR REPLACE FUNCTION search_optimized_index(search_query text, page_size integer, page_offset integer, types text[]) +DROP FUNCTION IF EXISTS search_optimized_index(text, integer, integer, text[]); + +CREATE FUNCTION search_optimized_index(search_query text, page_size integer, page_offset integer, sections text[]) RETURNS TABLE( result_id integer, url text, title text, description text, - tags text, + tags text[], genre_name text, genre_url text, + section text, type text, total_plays text, rank real, @@ -20,20 +23,21 @@ BEGIN s.url, s.title, s.description, - array_to_string(s.tags, ', ') AS tags, + s.tags, s.genre_name, s.genre_url, + s.section, s.type, s.total_plays, ts_rank_cd(to_tsvector('english', s.title || ' ' || s.description || array_to_string(s.tags, ' ')), plainto_tsquery('english', search_query)) AS rank, COUNT(*) OVER() AS total_count FROM optimized_search_index s - WHERE(types IS NULL - OR s.type = ANY(types)) + WHERE(sections IS NULL + OR s.section = ANY(sections)) AND plainto_tsquery('english', search_query) @@ to_tsvector('english', s.title || ' ' || s.description || array_to_string(s.tags, ' ')) ORDER BY - s.type = 'post' DESC, + s.section = 'post' DESC, s.content_date DESC NULLS LAST, rank DESC LIMIT page_size OFFSET page_offset; diff --git a/queries/views/feeds/search.sql b/queries/views/feeds/search.sql index 46f5183..c3b3072 100644 --- a/queries/views/feeds/search.sql +++ b/queries/views/feeds/search.sql @@ -1,37 +1,38 @@ CREATE OR REPLACE VIEW optimized_search_index AS WITH search_data AS ( SELECT - 'post' AS type, - CONCAT('📝 ', p.title) AS title, + p.title, p.url::TEXT AS url, p.description AS description, p.tags, NULL AS genre_name, NULL AS genre_url, NULL::TEXT AS total_plays, - p.date AS content_date + p.date AS content_date, + 'article' AS type, + 'post' AS section FROM optimized_posts p UNION ALL SELECT - 'link' AS type, - CONCAT('🔗 ', l.title, ' via ', l.name) AS title, + CONCAT(l.title, ' via ', l.name) AS title, l.link::TEXT AS url, l.description AS description, l.tags, NULL AS genre_name, NULL AS genre_url, NULL::TEXT AS total_plays, - l.date AS content_date + l.date AS content_date, + 'link' AS type, + 'link' AS section FROM optimized_links l UNION ALL SELECT - 'book' AS type, CASE WHEN b.rating IS NOT NULL THEN - CONCAT('📖 ', b.title, ' (', b.rating, ')') + CONCAT(b.title, ' (', b.rating, ')') ELSE - CONCAT('📖 ', b.title) + b.title END AS title, b.url::TEXT AS url, b.description AS description, @@ -39,58 +40,62 @@ WITH search_data AS ( NULL AS genre_name, NULL AS genre_url, NULL::TEXT AS total_plays, - b.date_finished AS content_date + b.date_finished AS content_date, + 'books' AS type, + 'book' AS section FROM optimized_books b WHERE LOWER(b.status) = 'finished' UNION ALL SELECT - 'artist' AS type, - CONCAT(COALESCE(ar.emoji, ar.genre_emoji, '🎧'), ' ', ar.name) AS title, + ar.name AS title, ar.url::TEXT AS url, ar.description AS description, ARRAY[ar.genre_name] AS tags, - ar.genre_name, + CONCAT(COALESCE(ar.emoji, ar.genre_emoji, '🎧'), ' ', ar.genre_name) AS genre_name, ar.genre_slug AS genre_url, TO_CHAR(ar.total_plays::NUMERIC, 'FM999,999,999,999') AS total_plays, - NULL AS content_date + NULL AS content_date, + 'music' AS type, + 'artist' AS section FROM optimized_artists ar UNION ALL SELECT - 'genre' AS type, - CONCAT(COALESCE(g.emoji, '🎵'), ' ', g.name) AS title, + g.name AS title, g.url::TEXT AS url, g.description AS description, NULL AS tags, g.name AS genre_name, g.url AS genre_url, - NULL::TEXT AS total_plays, - NULL AS content_date + g.total_plays AS total_plays, + NULL AS content_date, + 'music' AS type, + 'genre' AS section FROM optimized_genres g UNION ALL SELECT - 'show' AS type, - CONCAT('📺 ', s.title, ' (', s.year, ')') AS title, + CONCAT(s.title, ' (', s.year, ')') AS title, s.url::TEXT AS url, s.description AS description, s.tags, NULL AS genre_name, NULL AS genre_url, NULL::TEXT AS total_plays, - s.last_watched_at AS content_date + s.last_watched_at AS content_date, + 'tv' AS type, + 'show' AS section FROM optimized_shows s WHERE s.last_watched_at IS NOT NULL UNION ALL SELECT - 'movie' AS type, CASE - WHEN m.rating IS NOT NULL THEN CONCAT('🎬 ', m.title, ' (', m.rating, ')') - ELSE CONCAT('🎬 ', m.title, ' (', m.year, ')') + WHEN m.rating IS NOT NULL THEN CONCAT(m.title, ' (', m.rating, ')') + ELSE CONCAT(m.title, ' (', m.year, ')') END AS title, m.url::TEXT AS url, m.description AS description, @@ -98,7 +103,9 @@ WITH search_data AS ( NULL AS genre_name, NULL AS genre_url, NULL::TEXT AS total_plays, - m.last_watched AS content_date + m.last_watched AS content_date, + 'movies' AS type, + 'movie' AS section FROM optimized_movies m WHERE diff --git a/src/assets/styles/base/vars.css b/src/assets/styles/base/vars.css index 1590ed3..2800c0f 100644 --- a/src/assets/styles/base/vars.css +++ b/src/assets/styles/base/vars.css @@ -179,7 +179,4 @@ /* dialogs */ --dialog-overlay-background: light-dark(#ffffffbf, #000000bf); - - /* input accent color */ - accent-color: var(--accent-color); } diff --git a/src/assets/styles/components/forms.css b/src/assets/styles/components/forms.css index b34805d..240322a 100644 --- a/src/assets/styles/components/forms.css +++ b/src/assets/styles/components/forms.css @@ -3,6 +3,10 @@ opacity: .5; } +input { + accent-color: var(--section-color, var(--accent-color)); +} + input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]), textarea { width: var(--sizing-full); diff --git a/src/includes/blocks/tags.liquid b/src/includes/blocks/tags.liquid index 2d46c5e..e4de36c 100644 --- a/src/includes/blocks/tags.liquid +++ b/src/includes/blocks/tags.liquid @@ -1,7 +1,7 @@ {% if tags %}
- {%- for tag in tags -%} - #{{ tag | downcase }} - {%- endfor -%} -
+ {%- for tag in tags -%} + #{{ tag | downcase }} + {%- endfor -%} + {% endif %} diff --git a/src/pages/static/search.html b/src/pages/static/search.html index b486a13..b98cd47 100644 --- a/src/pages/static/search.html +++ b/src/pages/static/search.html @@ -27,27 +27,27 @@ description: Search through posts and other content on my site. Filter by type
@@ -59,25 +59,34 @@ description: Search through posts and other content on my site. value="coryd.dev" /> - +
+