defmodule AshHqWeb.Components.Search do @moduledoc "The search overlay modal" use Surface.LiveComponent require Ash.Query alias AshHqWeb.Components.Icon alias AshHqWeb.DocRoutes alias Surface.Components.{Form, LivePatch} alias Surface.Components.Form.Checkbox prop(close, :event, required: true) prop(libraries, :list, required: true) prop(selected_types, :list, required: true) prop(change_types, :event, required: true) prop(change_versions, :event, required: true) prop(remove_version, :event, required: true) prop(uri, :string, required: true) data(search, :string, default: "") data(item_list, :list, default: []) data(selected_item, :string) data(selecting_packages, :boolean, default: false) def render(assigns) do ~F""" """ end defp render_items(assigns, items) do ~F"""
{#for item <- items} {#if item.__struct__ == AshHq.Docs.Guide}
{item.library_name} {item_type(item)}
{item_name(item)}
{first_sentence(item)}
{/if} {#if item.__struct__ != AshHq.Docs.Guide}
{item.library_name} {item_type(item)}
{item_name(item)}
{first_sentence(item)}
{/if} {/for}
""" end defp first_sentence(%{text: text}), do: first_sentence(text) defp first_sentence(%{doc: doc}), do: first_sentence(doc) defp first_sentence(doc) do first_sentence = doc |> String.trim() |> String.split("", parts: 2) |> List.last() |> String.trim() |> String.split("\n", parts: 2) |> Enum.at(0) |> String.trim() if String.starts_with?(first_sentence, "`") do "" else first_sentence end end defp item_name(%AshHq.Docs.Function{call_name: call_name, arity: arity}), do: call_name <> "/#{arity}" defp item_name(%AshHq.Docs.Option{path: path, name: name}), do: Enum.join(path ++ [name], ".") defp item_name(%AshHq.Docs.Dsl{path: path, name: name}), do: Enum.join(path ++ [name], ".") defp item_name(%{name: name}), do: name defp item_name(%{version: version}), do: version def mount(socket) do {:ok, socket} end def update(assigns, socket) do if assigns[:uri] != socket.assigns[:uri] do {:ok, socket |> assign(:search, nil) |> assign(assigns) |> search()} else {:ok, socket |> assign(assigns) |> search()} end end def handle_event("toggle_versions", _, socket) do {:noreply, socket |> assign(:selecting_packages, !socket.assigns.selecting_packages)} end def handle_event("search", %{"search" => search}, socket) do {:noreply, socket |> assign(:search, search) |> search()} end def handle_event("select-next", _, socket) do if socket.assigns[:selected_item] && socket.assigns[:item_list] do next = socket.assigns.item_list |> Enum.drop_while(&(&1.id != socket.assigns.selected_item.id)) |> Enum.at(1) {:noreply, set_selected_item(socket, next)} else {:noreply, socket} end end def handle_event("select-previous", _, socket) do if socket.assigns[:selected_item] && socket.assigns[:item_list] do next = socket.assigns.item_list |> Enum.reverse() |> Enum.drop_while(&(&1.id != socket.assigns.selected_item.id)) |> Enum.at(1) {:noreply, set_selected_item(socket, next)} else {:noreply, socket} end end def handle_event("go-to-doc", _data, socket) do case Enum.find(socket.assigns.item_list, fn item -> item.id == socket.assigns.selected_item.id end) do nil -> {:noreply, socket} item -> {:noreply, socket |> push_event("click-on-item", %{"id" => "result-#{item.id}"})} end end defp item_type(%resource{}) do AshHq.Docs.Extensions.Search.item_type(resource) end defp search(socket) do if socket.assigns.search in [nil, ""] do socket else item_list = socket.assigns.search |> AshHq.Docs.Indexer.search() |> Enum.take(50) socket |> assign(:item_list, item_list) |> set_selected_item(Enum.at(item_list, 0)) end end defp set_selected_item(socket, nil), do: socket defp set_selected_item(socket, selected_item) do socket |> assign(:selected_item, selected_item) |> push_event("js:scroll-to", %{id: "result-#{selected_item.id}"}) end end