2022-03-26 10:17:01 +13:00
|
|
|
defmodule AshHqWeb.Components.Search do
|
2022-08-07 11:22:58 +12:00
|
|
|
@moduledoc "The search overlay modal"
|
2024-04-03 09:38:44 +13:00
|
|
|
use Phoenix.LiveComponent
|
2022-03-26 10:17:01 +13:00
|
|
|
|
|
|
|
require Ash.Query
|
|
|
|
|
2023-09-27 19:26:16 +13:00
|
|
|
alias AshHqWeb.Components.Icon
|
2022-08-07 11:22:58 +12:00
|
|
|
alias AshHqWeb.DocRoutes
|
2024-04-03 09:38:44 +13:00
|
|
|
import AshHqWeb.Tails
|
2022-03-26 10:17:01 +13:00
|
|
|
|
2024-04-03 09:38:44 +13:00
|
|
|
attr(:libraries, :list, required: true)
|
|
|
|
attr(:selected_types, :list, required: true)
|
|
|
|
attr(:uri, :string, required: true)
|
|
|
|
attr(:close, :any)
|
2022-03-26 10:17:01 +13:00
|
|
|
|
|
|
|
def render(assigns) do
|
2024-04-03 09:38:44 +13:00
|
|
|
~H"""
|
2022-03-26 16:16:20 +13:00
|
|
|
<div
|
|
|
|
id={@id}
|
2022-03-28 10:26:35 +13:00
|
|
|
style="display: none;"
|
2022-11-17 08:36:05 +13:00
|
|
|
class="fixed flex justify-center align-middle w-screen h-full backdrop-blur-sm bg-white bg-opacity-10 z-50"
|
2022-03-26 16:16:20 +13:00
|
|
|
>
|
2022-03-26 10:17:01 +13:00
|
|
|
<div
|
2024-04-03 09:38:44 +13:00
|
|
|
phx-click-away={AshHqWeb.AppViewLive.toggle_search()}
|
2023-02-01 05:13:42 +13:00
|
|
|
class="dark:text-white absolute rounded-xl left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-3/4 h-3/4 max-w-[1200px] bg-white dark:bg-base-dark-850 border-2 dark:border-base-dark-900"
|
2024-04-03 09:38:44 +13:00
|
|
|
phx-target={@myself}
|
|
|
|
phx-window-keydown="select-previous"
|
2022-03-26 16:16:20 +13:00
|
|
|
phx-key="ArrowUp"
|
|
|
|
>
|
2024-04-03 09:38:44 +13:00
|
|
|
<div id="search-body" class="h-full" phx-target={@myself} phx-window-keydown="select-next" phx-key="ArrowDown">
|
2023-01-31 03:21:42 +13:00
|
|
|
<div class="p-6 h-full grid gap-6 grid-rows-[max-content_auto_max-content]">
|
|
|
|
<button
|
|
|
|
id="close-search"
|
|
|
|
class="absolute top-6 right-6 h-6 w-6 cursor-pointer z-10 hover:text-base-light-400"
|
2024-04-03 09:38:44 +13:00
|
|
|
phx-click={@close}
|
2023-01-31 03:21:42 +13:00
|
|
|
>
|
2024-04-03 09:38:44 +13:00
|
|
|
<span class="hero-x-mark h-6 w-6"/>
|
2023-01-31 03:21:42 +13:00
|
|
|
</button>
|
|
|
|
<div class="flex flex-col w-full sticky">
|
|
|
|
<div class="w-full flex flex-row justify-start top-0">
|
2024-04-03 09:38:44 +13:00
|
|
|
<span class="hero-magnifying-glass h-6 w-6 mr-4"/>
|
2023-01-31 03:21:42 +13:00
|
|
|
<div class="flex flex-row justify-between w-full mr-10 border-b border-base-light-600">
|
2024-04-03 09:38:44 +13:00
|
|
|
<.form for={%{}} as={:search} phx-target={@myself} phx-change="search" phx-submit="go-to-doc" class="w-full">
|
2023-01-31 03:21:42 +13:00
|
|
|
<input
|
|
|
|
id="search-input"
|
|
|
|
name="search"
|
|
|
|
value={@search}
|
|
|
|
phx-debounce={300}
|
|
|
|
class="text-lg dark:bg-base-dark-850 grow ring-0 outline-none w-full"
|
|
|
|
/>
|
2024-04-03 09:38:44 +13:00
|
|
|
</.form>
|
2023-01-31 03:21:42 +13:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-03-26 10:17:01 +13:00
|
|
|
</div>
|
2023-09-28 03:08:52 +13:00
|
|
|
<div class="grid overflow-auto scroll-parent">
|
2024-04-03 09:38:44 +13:00
|
|
|
<%= render_items(assigns, @item_list) %>
|
2022-08-31 20:11:15 +12:00
|
|
|
</div>
|
2022-03-26 10:17:01 +13:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
2022-07-22 09:11:21 +12:00
|
|
|
defp render_items(assigns, items) do
|
2024-04-03 09:38:44 +13:00
|
|
|
assigns = assign(assigns, items: items)
|
|
|
|
|
|
|
|
~H"""
|
2023-01-31 03:21:42 +13:00
|
|
|
<div class="divide-y">
|
2024-04-03 09:38:44 +13:00
|
|
|
<%= for item <- @items do %>
|
|
|
|
<%= if item.__struct__ == AshHq.Docs.Guide do %>
|
|
|
|
<.link
|
2023-09-27 17:23:56 +13:00
|
|
|
class="block w-full text-left border-base-light-300 dark:border-base-dark-600"
|
2024-04-03 09:38:44 +13:00
|
|
|
href={DocRoutes.doc_link(item)}
|
|
|
|
id="result-#{item.id}"
|
|
|
|
phx-click={@close}
|
2023-09-27 17:23:56 +13:00
|
|
|
>
|
|
|
|
<div class={
|
2024-04-03 09:38:44 +13:00
|
|
|
classes(
|
|
|
|
["hover:bg-base-light-100 dark:hover:bg-base-dark-750 py-1 w-full",
|
|
|
|
"bg-base-light-200 dark:bg-base-dark-700": @selected_item.id == item.id]
|
|
|
|
)
|
2023-09-27 17:23:56 +13:00
|
|
|
}>
|
|
|
|
<div class="flex justify-start items-center space-x-2 pb-2 pl-2">
|
|
|
|
<div>
|
2024-04-03 09:38:44 +13:00
|
|
|
<Icon.icon type={item_type(item)} classes="h-4 w-4 flex-none mt-1 mx-1" />
|
2023-09-27 17:23:56 +13:00
|
|
|
</div>
|
2023-10-13 11:08:34 +13:00
|
|
|
<div class="flex flex-col">
|
|
|
|
<div class="text-primary-light-700 dark:text-primary-dark-300">
|
|
|
|
<span class="text-primary-light-700 dark:text-primary-dark-500">
|
2024-04-03 09:38:44 +13:00
|
|
|
<%= item.library_name %>
|
2023-10-13 11:08:34 +13:00
|
|
|
</span>
|
2024-04-03 09:38:44 +13:00
|
|
|
<%= item_type(item) %>
|
2023-10-13 11:08:34 +13:00
|
|
|
</div>
|
|
|
|
<div class="flex flex-row flex-wrap items-center">
|
|
|
|
<div class="font-bold">
|
2024-04-03 09:38:44 +13:00
|
|
|
<%= item_name(item) %>
|
2023-09-27 17:23:56 +13:00
|
|
|
</div>
|
2023-10-13 11:08:34 +13:00
|
|
|
</div>
|
|
|
|
<div>
|
2024-04-03 09:38:44 +13:00
|
|
|
<%= first_sentence(item) %>
|
2023-01-31 03:21:42 +13:00
|
|
|
</div>
|
2022-07-22 09:11:21 +12:00
|
|
|
</div>
|
2022-07-15 08:44:20 +12:00
|
|
|
</div>
|
2023-10-13 11:08:34 +13:00
|
|
|
<div>
|
2023-09-27 17:23:56 +13:00
|
|
|
</div>
|
2022-07-15 08:44:20 +12:00
|
|
|
</div>
|
2024-04-03 09:38:44 +13:00
|
|
|
</.link>
|
|
|
|
<% else %>
|
2023-10-13 11:08:34 +13:00
|
|
|
<a
|
2023-09-27 17:23:56 +13:00
|
|
|
class="block w-full text-left border-base-light-300 dark:border-base-dark-600"
|
2023-10-13 11:08:34 +13:00
|
|
|
href={DocRoutes.doc_link(item)}
|
|
|
|
id={"result-#{item.id}"}
|
|
|
|
phx-click={@close}
|
2023-09-27 17:23:56 +13:00
|
|
|
>
|
2024-04-03 09:38:44 +13:00
|
|
|
<div class={classes([
|
2023-10-13 11:08:34 +13:00
|
|
|
"hover:bg-base-light-100 dark:hover:bg-base-dark-750 py-1 w-full",
|
2023-09-27 17:23:56 +13:00
|
|
|
"bg-base-light-200 dark:bg-base-dark-700": @selected_item.id == item.id
|
2024-04-03 09:38:44 +13:00
|
|
|
])
|
2023-09-27 17:23:56 +13:00
|
|
|
}>
|
|
|
|
<div class="flex justify-start items-center space-x-2 pb-2 pl-2">
|
|
|
|
<div>
|
2024-04-03 09:38:44 +13:00
|
|
|
<Icon.icon type={item_type(item)} classes="h-4 w-4 flex-none mt-1 mx-1" />
|
2023-09-27 17:23:56 +13:00
|
|
|
</div>
|
2023-10-13 11:08:34 +13:00
|
|
|
<div class="flex flex-col">
|
|
|
|
<div class="text-primary-light-700 dark:text-primary-dark-300">
|
|
|
|
<span class="text-primary-light-700 dark:text-primary-dark-500">
|
2024-04-03 09:38:44 +13:00
|
|
|
<%= item.library_name %>
|
2023-10-13 11:08:34 +13:00
|
|
|
</span>
|
2024-04-03 09:38:44 +13:00
|
|
|
<%= item_type(item) %>
|
2023-10-13 11:08:34 +13:00
|
|
|
</div>
|
|
|
|
<div class="flex flex-row flex-wrap items-center">
|
|
|
|
<div class="font-bold">
|
2024-04-03 09:38:44 +13:00
|
|
|
<%= item_name(item) %>
|
2023-09-27 17:23:56 +13:00
|
|
|
</div>
|
2023-10-13 11:08:34 +13:00
|
|
|
</div>
|
|
|
|
<div>
|
2024-04-03 09:38:44 +13:00
|
|
|
<%= first_sentence(item) %>
|
2023-09-27 17:23:56 +13:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-10-13 11:08:34 +13:00
|
|
|
<div>
|
2023-09-27 17:23:56 +13:00
|
|
|
</div>
|
2023-01-31 03:21:42 +13:00
|
|
|
</div>
|
2023-10-13 11:08:34 +13:00
|
|
|
</a>
|
2024-04-03 09:38:44 +13:00
|
|
|
<% end %>
|
|
|
|
<% end %>
|
2023-01-31 03:21:42 +13:00
|
|
|
</div>
|
2022-03-26 16:16:20 +13:00
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
2023-10-13 11:08:34 +13:00
|
|
|
defp first_sentence(%{text: text}), do: first_sentence(text)
|
|
|
|
defp first_sentence(%{doc: doc}), do: first_sentence(doc)
|
2022-07-22 09:11:21 +12:00
|
|
|
|
2023-10-13 11:08:34 +13:00
|
|
|
defp first_sentence(doc) do
|
|
|
|
first_sentence =
|
|
|
|
doc
|
|
|
|
|> String.trim()
|
2023-10-14 04:57:12 +13:00
|
|
|
|> String.split("<!--- heads-end -->", parts: 2, trim: true)
|
2023-10-13 11:08:34 +13:00
|
|
|
|> List.last()
|
|
|
|
|> String.trim()
|
2023-10-14 04:57:12 +13:00
|
|
|
|> String.split("\n", parts: 3, trim: true)
|
|
|
|
|> case do
|
|
|
|
["#" <> _ | rest] -> rest
|
|
|
|
other -> other
|
|
|
|
end
|
2023-10-13 11:08:34 +13:00
|
|
|
|> Enum.at(0)
|
|
|
|
|> String.trim()
|
2022-07-22 09:11:21 +12:00
|
|
|
|
2023-10-13 11:08:34 +13:00
|
|
|
if String.starts_with?(first_sentence, "`") do
|
|
|
|
""
|
|
|
|
else
|
|
|
|
first_sentence
|
|
|
|
end
|
2022-07-22 09:11:21 +12:00
|
|
|
end
|
|
|
|
|
2023-10-13 11:08:34 +13:00
|
|
|
defp item_name(%AshHq.Docs.Function{call_name: call_name, arity: arity}),
|
|
|
|
do: call_name <> "/#{arity}"
|
2022-07-22 09:11:21 +12:00
|
|
|
|
2023-10-13 11:08:34 +13:00
|
|
|
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
|
2022-07-22 09:11:21 +12:00
|
|
|
|
2022-03-26 10:17:01 +13:00
|
|
|
def mount(socket) do
|
|
|
|
{:ok, socket}
|
|
|
|
end
|
|
|
|
|
2022-03-30 17:40:17 +13:00
|
|
|
def update(assigns, socket) do
|
2023-01-05 05:18:04 +13:00
|
|
|
if assigns[:uri] != socket.assigns[:uri] do
|
|
|
|
{:ok, socket |> assign(:search, nil) |> assign(assigns) |> search()}
|
|
|
|
else
|
|
|
|
{:ok, socket |> assign(assigns) |> search()}
|
|
|
|
end
|
2022-03-30 17:40:17 +13:00
|
|
|
end
|
|
|
|
|
2022-09-28 16:18:05 +13:00
|
|
|
def handle_event("toggle_versions", _, socket) do
|
|
|
|
{:noreply, socket |> assign(:selecting_packages, !socket.assigns.selecting_packages)}
|
|
|
|
end
|
|
|
|
|
2022-03-26 10:17:01 +13:00
|
|
|
def handle_event("search", %{"search" => search}, socket) do
|
|
|
|
{:noreply, socket |> assign(:search, search) |> search()}
|
|
|
|
end
|
|
|
|
|
2022-03-26 16:16:20 +13:00
|
|
|
def handle_event("select-next", _, socket) do
|
2022-03-28 10:26:35 +13:00
|
|
|
if socket.assigns[:selected_item] && socket.assigns[:item_list] do
|
2022-03-26 16:16:20 +13:00
|
|
|
next =
|
2024-04-03 09:38:44 +13:00
|
|
|
socket.assigns[:item_list]
|
|
|
|
|> Kernel.||([])
|
2022-03-28 10:26:35 +13:00
|
|
|
|> Enum.drop_while(&(&1.id != socket.assigns.selected_item.id))
|
2022-03-26 16:16:20 +13:00
|
|
|
|> Enum.at(1)
|
|
|
|
|
|
|
|
{:noreply, set_selected_item(socket, next)}
|
|
|
|
else
|
|
|
|
{:noreply, socket}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def handle_event("select-previous", _, socket) do
|
2022-03-28 10:26:35 +13:00
|
|
|
if socket.assigns[:selected_item] && socket.assigns[:item_list] do
|
2022-03-26 16:16:20 +13:00
|
|
|
next =
|
2024-04-03 09:38:44 +13:00
|
|
|
socket.assigns[:item_list]
|
|
|
|
|> Kernel.||([])
|
2022-03-26 16:16:20 +13:00
|
|
|
|> Enum.reverse()
|
2022-03-28 10:26:35 +13:00
|
|
|
|> Enum.drop_while(&(&1.id != socket.assigns.selected_item.id))
|
2022-03-26 16:16:20 +13:00
|
|
|
|> Enum.at(1)
|
|
|
|
|
|
|
|
{:noreply, set_selected_item(socket, next)}
|
|
|
|
else
|
|
|
|
{:noreply, socket}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-11-26 20:53:55 +13:00
|
|
|
def handle_event("go-to-doc", _data, socket) do
|
2024-04-03 09:38:44 +13:00
|
|
|
case Enum.find(socket.assigns[:item_list] || [], fn item ->
|
2022-03-28 10:26:35 +13:00
|
|
|
item.id == socket.assigns.selected_item.id
|
|
|
|
end) do
|
|
|
|
nil ->
|
|
|
|
{:noreply, socket}
|
|
|
|
|
|
|
|
item ->
|
2022-11-26 20:53:55 +13:00
|
|
|
{:noreply,
|
|
|
|
socket
|
|
|
|
|> push_event("click-on-item", %{"id" => "result-#{item.id}"})}
|
2022-03-28 10:26:35 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-15 08:44:20 +12:00
|
|
|
defp item_type(%resource{}) do
|
|
|
|
AshHq.Docs.Extensions.Search.item_type(resource)
|
|
|
|
end
|
|
|
|
|
2022-03-26 10:17:01 +13:00
|
|
|
defp search(socket) do
|
2023-09-27 19:26:16 +13:00
|
|
|
if socket.assigns.search in [nil, ""] do
|
2024-04-03 09:38:44 +13:00
|
|
|
assign(socket, :item_list, [])
|
2023-09-28 02:56:53 +13:00
|
|
|
else
|
2023-10-13 11:08:34 +13:00
|
|
|
item_list =
|
|
|
|
socket.assigns.search
|
|
|
|
|> AshHq.Docs.Indexer.search()
|
|
|
|
|> Enum.take(50)
|
2022-03-26 16:16:20 +13:00
|
|
|
|
2023-09-28 02:56:53 +13:00
|
|
|
socket
|
|
|
|
|> assign(:item_list, item_list)
|
2023-10-13 11:08:34 +13:00
|
|
|
|> set_selected_item(Enum.at(item_list, 0))
|
2022-03-26 10:17:01 +13:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-03-26 16:16:20 +13:00
|
|
|
defp set_selected_item(socket, nil), do: socket
|
|
|
|
|
|
|
|
defp set_selected_item(socket, selected_item) do
|
|
|
|
socket
|
|
|
|
|> assign(:selected_item, selected_item)
|
2022-11-17 08:36:05 +13:00
|
|
|
|> push_event("js:scroll-to", %{id: "result-#{selected_item.id}"})
|
2022-03-26 16:16:20 +13:00
|
|
|
end
|
2022-03-26 10:17:01 +13:00
|
|
|
end
|