add search to notes

This commit is contained in:
shibao 2022-11-19 00:21:14 -05:00
parent b95d3039bb
commit f9be5229e7
8 changed files with 128 additions and 17 deletions

View File

@ -20,10 +20,34 @@ defmodule Memex.Notes do
""" """
@spec list_notes(User.t()) :: [Note.t()] @spec list_notes(User.t()) :: [Note.t()]
def list_notes(%{id: user_id}) do @spec list_notes(search :: String.t() | nil, User.t()) :: [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
@ -36,10 +60,34 @@ defmodule Memex.Notes do
[%Note{title: "my note"}, ...] [%Note{title: "my 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,7 +59,7 @@ 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
@ -79,7 +79,8 @@ 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 "/contexts/:id", ContextLive.Show, :show

View File

@ -75,7 +75,7 @@ msgid "create invite"
msgstr "" msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:53 #: lib/memex_web/live/context_live/index.html.heex:53
#: lib/memex_web/live/note_live/index.html.heex:32 #: lib/memex_web/live/note_live/index.html.heex:43
#: lib/memex_web/live/pipeline_live/index.html.heex:51 #: lib/memex_web/live/pipeline_live/index.html.heex:51
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "delete" msgid "delete"
@ -88,7 +88,7 @@ msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:43 #: lib/memex_web/live/context_live/index.html.heex:43
#: lib/memex_web/live/context_live/show.html.heex:40 #: lib/memex_web/live/context_live/show.html.heex:40
#: lib/memex_web/live/note_live/index.html.heex:23 #: lib/memex_web/live/note_live/index.html.heex:34
#: lib/memex_web/live/note_live/show.html.heex:27 #: lib/memex_web/live/note_live/show.html.heex:27
#: lib/memex_web/live/pipeline_live/index.html.heex:41 #: lib/memex_web/live/pipeline_live/index.html.heex:41
#: lib/memex_web/live/pipeline_live/show.html.heex:35 #: lib/memex_web/live/pipeline_live/show.html.heex:35
@ -117,7 +117,7 @@ msgstr ""
msgid "new context" msgid "new context"
msgstr "" msgstr ""
#: lib/memex_web/live/note_live/index.html.heex:41 #: lib/memex_web/live/note_live/index.html.heex:52
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "new note" msgid "new note"
msgstr "" msgstr ""

View File

@ -15,7 +15,7 @@ msgstr ""
msgid "%{title} created" msgid "%{title} created"
msgstr "" msgstr ""
#: lib/memex_web/live/note_live/index.ex:48 #: lib/memex_web/live/note_live/index.ex:57
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "%{title} deleted" msgid "%{title} deleted"
msgstr "" msgstr ""
@ -167,7 +167,7 @@ msgstr ""
msgid "document your processes, attaching contexts to each step" msgid "document your processes, attaching contexts to each step"
msgstr "" msgstr ""
#: lib/memex_web/live/note_live/index.ex:24 #: lib/memex_web/live/note_live/index.ex:23
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "edit %{title}" msgid "edit %{title}"
msgstr "" msgstr ""
@ -267,7 +267,7 @@ msgstr ""
msgid "no invites 😔" msgid "no invites 😔"
msgstr "" msgstr ""
#: lib/memex_web/live/note_live/index.html.heex:8 #: lib/memex_web/live/note_live/index.html.heex:19
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "no notes found" msgid "no notes found"
msgstr "" msgstr ""

View File

@ -142,7 +142,7 @@ msgid "are you sure you want to make %{invite_name} unlimited?"
msgstr "" msgstr ""
#: lib/memex_web/live/context_live/index.html.heex:51 #: lib/memex_web/live/context_live/index.html.heex:51
#: lib/memex_web/live/note_live/index.html.heex:29 #: lib/memex_web/live/note_live/index.html.heex:40
#: lib/memex_web/live/pipeline_live/index.html.heex:49 #: lib/memex_web/live/pipeline_live/index.html.heex:49
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "are you sure?" msgid "are you sure?"

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