add search to notes
Some checks are pending
continuous-integration/drone/push Build is pending

This commit is contained in:
shibao 2022-11-18 23:45:15 -05:00
parent ec2fe32afe
commit 0729c63c3a
5 changed files with 125 additions and 14 deletions

View File

@ -17,10 +17,34 @@ defmodule Memex.Notes do
""" """
@spec list_notes(User.t() | nil) :: [Note.t()] @spec list_notes(User.t() | nil) :: [Note.t()]
def list_notes(%{id: user_id}) do @spec list_notes(search :: String.t() | nil, User.t() | nil) :: [Note.t()]
def list_notes(search \\ nil, user)
def list_notes(search, %{id: user_id}) when search |> is_nil() or search == "" do
Repo.all(from n in Note, where: n.user_id == ^user_id, order_by: n.title) Repo.all(from n in Note, where: n.user_id == ^user_id, order_by: n.title)
end end
def list_notes(search, %{id: user_id}) when search |> is_binary() do
trimmed_search = String.trim(search)
Repo.all(
from n in Note,
where: n.user_id == ^user_id,
where:
fragment(
"search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)",
^trimmed_search
)
}
)
end
@doc """ @doc """
Returns the list of public notes for viewing Returns the list of public notes for viewing
@ -30,10 +54,34 @@ defmodule Memex.Notes do
[%Note{}, ...] [%Note{}, ...]
""" """
@spec list_public_notes() :: [Note.t()] @spec list_public_notes() :: [Note.t()]
def list_public_notes do @spec list_public_notes(search :: String.t() | nil) :: [Note.t()]
def list_public_notes(search \\ nil)
def list_public_notes(search) when search |> is_nil() or search == "" do
Repo.all(from n in Note, where: n.visibility == :public, order_by: n.title) Repo.all(from n in Note, where: n.visibility == :public, order_by: n.title)
end end
def list_public_notes(search) when search |> is_binary() do
trimmed_search = String.trim(search)
Repo.all(
from n in Note,
where: n.visibility == :public,
where:
fragment(
"search @@ to_tsquery(websearch_to_tsquery(?)::text || ':*')",
^trimmed_search
),
order_by: {
:desc,
fragment(
"ts_rank_cd(search, to_tsquery(websearch_to_tsquery(?)::text || ':*'), 4)",
^trimmed_search
)
}
)
end
@doc """ @doc """
Gets a single note. Gets a single note.

View File

@ -3,13 +3,12 @@ defmodule MemexWeb.NoteLive.Index do
alias Memex.{Notes, Notes.Note} alias Memex.{Notes, Notes.Note}
@impl true @impl true
def mount(_params, _session, %{assigns: %{current_user: current_user}} = socket) def mount(%{"search" => search}, _session, socket) do
when not (current_user |> is_nil()) do {:ok, socket |> assign(search: search) |> display_notes()}
{:ok, socket |> assign(notes: Notes.list_notes(current_user))}
end end
def mount(_params, _session, socket) do def mount(_params, _session, socket) do
{:ok, socket |> assign(notes: Notes.list_public_notes())} {:ok, socket |> assign(search: nil) |> display_notes()}
end end
@impl true @impl true
@ -34,7 +33,17 @@ defmodule MemexWeb.NoteLive.Index do
defp apply_action(socket, :index, _params) do defp apply_action(socket, :index, _params) do
socket socket
|> assign(page_title: "notes") |> assign(page_title: "notes")
|> assign(search: nil)
|> assign(note: nil) |> assign(note: nil)
|> display_notes()
end
defp apply_action(socket, :search, %{"search" => search}) do
socket
|> assign(page_title: "notes")
|> assign(search: search)
|> assign(note: nil)
|> display_notes()
end end
@impl true @impl true
@ -49,4 +58,22 @@ defmodule MemexWeb.NoteLive.Index do
{:noreply, socket} {:noreply, socket}
end end
@impl true
def handle_event("search", %{"search" => %{"search_term" => ""}}, socket) do
{:noreply, socket |> push_patch(to: Routes.note_index_path(Endpoint, :index))}
end
def handle_event("search", %{"search" => %{"search_term" => search_term}}, socket) do
{:noreply, socket |> push_patch(to: Routes.note_index_path(Endpoint, :search, search_term))}
end
defp display_notes(%{assigns: %{current_user: current_user, search: search}} = socket)
when not (current_user |> is_nil()) do
socket |> assign(notes: Notes.list_notes(search, current_user))
end
defp display_notes(%{assigns: %{search: search}} = socket) do
socket |> assign(notes: Notes.list_public_notes(search))
end
end end

View File

@ -3,6 +3,17 @@
<%= gettext("notes") %> <%= gettext("notes") %>
</h1> </h1>
<.form
:let={f}
for={:search}
phx-change="search"
phx-submit="search"
phx-debounce="500"
class="self-stretch flex flex-col items-stretch"
>
<%= text_input(f, :search_term, class: "input input-primary", value: @search) %>
</.form>
<%= if @notes |> Enum.empty?() do %> <%= if @notes |> Enum.empty?() do %>
<h1 class="self-center text-primary-500"> <h1 class="self-center text-primary-500">
<%= gettext("no notes found") %> <%= gettext("no notes found") %>

View File

@ -59,15 +59,15 @@ defmodule MemexWeb.Router do
live "/notes/new", NoteLive.Index, :new live "/notes/new", NoteLive.Index, :new
live "/notes/:id/edit", NoteLive.Index, :edit live "/notes/:id/edit", NoteLive.Index, :edit
live "/notes/:id/show/edit", NoteLive.Show, :edit live "/note/:id/edit", NoteLive.Show, :edit
live "/contexts/new", ContextLive.Index, :new live "/contexts/new", ContextLive.Index, :new
live "/contexts/:id/edit", ContextLive.Index, :edit live "/contexts/:id/edit", ContextLive.Index, :edit
live "/contexts/:id/show/edit", ContextLive.Show, :edit live "/context/:id/show/edit", ContextLive.Show, :edit
live "/pipelines/new", PipelineLive.Index, :new live "/pipelines/new", PipelineLive.Index, :new
live "/pipelines/:id/edit", PipelineLive.Index, :edit live "/pipelines/:id/edit", PipelineLive.Index, :edit
live "/pipelines/:id/show/edit", PipelineLive.Show, :edit live "/pipeline/:id/edit", PipelineLive.Show, :edit
get "/users/settings", UserSettingsController, :edit get "/users/settings", UserSettingsController, :edit
put "/users/settings", UserSettingsController, :update put "/users/settings", UserSettingsController, :update
@ -79,13 +79,14 @@ defmodule MemexWeb.Router do
pipe_through [:browser] pipe_through [:browser]
live "/notes", NoteLive.Index, :index live "/notes", NoteLive.Index, :index
live "/notes/:id", NoteLive.Show, :show live "/notes/:search", NoteLive.Index, :search
live "/note/:id", NoteLive.Show, :show
live "/contexts", ContextLive.Index, :index live "/contexts", ContextLive.Index, :index
live "/contexts/:id", ContextLive.Show, :show live "/context/:id", ContextLive.Show, :show
live "/pipelines", PipelineLive.Index, :index live "/pipelines", PipelineLive.Index, :index
live "/pipelines/:id", PipelineLive.Show, :show live "/pipeline/:id", PipelineLive.Show, :show
end end
end end

View File

@ -1,17 +1,41 @@
defmodule Memex.Repo.Migrations.CreateNotes do defmodule Memex.Repo.Migrations.CreateNotes do
use Ecto.Migration use Ecto.Migration
def change do def up do
create table(:notes, primary_key: false) do create table(:notes, primary_key: false) do
add :id, :binary_id, primary_key: true add :id, :binary_id, primary_key: true
add :title, :string add :title, :string
add :content, :text add :content, :text
add :tags, {:array, :string} add :tags, {:array, :citext}
add :visibility, :string add :visibility, :string
add :user_id, references(:users, on_delete: :delete_all, type: :binary_id) add :user_id, references(:users, on_delete: :delete_all, type: :binary_id)
timestamps() timestamps()
end end
flush()
execute """
CREATE FUNCTION immutable_array_to_string(text[], text)
RETURNS text LANGUAGE sql IMMUTABLE as $$SELECT array_to_string($1, $2)$$
"""
execute """
ALTER TABLE notes
ADD COLUMN search tsvector
GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(immutable_array_to_string(tags, ' '), '')), 'B') ||
setweight(to_tsvector('english', coalesce(content, '')), 'C')
) STORED
"""
execute("CREATE INDEX notes_trgm_idx ON notes USING GIN (search)")
end
def down do
drop table(:notes)
execute("DROP FUNCTION immutable_array_to_string(text[], text)")
end end
end end