From dd8940893f503335d42bb651c281e177896d4a4c Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Tue, 13 Feb 2024 11:43:43 -0800 Subject: [PATCH] Hooked up search functionality to the search form --- lib/pinchflat/media.ex | 2 -- lib/pinchflat/utils/string_utils.ex | 17 +++++++++++ lib/pinchflat_web.ex | 2 ++ .../layouts/partials/header.html.heex | 21 +++++++------- .../media_sources/source_html/show.html.heex | 10 +++---- .../controllers/searches/search_controller.ex | 12 ++++++++ .../controllers/searches/search_html.ex | 25 ++++++++++++++++ .../searches/search_html/show.html.heex | 29 +++++++++++++++++++ lib/pinchflat_web/router.ex | 1 + test/pinchflat/media_test.exs | 2 +- test/pinchflat/utils/string_utils_test.exs | 14 +++++++++ .../controllers/search_controller_test.exs | 10 +++++++ 12 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 lib/pinchflat_web/controllers/searches/search_controller.ex create mode 100644 lib/pinchflat_web/controllers/searches/search_html.ex create mode 100644 lib/pinchflat_web/controllers/searches/search_html/show.html.heex create mode 100644 test/pinchflat_web/controllers/search_controller_test.exs diff --git a/lib/pinchflat/media.ex b/lib/pinchflat/media.ex index b19fc6c..6bd21ea 100644 --- a/lib/pinchflat/media.ex +++ b/lib/pinchflat/media.ex @@ -52,8 +52,6 @@ defmodule Pinchflat.Media do virtual field to the result set. Returns [%MediaItem{}, ...]. - - TODO: test limit """ def search(search_term, opts \\ []) do limit = Keyword.get(opts, :limit, 50) diff --git a/lib/pinchflat/utils/string_utils.ex b/lib/pinchflat/utils/string_utils.ex index e0f01e8..5f23e6f 100644 --- a/lib/pinchflat/utils/string_utils.ex +++ b/lib/pinchflat/utils/string_utils.ex @@ -24,4 +24,21 @@ defmodule Pinchflat.Utils.StringUtils do |> Base.encode16(case: :lower) |> String.slice(0..(length - 1)) end + + @doc """ + Truncates a string to the given length and adds `...` if the string is longer than the given length. + Will break on a word boundary. Nothing happens if the string is shorter than the given length. + + Returns binary() + """ + def truncate(string, length) do + if String.length(string) > length do + string + |> String.slice(0..(length - 1)) + |> String.replace(~r/\s+\S*$/, "") + |> Kernel.<>("...") + else + string + end + end end diff --git a/lib/pinchflat_web.ex b/lib/pinchflat_web.ex index 05187a1..0ec628b 100644 --- a/lib/pinchflat_web.ex +++ b/lib/pinchflat_web.ex @@ -89,6 +89,8 @@ defmodule PinchflatWeb do import PinchflatWeb.CustomComponents.TableComponents import PinchflatWeb.CustomComponents.ButtonComponents + alias Pinchflat.Utils.StringUtils + # Shortcut for generating JS commands alias Phoenix.LiveView.JS diff --git a/lib/pinchflat_web/components/layouts/partials/header.html.heex b/lib/pinchflat_web/components/layouts/partials/header.html.heex index a89a162..c08b6c1 100644 --- a/lib/pinchflat_web/components/layouts/partials/header.html.heex +++ b/lib/pinchflat_web/components/layouts/partials/header.html.heex @@ -1,5 +1,5 @@
-
+
- +
- diff --git a/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex b/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex index 97332cf..52c14f6 100644 --- a/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex +++ b/lib/pinchflat_web/controllers/media_sources/source_html/show.html.heex @@ -16,7 +16,7 @@
-
+

Relationships

@@ -35,10 +35,10 @@ <.list_items_from_map map={Map.from_struct(@source)} />

Downloaded Media

- <%= if length(@downloaded_media) > 0 do %> + <%= if match?([_|_], @downloaded_media) do %> <.table rows={@downloaded_media} table_class="text-black dark:text-white"> <:col :let={media_item} label="Title"> - <%= String.slice(media_item.title, 0..50) %>... + <%= StringUtils.truncate(media_item.title, 50) %> <:col :let={media_item} label="" class="flex place-content-evenly"> <.link @@ -54,10 +54,10 @@ <% end %>

Pending Media

- <%= if length(@pending_media) > 0 do %> + <%= if match?([_|_], @pending_media) do %> <.table rows={@pending_media} table_class="text-black dark:text-white"> <:col :let={media_item} label="Title"> - <%= String.slice(media_item.title, 0..50) %>... + <%= StringUtils.truncate(media_item.title, 50) %> <:col :let={media_item} label="" class="flex place-content-evenly"> <.link diff --git a/lib/pinchflat_web/controllers/searches/search_controller.ex b/lib/pinchflat_web/controllers/searches/search_controller.ex new file mode 100644 index 0000000..333abea --- /dev/null +++ b/lib/pinchflat_web/controllers/searches/search_controller.ex @@ -0,0 +1,12 @@ +defmodule PinchflatWeb.Searches.SearchController do + use PinchflatWeb, :controller + + alias Pinchflat.Media + + def show(conn, params) do + search_term = Map.get(params, "q", "") + search_results = Media.search(search_term) + + render(conn, :show, search_term: search_term, search_results: search_results) + end +end diff --git a/lib/pinchflat_web/controllers/searches/search_html.ex b/lib/pinchflat_web/controllers/searches/search_html.ex new file mode 100644 index 0000000..1e049be --- /dev/null +++ b/lib/pinchflat_web/controllers/searches/search_html.ex @@ -0,0 +1,25 @@ +defmodule PinchflatWeb.Searches.SearchHTML do + use PinchflatWeb, :html + + embed_templates "search_html/*" + + @doc """ + Highlight search terms in a string of text based on `[PF_HIGHLIGHT]` and `[/PF_HIGHLIGHT]` tags + """ + attr :text, :string, required: true + + def highlight_search_terms(assigns) do + split_string = String.split(assigns.text, ~r{\[PF_HIGHLIGHT\]|\[/PF_HIGHLIGHT\]}, include_captures: true) + assigns = assign(assigns, split_string: split_string) + + ~H""" + <%= for fragment <- @split_string do %> + <%= render_fragment(fragment) %> + <% end %> + """ + end + + defp render_fragment("[PF_HIGHLIGHT]"), do: raw(~s()) + defp render_fragment("[/PF_HIGHLIGHT]"), do: raw("") + defp render_fragment(text), do: text +end diff --git a/lib/pinchflat_web/controllers/searches/search_html/show.html.heex b/lib/pinchflat_web/controllers/searches/search_html/show.html.heex new file mode 100644 index 0000000..996701e --- /dev/null +++ b/lib/pinchflat_web/controllers/searches/search_html/show.html.heex @@ -0,0 +1,29 @@ +
+

+ Results for "<%= StringUtils.truncate(@search_term, 50) %>" +

+
+ +
+
+
+ <%= if match?([_|_], @search_results) do %> + <.table rows={@search_results} table_class="text-black dark:text-white"> + <:col :let={result} label="Title"> + <%= StringUtils.truncate(result.title, 40) %> + + <:col :let={result} label="Excerpt"> + <.highlight_search_terms text={result.matching_search_term} /> + + <:col :let={result} label="" class="flex place-content-evenly"> + <.link navigate={~p"/media/#{result.id}"} class="hover:text-secondary duration-200 ease-in-out mx-0.5"> + <.icon name="hero-eye" /> + + + + <% else %> +

No results found

+ <% end %> +
+
+
diff --git a/lib/pinchflat_web/router.ex b/lib/pinchflat_web/router.ex index 55f177a..55c9fcb 100644 --- a/lib/pinchflat_web/router.ex +++ b/lib/pinchflat_web/router.ex @@ -21,6 +21,7 @@ defmodule PinchflatWeb.Router do resources "/media_profiles", MediaProfiles.MediaProfileController resources "/media", Media.MediaItemController, only: [:show] + resources "/search", Searches.SearchController, only: [:show], singleton: true resources "/sources", MediaSources.SourceController do resources "/media", Media.MediaItemController, only: [:show] diff --git a/test/pinchflat/media_test.exs b/test/pinchflat/media_test.exs index d20a92b..8fbe8d7 100644 --- a/test/pinchflat/media_test.exs +++ b/test/pinchflat/media_test.exs @@ -207,7 +207,7 @@ defmodule Pinchflat.MediaTest do assert String.contains?(res.matching_search_term, "The [PF_HIGHLIGHT]quick[/PF_HIGHLIGHT] brown fox") end - test "optionall lets you specify a limit" do + test "optionally lets you specify a limit" do media_item_fixture(%{title: "The small gray dog"}) assert [_] = Media.search("dog", limit: 1) diff --git a/test/pinchflat/utils/string_utils_test.exs b/test/pinchflat/utils/string_utils_test.exs index d77e468..51172e3 100644 --- a/test/pinchflat/utils/string_utils_test.exs +++ b/test/pinchflat/utils/string_utils_test.exs @@ -27,4 +27,18 @@ defmodule Pinchflat.Utils.StringUtilsTest do assert String.length(StringUtils.random_string(64)) == 64 end end + + describe "truncate/2" do + test "truncates a string to the given length and adds ..." do + assert StringUtils.truncate("hello world", 5) == "hello..." + end + + test "breaks on a word boundary" do + assert StringUtils.truncate("hello world", 7) == "hello..." + end + + test "does not truncate a string shorter than the given length" do + assert StringUtils.truncate("hello", 10) == "hello" + end + end end diff --git a/test/pinchflat_web/controllers/search_controller_test.exs b/test/pinchflat_web/controllers/search_controller_test.exs new file mode 100644 index 0000000..073ebc8 --- /dev/null +++ b/test/pinchflat_web/controllers/search_controller_test.exs @@ -0,0 +1,10 @@ +defmodule PinchflatWeb.SearchControllerTest do + use PinchflatWeb.ConnCase + + describe "show search" do + test "renders the page", %{conn: conn} do + conn = get(conn, ~p"/search") + assert html_response(conn, 200) =~ "Results" + end + end +end