2 Commits

  1. 9
      CHANGELOG.md
  2. 4
      assets/js/app.js
  3. 4
      assets/js/hooks.js
  4. 6
      lib/mirage/links.ex
  5. 9
      lib/mirage/links/link.ex
  6. 18
      lib/mirage/links/link_topic.ex
  7. 100
      lib/mirage/notes.ex
  8. 21
      lib/mirage/notes/note.ex
  9. 6
      lib/mirage/notes/note_link.ex
  10. 5
      lib/mirage/notes/note_note.ex
  11. 5
      lib/mirage/notes/note_topic.ex
  12. 145
      lib/mirage/notes/tags.ex
  13. 208
      lib/mirage/tags.ex
  14. 104
      lib/mirage/topics.ex
  15. 3
      lib/mirage/topics/topic.ex
  16. 26
      lib/mirage_web/controllers/topic_controller.ex
  17. 2
      lib/mirage_web/live/goto_anything_live.ex
  18. 22
      lib/mirage_web/live/link_live/form_component.ex
  19. 9
      lib/mirage_web/live/link_live/form_component.html.leex
  20. 13
      lib/mirage_web/live/link_live/show.ex
  21. 5
      lib/mirage_web/live/link_live/show.html.leex
  22. 29
      lib/mirage_web/live/note_live/form_component.ex
  23. 2
      lib/mirage_web/templates/topic/index.html.eex
  24. 16
      lib/mirage_web/templates/topic/show.html.eex
  25. 2
      mix.exs
  26. 13
      priv/repo/migrations/20210223202011_create_link_topic.exs

9
CHANGELOG.md

@ -5,6 +5,15 @@ See [Conventional Commits](Https://conventionalcommits.org) for commit guideline
<!-- changelog -->
## [v0.56.0](https://git.inhji.de/inhji/mirage/compare/v0.55.2...v0.56.0) (2021-02-23)
### Features:
* add Topics to Links, Separate Topics from Notes
## [v0.55.2](https://git.inhji.de/inhji/mirage/compare/v0.55.1...v0.55.2) (2021-02-23)

4
assets/js/app.js

@ -18,10 +18,10 @@ import {Socket} from "phoenix"
import NProgress from "nprogress"
import {LiveSocket} from "phoenix_live_view"
import TableSort from "tablesort"
import {NoteForm, GotoAnything, onKeydown} from "./hooks"
import {NoteForm, LinkForm, GotoAnything, onKeydown} from "./hooks"
const handledKeys = ["p"]
const Hooks = {NoteForm, GotoAnything}
const Hooks = {NoteForm, LinkForm, GotoAnything}
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {

4
assets/js/hooks.js

@ -20,7 +20,7 @@ export const NoteForm = {
const allTags = document.querySelector("#all_tags").value
const tagsEl = document.querySelector("#tags")
var tagify = new module.default(tagsEl, {whitelist: allTags.split(",")})
const tagify = new module.default(tagsEl, {whitelist: allTags.split(",")})
})
}
@ -28,6 +28,8 @@ export const NoteForm = {
}
}
export const LinkForm = NoteForm
export const onKeydown = (e, el) => {
if (handledKeys.includes(e.key) && e.ctrlKey)
e.preventDefault()

6
lib/mirage/links.ex

@ -5,9 +5,10 @@ defmodule Mirage.Links do
import Ecto.Query, warn: false
alias Mirage.Repo
alias Mirage.Links.Link
@link_preloads [:topics, :notes]
@doc """
Returns the list of notes_links.
@ -20,6 +21,7 @@ defmodule Mirage.Links do
def list_links do
Link
|> Repo.all()
|> Repo.preload(@link_preloads)
end
@doc """
@ -35,6 +37,8 @@ defmodule Mirage.Links do
"""
def get_link!(id), do: Repo.get!(Link, id)
def preload_link(link), do: Repo.preload(link, @link_preloads)
@doc """
Creates a link.

9
lib/mirage/links/link.ex

@ -1,14 +1,18 @@
defmodule Mirage.Links.Link do
use Ecto.Schema
import Ecto.Changeset
alias Mirage.Notes.Note
schema "links" do
field :title, :string
field :url, :string
field :domain, :string
many_to_many :note, Note, join_through: "notes_links"
many_to_many :notes, Mirage.Notes.Note, join_through: "notes_links"
many_to_many :topics, Mirage.Topics.Topic, join_through: "links_topics"
field :topic_string, :string,
virtual: true,
default: ""
end
@doc false
@ -17,6 +21,7 @@ defmodule Mirage.Links.Link do
|> cast(attrs, [:title, :url, :domain])
|> validate_required([:url])
|> maybe_extract_domain()
|> Mirage.Tags.maybe_load_tag_string()
end
defp maybe_extract_domain(changeset) do

18
lib/mirage/links/link_topic.ex

@ -0,0 +1,18 @@
defmodule Mirage.Links.LinkTopic do
use Ecto.Schema
import Ecto.Changeset
schema "links_topics" do
belongs_to :link, Mirage.Links.Link
belongs_to :topic, Mirage.Topics.Topic
end
@attrs [:link_id, :topic_id]
@doc false
def changeset(link_topic, attrs) do
link_topic
|> cast(attrs, @attrs)
|> validate_required(@attrs)
end
end

100
lib/mirage/notes.ex

@ -6,10 +6,9 @@ defmodule Mirage.Notes do
import Ecto.Query, warn: false
alias Mirage.Repo
alias Mirage.Notes.{Note, Topic, NoteNote, NoteTopic, NoteLink}
alias Mirage.Notes.{Note, NoteNote, NoteTopic, NoteLink}
@note_preloads [:links, :topics, :backlinks]
@topic_preloads [notes: @note_preloads]
defmacro contains(content, search_term) do
quote do
@ -280,103 +279,6 @@ defmodule Mirage.Notes do
Repo.transaction(multi)
end
@doc """
Returns the list of topics.
## Examples
iex> list_topics()
[%Topic{}, ...]
"""
def list_topics do
Repo.all(Topic)
|> Repo.preload(@topic_preloads)
end
@doc """
Gets a single topic.
Raises `Ecto.NoResultsError` if the Note topic does not exist.
## Examples
iex> get_topic!(123)
%Topic{}
iex> get_topic!(456)
** (Ecto.NoResultsError)
"""
def get_topic!(id), do: Repo.get!(Topic, id)
def preload_topic(topic), do: Repo.preload(topic, @topic_preloads)
@doc """
Creates a topic.
## Examples
iex> create_topic(%{field: value})
{:ok, %Topic{}}
iex> create_topic(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_topic(attrs \\ %{}) do
%Topic{}
|> Topic.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a topic.
## Examples
iex> update_topic(topic, %{field: new_value})
{:ok, %Topic{}}
iex> update_topic(topic, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_topic(%Topic{} = topic, attrs) do
topic
|> Topic.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a topic.
## Examples
iex> delete_topic(topic)
{:ok, %Topic{}}
iex> delete_topic(topic)
{:error, %Ecto.Changeset{}}
"""
def delete_topic(%Topic{} = topic) do
Repo.delete(topic)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking topic changes.
## Examples
iex> change_topic(topic)
%Ecto.Changeset{data: %Topic{}}
"""
def change_topic(%Topic{} = topic, attrs \\ %{}) do
Topic.changeset(topic, attrs)
end
def create_note_link(attrs) do
%NoteLink{}
|> NoteLink.changeset(attrs)

21
lib/mirage/notes/note.ex

@ -2,9 +2,6 @@ defmodule Mirage.Notes.Note do
use Ecto.Schema
import Ecto.Changeset
alias Mirage.Notes.{Topic, Tags, Note}
alias Mirage.Links.Link
schema "notes" do
field :title, :string
field :content, :string
@ -12,10 +9,10 @@ defmodule Mirage.Notes.Note do
field :views, :integer
field :viewed_at, :naive_datetime
many_to_many :links, Link, join_through: "notes_links"
many_to_many :topics, Topic, join_through: "notes_topics"
many_to_many :links, Mirage.Links.Link, join_through: "notes_links"
many_to_many :topics, Mirage.Topics.Topic, join_through: "notes_topics"
many_to_many :backlinks, Note,
many_to_many :backlinks, Mirage.Notes.Note,
join_through: "notes_notes",
join_keys: [target_id: :id, source_id: :id]
@ -32,16 +29,6 @@ defmodule Mirage.Notes.Note do
|> cast(attrs, [:content, :title, :views, :viewed_at])
|> validate_required([:content, :title])
|> Mirage.Markdown.maybe_render_markdown(:content, :content_html)
|> maybe_load_tag_string()
end
def maybe_load_tag_string(changeset) do
case get_field(changeset, :topic_string) do
"" ->
put_change(changeset, :topic_string, Tags.get_tags(changeset.data))
_ ->
changeset
end
|> Mirage.Tags.maybe_load_tag_string()
end
end

6
lib/mirage/notes/note_link.ex

@ -1,12 +1,10 @@
defmodule Mirage.Notes.NoteLink do
use Ecto.Schema
import Ecto.Changeset
alias Mirage.Notes.Note
alias Mirage.Links.Link
schema "notes_links" do
belongs_to :note, Note
belongs_to :link, Link
belongs_to :note, Mirage.Notes.Note
belongs_to :link, Mirage.Links.Link
end
@attrs [:note_id, :link_id]

5
lib/mirage/notes/note_note.ex

@ -1,11 +1,10 @@
defmodule Mirage.Notes.NoteNote do
use Ecto.Schema
import Ecto.Changeset
alias Mirage.Notes.Note
schema "notes_notes" do
belongs_to :source, Note
belongs_to :target, Note
belongs_to :source, Mirage.Notes.Note
belongs_to :target, Mirage.Notes.Note
end
@attrs [:source, :target]

5
lib/mirage/notes/note_topic.ex

@ -1,11 +1,10 @@
defmodule Mirage.Notes.NoteTopic do
use Ecto.Schema
import Ecto.Changeset
alias Mirage.Notes.{Note, Topic}
schema "notes_topics" do
belongs_to :note, Note
belongs_to :topic, Topic
belongs_to :note, Mirage.Notes.Note
belongs_to :topic, Mirage.Topics.Topic
end
@attrs [:note_id, :topic_id]

145
lib/mirage/notes/tags.ex

@ -1,145 +0,0 @@
defmodule Mirage.Notes.Tags do
import Ecto.Query, warn: false
alias Mirage.Repo
alias Mirage.Notes.{Note, Topic, NoteTopic}
def list_tags() do
Topic
|> Repo.all()
end
@doc utils: :tag
@doc """
Updates tags of a given note.
## Examples
iex> Tags.update_tags(note, "foo,bar")
"""
def update_tags(note, new_tags) when is_binary(new_tags) do
old_tags = tags_loaded(note) |> split_tags()
new_tags = new_tags |> split_tags()
note
|> add_tags(new_tags -- old_tags)
|> remove_tags(old_tags -- new_tags)
end
def update_tags(_, _), do: nil
@doc """
Retrieves a string of tags for a given note.
## Examples
iex> Tags.get_tags(note)
"foo,bar"
"""
def get_tags(note), do: tags_loaded(note)
@doc utils: :tag
defp add_tag(note, topic_text) when is_binary(topic_text) do
slug =
topic_text
|> Slugger.slugify()
|> String.downcase()
topic =
case Repo.get_by(Topic, %{text_slug: slug}) do
nil ->
%Topic{}
|> Topic.changeset(%{text: topic_text})
|> Repo.insert!()
topic ->
topic
end
add_tag(note, topic.id)
end
@doc utils: :tag
defp add_tag(%{note_id: note_id}, topic_id) do
add_tag(note_id, topic_id)
end
@doc utils: :tag
defp add_tag(%Note{} = note, topic_id) do
add_tag(note.id, topic_id)
end
@doc utils: :tag
defp add_tag(note_id, topic_id) do
%NoteTopic{}
|> NoteTopic.changeset(%{note_id: note_id, topic_id: topic_id})
|> Repo.insert()
end
@doc utils: :tag
defp add_tags(note, tags) when is_binary(tags) do
Enum.each(split_tags(tags), &add_tag(note, &1))
note
end
@doc utils: :tag
defp add_tags(note, tags) do
Enum.each(tags, &add_tag(note, &1))
note
end
@doc utils: :tag
defp remove_tag(note, topic_text) when is_binary(topic_text) do
case Repo.get_by(Topic, %{text: topic_text}) do
nil -> nil
topic -> remove_tag(note, topic.id)
end
end
@doc utils: :tag
defp remove_tag(%{note_id: note_id}, topic_id) do
remove_tag(note_id, topic_id)
end
@doc utils: :tag
defp remove_tag(%Note{} = note, topic_id) do
remove_tag(note.id, topic_id)
end
@doc utils: :tag
defp remove_tag(note_id, topic_id) do
case Repo.get_by(NoteTopic, %{note_id: note_id, topic_id: topic_id}) do
nil -> nil
tag -> Repo.delete(tag)
end
end
@doc utils: :tag
defp remove_tags(note, tags) when is_binary(tags) do
Enum.each(split_tags(tags), &remove_tag(note, &1))
note
end
@doc utils: :tag
defp remove_tags(note, tags) do
Enum.each(tags, &remove_tag(note, &1))
note
end
@doc utils: :tag
defp tags_loaded(%{topics: topics}) when is_list(topics) do
topics |> Enum.map_join(", ", & &1.text)
end
defp tags_loaded(_), do: ""
@doc utils: :tag
defp split_tags(tags_string) when is_binary(tags_string) do
tags_string
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.filter(&(String.length(&1) > 0))
end
end

208
lib/mirage/tags.ex

@ -0,0 +1,208 @@
defmodule Mirage.Tags do
require Logger
import Ecto.Query, warn: false
import Ecto.Changeset, only: [get_field: 2, put_change: 3]
alias Mirage.Repo
alias Mirage.Notes.{Note, NoteTopic}
alias Mirage.Links.{Link, LinkTopic}
alias Mirage.Topics.Topic
@doc """
Lists all existing tags
"""
def list_tags() do
Topic |> Repo.all()
end
def list_tags_as_string() do
list_tags()
|> Enum.map_join(",", fn tag -> tag.text end)
end
def tag_json_to_list(json_string) do
case json_string do
"" ->
json_string
json ->
json
|> Jason.decode!()
|> Enum.map(&Map.get(&1, "value"))
|> Enum.join(",")
end
end
@doc """
Changeset function to convert a topic string into a list of tags
"""
def maybe_load_tag_string(changeset) do
case get_field(changeset, :topic_string) do
"" ->
put_change(changeset, :topic_string, get_tags(changeset.data))
_ ->
changeset
end
end
@doc utils: :tag
@doc """
Updates tags of a given note.
## Examples
iex> Tags.update_tags(note, "foo,bar")
"""
def update_tags(schema, new_tags) when is_binary(new_tags) do
Logger.info("Updating tags")
Logger.info("Tags: #{inspect(new_tags)}")
Logger.info("Schema: #{inspect(schema)}")
old_tags = tags_loaded(schema) |> split_tags()
new_tags = new_tags |> split_tags()
schema
|> add_tags(new_tags -- old_tags)
|> remove_tags(old_tags -- new_tags)
end
def update_tags(_, _), do: nil
@doc """
Retrieves a string of tags for a given note.
## Examples
iex> Tags.get_tags(note)
"foo,bar"
"""
def get_tags(schema), do: tags_loaded(schema)
@doc utils: :tag
defp add_tag(schema, topic_text) when is_binary(topic_text) do
slug =
topic_text
|> Slugger.slugify()
|> String.downcase()
topic =
case Repo.get_by(Topic, %{text_slug: slug}) do
nil ->
%Topic{}
|> Topic.changeset(%{text: topic_text})
|> Repo.insert!()
topic ->
topic
end
add_tag(schema, topic.id)
end
# WHY is this needed?
# @doc utils: :tag
# defp add_tag(%{note_id: note_id}, topic_id) do
# add_tag(note_id, topic_id)
# end
@doc utils: :tag
defp add_tag(%Note{id: id}, topic_id), do: add_tag_for_note(id, topic_id)
@doc utils: :tag
defp add_tag(%Link{id: id}, topic_id), do: add_tag_for_link(id, topic_id)
@doc utils: :tag
defp add_tag_for_note(note_id, topic_id) do
Logger.info("Adding Tag for Note #{note_id}")
%NoteTopic{}
|> NoteTopic.changeset(%{note_id: note_id, topic_id: topic_id})
|> Repo.insert()
end
@doc utils: :tag
defp add_tag_for_link(link_id, topic_id) do
Logger.info("Adding Tag for Link #{link_id}")
%LinkTopic{}
|> LinkTopic.changeset(%{link_id: link_id, topic_id: topic_id})
|> Repo.insert()
end
@doc utils: :tag
defp add_tags(schema, tags) when is_binary(tags) do
Enum.each(split_tags(tags), &add_tag(schema, &1))
schema
end
@doc utils: :tag
defp add_tags(schema, tags) do
Enum.each(tags, &add_tag(schema, &1))
schema
end
@doc utils: :tag
defp remove_tag(note, topic_text) when is_binary(topic_text) do
case Repo.get_by(Topic, %{text: topic_text}) do
nil -> nil
topic -> remove_tag(note, topic.id)
end
end
# WHY is this needed???
# @doc utils: :tag
# defp remove_tag(%{note_id: note_id}, topic_id) do
# remove_tag(note_id, topic_id)
# end
@doc utils: :tag
defp remove_tag(%Note{} = note, topic_id), do: remove_tag_for_note(note.id, topic_id)
@doc utils: :tag
defp remove_tag(%Link{} = note, topic_id), do: remove_tag_for_link(note.id, topic_id)
@doc utils: :tag
defp remove_tag_for_note(note_id, topic_id) do
case Repo.get_by(NoteTopic, %{note_id: note_id, topic_id: topic_id}) do
nil -> nil
tag -> Repo.delete(tag)
end
end
@doc utils: :tag
defp remove_tag_for_link(link_id, topic_id) do
case Repo.get_by(LinkTopic, %{link_id: link_id, topic_id: topic_id}) do
nil -> nil
tag -> Repo.delete(tag)
end
end
@doc utils: :tag
defp remove_tags(note, tags) when is_binary(tags) do
Enum.each(split_tags(tags), &remove_tag(note, &1))
note
end
@doc utils: :tag
defp remove_tags(note, tags) do
Enum.each(tags, &remove_tag(note, &1))
note
end
@doc utils: :tag
defp tags_loaded(%{topics: topics}) when is_list(topics) do
topics |> Enum.map_join(", ", & &1.text)
end
defp tags_loaded(_), do: ""
@doc utils: :tag
defp split_tags(tags_string) when is_binary(tags_string) do
tags_string
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.filter(&(String.length(&1) > 0))
end
end

104
lib/mirage/topics.ex

@ -0,0 +1,104 @@
defmodule Mirage.Topics do
import Ecto.Query, warn: false
alias Mirage.Repo
alias Mirage.Topics.Topic
@topic_preloads [notes: [:links, :topics, :backlinks], links: []]
@doc """
Returns the list of topics.
## Examples
iex> list_topics()
[%Topic{}, ...]
"""
def list_topics do
Repo.all(Topic)
|> Repo.preload(@topic_preloads)
end
@doc """
Gets a single topic.
Raises `Ecto.NoResultsError` if the Note topic does not exist.
## Examples
iex> get_topic!(123)
%Topic{}
iex> get_topic!(456)
** (Ecto.NoResultsError)
"""
def get_topic!(id), do: Repo.get!(Topic, id)
def preload_topic(topic), do: Repo.preload(topic, @topic_preloads)
@doc """
Creates a topic.
## Examples
iex> create_topic(%{field: value})
{:ok, %Topic{}}
iex> create_topic(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_topic(attrs \\ %{}) do
%Topic{}
|> Topic.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a topic.
## Examples
iex> update_topic(topic, %{field: new_value})
{:ok, %Topic{}}
iex> update_topic(topic, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_topic(%Topic{} = topic, attrs) do
topic
|> Topic.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a topic.
## Examples
iex> delete_topic(topic)
{:ok, %Topic{}}
iex> delete_topic(topic)
{:error, %Ecto.Changeset{}}
"""
def delete_topic(%Topic{} = topic) do
Repo.delete(topic)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking topic changes.
## Examples
iex> change_topic(topic)
%Ecto.Changeset{data: %Topic{}}
"""
def change_topic(%Topic{} = topic, attrs \\ %{}) do
Topic.changeset(topic, attrs)
end
end

3
lib/mirage/notes/topic.ex → lib/mirage/topics/topic.ex

@ -1,4 +1,4 @@
defmodule Mirage.Notes.Topic do
defmodule Mirage.Topics.Topic do
use Ecto.Schema
import Ecto.Changeset
@ -9,6 +9,7 @@ defmodule Mirage.Notes.Topic do
field :icon, :string
many_to_many :notes, Mirage.Notes.Note, join_through: "notes_topics"
many_to_many :links, Mirage.Links.Link, join_through: "links_topics"
timestamps()
end

26
lib/mirage_web/controllers/topic_controller.ex

@ -1,23 +1,23 @@
defmodule MirageWeb.TopicController do
use MirageWeb, :controller
alias Mirage.Notes
alias Mirage.Notes.Topic
alias Mirage.Topics
alias Mirage.Topics.Topic
plug :require_authenticated_user when action in [:new, :edit, :update, :delete]
def index(conn, _params) do
topics = Notes.list_topics()
topics = Topics.list_topics()
render(conn, "index.html", topics: topics)
end
def new(conn, _params) do
changeset = Notes.change_topic(%Topic{})
changeset = Topics.change_topic(%Topic{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"topic" => topic_params}) do
case Notes.create_topic(topic_params) do
case Topics.create_topic(topic_params) do
{:ok, topic} ->
conn
|> put_flash(:info, "Note topic created successfully.")
@ -31,22 +31,22 @@ defmodule MirageWeb.TopicController do
def show(conn, %{"id" => id}) do
topic =
id
|> Notes.get_topic!()
|> Notes.preload_topic()
|> Topics.get_topic!()
|> Topics.preload_topic()
render(conn, "show.html", topic: topic)
end
def edit(conn, %{"id" => id}) do
topic = Notes.get_topic!(id)
changeset = Notes.change_topic(topic)
topic = Topics.get_topic!(id)
changeset = Topics.change_topic(topic)
render(conn, "edit.html", topic: topic, changeset: changeset)
end
def update(conn, %{"id" => id, "topic" => topic_params}) do
topic = Notes.get_topic!(id)
topic = Topics.get_topic!(id)
case Notes.update_topic(topic, topic_params) do
case Topics.update_topic(topic, topic_params) do
{:ok, topic} ->
conn
|> put_flash(:info, "Note topic updated successfully.")
@ -58,8 +58,8 @@ defmodule MirageWeb.TopicController do
end
def delete(conn, %{"id" => id}) do
topic = Notes.get_topic!(id)
{:ok, _topic} = Notes.delete_topic(topic)
topic = Topics.get_topic!(id)
{:ok, _topic} = Topics.delete_topic(topic)
conn
|> put_flash(:info, "Note topic deleted successfully.")

2
lib/mirage_web/live/goto_anything_live.ex

@ -17,7 +17,7 @@ defmodule MirageWeb.GotoAnythingLive do
end
@impl true
def handle_event("hotkey", params, socket) do
def handle_event("hotkey", _params, socket) do
{:noreply, socket}
end

22
lib/mirage_web/live/link_live/form_component.ex

@ -2,6 +2,7 @@ defmodule MirageWeb.LinkLive.FormComponent do
use MirageWeb, :live_component
alias Mirage.Links
alias Mirage.Tags
@impl true
def update(%{link: link} = assigns, socket) do
@ -10,7 +11,8 @@ defmodule MirageWeb.LinkLive.FormComponent do
{:ok,
socket
|> assign(assigns)
|> assign(:changeset, changeset)}
|> assign(:changeset, changeset)
|> assign(:tags, Mirage.Tags.list_tags_as_string())}
end
@impl true
@ -28,8 +30,15 @@ defmodule MirageWeb.LinkLive.FormComponent do
end
defp save_link(socket, :edit, link_params) do
tag_string =
link_params["topic_string"]
|> Tags.tag_json_to_list()
case Links.update_link(socket.assigns.link, link_params) do
{:ok, _link} ->
{:ok, link} ->
link
|> Tags.update_tags(tag_string)
{:noreply,
socket
|> put_flash(:info, "Note link updated successfully")
@ -41,8 +50,15 @@ defmodule MirageWeb.LinkLive.FormComponent do
end
defp save_link(socket, :new, link_params) do
tag_string =
link_params["topic_string"]
|> Tags.tag_json_to_list()
case Links.create_link(link_params) do
{:ok, _link} ->
{:ok, link} ->
link
|> Tags.update_tags(tag_string)
{:noreply,
socket
|> put_flash(:info, "Note link created successfully")

9
lib/mirage_web/live/link_live/form_component.html.leex

@ -4,7 +4,8 @@
id: "link-form",
phx_target: @myself,
phx_change: "validate",
phx_submit: "save" %>
phx_submit: "save",
phx_hook: "LinkForm" %>
<fieldset>
<%= label f, :title %>
@ -24,5 +25,11 @@
<%= error_tag f, :domain %>
</fieldset>
<fieldset phx-update="ignore">
<%= text_input f, :topic_string, id: "tags", placeholder: "Tags, comma-separated" %>
<%= hidden_input f, :all_tags, value: @tags, id: "all_tags" %>
<%= error_tag f, :topic_string %>
</fieldset>
<%= submit "Save", phx_disable_with: "Saving..." %>
</form>

13
lib/mirage_web/live/link_live/show.ex

@ -3,6 +3,9 @@ defmodule MirageWeb.LinkLive.Show do
alias Mirage.Links
defp page_title(:show), do: "Show Link"
defp page_title(:edit), do: "Edit Link"
@impl true
def mount(_params, session, socket) do
{:ok, socket |> with_user(session)}
@ -10,12 +13,14 @@ defmodule MirageWeb.LinkLive.Show do
@impl true
def handle_params(%{"id" => id}, _, socket) do
link =
id
|> Links.get_link!()
|> Links.preload_link()
{:noreply,
socket
|> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:link, Links.get_link!(id))}
|> assign(:link, link)}
end
defp page_title(:show), do: "Show Note link"
defp page_title(:edit), do: "Edit Note link"
end

5
lib/mirage_web/live/link_live/show.html.leex

@ -1,5 +1,10 @@
<header class="hero">
<h1><%= @link.title || @link.url %></h1>
<p class="tags">
<%= for topic <- @link.topics do %>
<span class="tag"><%= topic.text %></span>
<% end %>
</p>
</header>
<%= if @live_action in [:edit] do %>

29
lib/mirage_web/live/note_live/form_component.ex

@ -2,21 +2,17 @@ defmodule MirageWeb.NoteLive.FormComponent do
use MirageWeb, :live_component
alias Mirage.Notes
alias Mirage.Tags
@impl true
def update(%{note: note} = assigns, socket) do
changeset = Notes.change_note(note)
tags =
Mirage.Notes.Tags.list_tags()
|> Enum.map(fn tag -> tag.text end)
|> Enum.join(",")
{:ok,
socket
|> assign(assigns)
|> assign(:changeset, changeset)
|> assign(:tags, tags)}
|> assign(:tags, Mirage.Tags.list_tags_as_string())}
end
@impl true
@ -36,12 +32,12 @@ defmodule MirageWeb.NoteLive.FormComponent do
defp save_note(socket, :edit, note_params) do
tag_string =
note_params["topic_string"]
|> tag_json_to_list()
|> Tags.tag_json_to_list()
case Notes.update_note(socket.assigns.note, note_params) do
{:ok, note} ->
note
|> Notes.Tags.update_tags(tag_string)
|> Tags.update_tags(tag_string)
|> Notes.link_note()
{:noreply,
@ -57,13 +53,13 @@ defmodule MirageWeb.NoteLive.FormComponent do
defp save_note(socket, :new, note_params) do
tag_string =
note_params["topic_string"]
|> tag_json_to_list()
|> Tags.tag_json_to_list()
case Notes.create_note(note_params) do
{:ok, note} ->
note
|> Notes.preload_note()
|> Notes.Tags.update_tags(tag_string)
|> Tags.update_tags(tag_string)
|> Notes.link_note()
{:noreply,
@ -75,17 +71,4 @@ defmodule MirageWeb.NoteLive.FormComponent do
{:noreply, assign(socket, changeset: changeset)}
end
end
def tag_json_to_list(json_string) do
case json_string do
"" ->
json_string
json ->
json
|> Jason.decode!()
|> Enum.map(&Map.get(&1, "value"))
|> Enum.join(",")
end
end
end

2
lib/mirage_web/templates/topic/index.html.eex

@ -6,7 +6,7 @@
<%= for topic <- @topics do %>
<span class="tag count-<%= Enum.count(topic.notes) %>">
<%= link to: Routes.topic_path(@conn, :show, topic) do %>
<%= topic.text %> (<%= Enum.count(topic.notes) %>)
<%= topic.text %> (<%= Enum.count(topic.notes) + Enum.count(topic.links) %>)
<% end %>
</span>
<% end %>

16
lib/mirage_web/templates/topic/show.html.eex

@ -1,6 +1,6 @@
<header class="hero">
<h1>Topic <em><%= @topic.text %></em></h1>
<p><%= Enum.count(@topic.notes) %> Notes</p>
<p><%= Enum.count(@topic.notes) + Enum.count(@topic.links) %> Notes & Links</p>
</header>
<section>
@ -17,6 +17,20 @@
<% end %>
</section>
<section>
<%= for link <- @topic.links do %>
<article class="link single" id="link-<%= link.id %>">
<%= live_patch to: Routes.link_show_path(MirageWeb.Endpoint, :show, link) do %>
<header class="width-full">
<h2 class="title">
<%= link.title || link.url %>
</h2>
</header>
<% end %>
</article>
<% end %>
</section>
<div class="buttons">
<%= link "Edit", to: Routes.topic_path(@conn, :edit, @topic), class: "button" %>
<%= link "Back", to: Routes.topic_path(@conn, :index), class: "button" %>

2
mix.exs

@ -1,7 +1,7 @@
defmodule Mirage.MixProject do
use Mix.Project
@version "0.55.2"
@version "0.56.0"
def project do
[

13
priv/repo/migrations/20210223202011_create_link_topic.exs

@ -0,0 +1,13 @@
defmodule Mirage.Repo.Migrations.CreateLinkTopic do
use Ecto.Migration
def change do
create table(:links_topics) do
add :link_id, references(:links, on_delete: :delete_all)
add :topic_id, references(:topics, on_delete: :delete_all)
end
create index(:links_topics, [:link_id])
create index(:links_topics, [:topic_id])
end
end
Loading…
Cancel
Save