From 8441f3fa5b3b92619bb4f2d3d6c5d2bd2bfb8fa0 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Mon, 11 Nov 2024 15:44:51 +0100 Subject: [PATCH] add comments (#454) --- README.md | 3 +- assets/scss/_bootstrap_custom.scss | 2 +- lib/mindwendel/brainstormings.ex | 3 +- lib/mindwendel/brainstormings/comment.ex | 23 ++ lib/mindwendel/brainstormings/idea.ex | 6 +- lib/mindwendel/comments.ex | 122 +++++++++ lib/mindwendel/ideas.ex | 44 +++- .../live/brainstorming_live/show.ex | 29 +++ .../live/brainstorming_live/show.html.heex | 28 +- .../live/comment_live/form_component.ex | 119 +++++++++ .../comment_live/form_component.html.heex | 43 ++++ .../live/comment_live/show_component.ex | 19 ++ .../comment_live/show_component.html.heex | 49 ++++ .../live/idea_live/show_component.ex | 3 + .../live/idea_live/show_component.html.heex | 63 +++++ .../live/lane_live/index_component.ex | 4 - .../live/lane_live/index_component.html.heex | 17 +- lib/mindwendel_web/live/live_helpers.ex | 9 +- lib/mindwendel_web/router.ex | 11 +- priv/gettext/de/LC_MESSAGES/default.po | 82 ++++-- priv/gettext/default.pot | 80 ++++-- priv/gettext/en/LC_MESSAGES/default.po | 80 ++++-- .../migrate_add_position_order_to_ideas.exs | 64 ++++- .../20241025074220_add_comments_for_ideas.exs | 22 ++ test/mindwendel/brainstormings_test.exs | 19 ++ test/mindwendel/comments_test.exs | 30 +++ test/mindwendel/ideas_test.exs | 21 ++ .../show_idea_comment_test.exs | 243 ++++++++++++++++++ .../live/brainstorming_live_test.exs | 6 +- test/support/factory.ex | 5 + 30 files changed, 1160 insertions(+), 89 deletions(-) create mode 100644 lib/mindwendel/brainstormings/comment.ex create mode 100644 lib/mindwendel/comments.ex create mode 100644 lib/mindwendel_web/live/comment_live/form_component.ex create mode 100644 lib/mindwendel_web/live/comment_live/form_component.html.heex create mode 100644 lib/mindwendel_web/live/comment_live/show_component.ex create mode 100644 lib/mindwendel_web/live/comment_live/show_component.html.heex create mode 100644 lib/mindwendel_web/live/idea_live/show_component.ex create mode 100644 lib/mindwendel_web/live/idea_live/show_component.html.heex create mode 100644 priv/repo/migrations/20241025074220_add_comments_for_ideas.exs create mode 100644 test/mindwendel/comments_test.exs create mode 100644 test/mindwendel_web/live/brainstorming_live/show_idea_comment_test.exs diff --git a/README.md b/README.md index bea05be6..6c1be0af 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,13 @@ Create a challenge. Ready? Brainstorm. mindwendel helps you to easily brainstorm ## Features - 5 minute setup (It is not a joke) -- Anonymously invite people to your brainstormings - no registration needed. Usernames are optional +- Anonymously invite people to your brainstormings - no registration needed. Usernames are optional! - Easily create and upvote ideas, with live updates from your mindwendel members - Cluster or filter your ideas with custom labels - Preview of links to ease URL sharing - Add automatically encrypted file attachments which are uploaded to an S3 compatible storage backend - Add lanes, use drag & drop to order ideas +- Add comments to ideas - Export your generated ideas to html or csv (currently comma separated) - German & English Translation files - By default, brainstormings are deleted after 30 days to ensure GDPR compliancy diff --git a/assets/scss/_bootstrap_custom.scss b/assets/scss/_bootstrap_custom.scss index b794a1d1..f1ae4911 100644 --- a/assets/scss/_bootstrap_custom.scss +++ b/assets/scss/_bootstrap_custom.scss @@ -6,7 +6,7 @@ padding: 0.5rem 0.5rem; } -.card-footer-mindwendel-idea { +.card-footer-mindwendel { @extend .card-footer; padding: 0.5rem 0.5rem; background-color: inherit; diff --git a/lib/mindwendel/brainstormings.ex b/lib/mindwendel/brainstormings.ex index 56a9ca85..642778e8 100644 --- a/lib/mindwendel/brainstormings.ex +++ b/lib/mindwendel/brainstormings.ex @@ -308,7 +308,8 @@ defmodule Mindwendel.Brainstormings do :link, :likes, :idea_labels, - :files + :files, + :comments ]) } ) diff --git a/lib/mindwendel/brainstormings/comment.ex b/lib/mindwendel/brainstormings/comment.ex new file mode 100644 index 00000000..866207dd --- /dev/null +++ b/lib/mindwendel/brainstormings/comment.ex @@ -0,0 +1,23 @@ +defmodule Mindwendel.Brainstormings.Comment do + use Mindwendel.Schema + + import Ecto.Changeset + alias Mindwendel.Brainstormings.Idea + alias Mindwendel.Accounts.User + + schema "idea_comments" do + belongs_to :idea, Idea + belongs_to :user, User + field :body, :string + field :username, :string, default: "Anonymous" + + timestamps() + end + + @doc false + def changeset(comment, attrs) do + comment + |> cast(attrs, [:idea_id, :user_id, :body, :username]) + |> validate_required([:idea_id, :body, :username]) + end +end diff --git a/lib/mindwendel/brainstormings/idea.ex b/lib/mindwendel/brainstormings/idea.ex index 1b742216..6c208bb7 100644 --- a/lib/mindwendel/brainstormings/idea.ex +++ b/lib/mindwendel/brainstormings/idea.ex @@ -7,6 +7,7 @@ defmodule Mindwendel.Brainstormings.Idea do alias Mindwendel.Brainstormings.IdeaIdeaLabel alias Mindwendel.Brainstormings.Like alias Mindwendel.Brainstormings.Lane + alias Mindwendel.Brainstormings.Comment alias Mindwendel.Ideas alias Mindwendel.Attachments alias Mindwendel.Attachments.Link @@ -21,10 +22,12 @@ defmodule Mindwendel.Brainstormings.Idea do field :body, :string field :position_order, :integer field :username, :string, default: "Anonymous" + field :comments_count, :integer field :deprecated_label, Ecto.Enum, source: :label, values: @label_values has_one :link, Link belongs_to :user, User has_many :likes, Like + has_many :comments, Comment, preload_order: [desc: :inserted_at] has_many :files, File belongs_to :brainstorming, Brainstorming belongs_to :label, IdeaLabel, on_replace: :nilify @@ -45,7 +48,8 @@ defmodule Mindwendel.Brainstormings.Idea do :deprecated_label, :label_id, :user_id, - :position_order + :position_order, + :comments_count ]) |> validate_required([:username, :body, :brainstorming_id]) |> maybe_put_idea_labels(attrs) diff --git a/lib/mindwendel/comments.ex b/lib/mindwendel/comments.ex new file mode 100644 index 00000000..2e60bbeb --- /dev/null +++ b/lib/mindwendel/comments.ex @@ -0,0 +1,122 @@ +defmodule Mindwendel.Comments do + import Ecto.Query, warn: false + alias Mindwendel.Repo + alias Mindwendel.Brainstormings.Comment + alias Mindwendel.Brainstormings + alias Mindwendel.Ideas + + require Logger + + @doc """ + Gets a single comment + + ## Examples + + iex> get_comment!("0323906b-b496-4778-ae67-1dd779d3de3c") + %Comment{ ... } + + """ + def get_comment!(id) do + Repo.get!(Comment, id) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking comment changes. + + ## Examples + + iex> change_comment(comment) + %Ecto.Changeset(data: %Comment{}) + + """ + def change_comment(%Comment{} = comment, attrs \\ %{}) do + Comment.changeset(comment, attrs) + end + + @doc """ + Creates a comment. + + ## Examples + + iex> create_comment(%{field: value}) + {:ok, %Comment{}} + + iex> create_comment(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_comment(attrs \\ %{}) do + result = + %Comment{} + |> Comment.changeset(attrs) + |> Repo.insert() + + case result do + {:ok, comment} -> Ideas.increment_comment_count(comment.idea_id) + {:error, _} -> nil + end + + handle_result_for_broadcast(result) + result + end + + @doc """ + Updates a comment. + + ## Examples + + iex> update_comment(comment, %{field: new_value}) + {:ok, %Comment{}} + + iex> update_comment(comment, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_comment(%Comment{} = comment, attrs) do + result = + comment + |> Comment.changeset(attrs) + |> Repo.update() + + handle_result_for_broadcast(result) + result + end + + @doc """ + Deletes a comment. + + ## Examples + + iex> delete_comment(comment) + {:ok, %Comment{}} + + iex> delete_comment(comment) + {:error, %Ecto.Changeset{}} + + """ + def delete_comment(%Comment{} = comment) do + result = Repo.delete(comment) + + case result do + {:ok, comment} -> Ideas.decrement_comment_count(comment.idea_id) + end + + handle_result_for_broadcast(result) + result + end + + defp handle_result_for_broadcast(result) do + case result do + {:ok, comment} -> + idea = Ideas.get_idea!(comment.idea_id) + + Brainstormings.broadcast( + {:ok, idea}, + :idea_updated + ) + + {:error, changeset} -> + {:error, changeset} + end + end +end diff --git a/lib/mindwendel/ideas.ex b/lib/mindwendel/ideas.ex index f62b75f3..d581ee7e 100644 --- a/lib/mindwendel/ideas.ex +++ b/lib/mindwendel/ideas.ex @@ -88,7 +88,8 @@ defmodule Mindwendel.Ideas do :link, :likes, :label, - :idea_labels + :idea_labels, + :comments ]) end @@ -283,7 +284,8 @@ defmodule Mindwendel.Ideas do ** (Ecto.NoResultsError) """ - def get_idea!(id), do: Repo.get!(Idea, id) |> Repo.preload([:idea_labels, :files]) + def get_idea!(id), + do: Repo.get!(Idea, id) |> Repo.preload([:idea_labels, :files, :link, :comments]) @doc """ Creates a idea. @@ -355,6 +357,42 @@ defmodule Mindwendel.Ideas do result end + @doc """ + Increments the comment count of an idea. + + ## Examples + + iex> increment_comment_count(idea_id) + {:ok, %Idea{}} + + iex> increment_comment_count(idea_id) + {:error, %Ecto.Changeset{}} + + """ + def increment_comment_count(idea_id) do + idea = Repo.get!(Idea, idea_id) + changeset = Idea.changeset(idea, %{comments_count: idea.comments_count + 1}) + Repo.update(changeset) + end + + @doc """ + Decrements the comment count of an idea. + + ## Examples + + iex> decrement_comment_count(idea_id) + {:ok, %Idea{}} + + iex> decrement_comment_count(idea_id) + {:error, %Ecto.Changeset{}} + + """ + def decrement_comment_count(idea_id) do + idea = Repo.get!(Idea, idea_id) + changeset = Idea.changeset(idea, %{comments_count: idea.comments_count - 1}) + Repo.update(changeset) + end + @doc """ Deletes a idea. @@ -389,6 +427,6 @@ defmodule Mindwendel.Ideas do """ def change_idea(%Idea{} = idea, attrs \\ %{}) do - Repo.preload(idea, [:link, :idea_labels]) |> Idea.changeset(attrs) + Repo.preload(idea, [:link, :idea_labels, :comments, :files]) |> Idea.changeset(attrs) end end diff --git a/lib/mindwendel_web/live/brainstorming_live/show.ex b/lib/mindwendel_web/live/brainstorming_live/show.ex index e5b6360f..34cee437 100644 --- a/lib/mindwendel_web/live/brainstorming_live/show.ex +++ b/lib/mindwendel_web/live/brainstorming_live/show.ex @@ -67,6 +67,26 @@ defmodule MindwendelWeb.BrainstormingLive.Show do {:noreply, assign(socket, :lanes, lanes)} end + def handle_info({:lane_updated, lane}, socket) do + new_lanes = + Enum.map(socket.assigns.lanes, fn existing_lane -> + if lane.id == existing_lane.id, do: lane, else: existing_lane + end) + + {:noreply, assign(socket, :lanes, new_lanes)} + end + + # idea updated - only relevant if the show modal of the idea is opened + def handle_info({:idea_updated, idea}, socket) do + if socket.assigns.live_action == :show_idea and socket.assigns.idea.id == idea.id do + # update the idea modal with new content + send_update(MindwendelWeb.IdeaLive.ShowComponent, id: :show, idea: idea) + {:noreply, socket} + else + {:noreply, socket} + end + end + def handle_info({:brainstorming_filter_updated, brainstorming, lanes}, socket) do {:noreply, push_patch( @@ -102,6 +122,15 @@ defmodule MindwendelWeb.BrainstormingLive.Show do |> assign(:idea, Ideas.get_idea!(idea_id)) end + defp apply_action( + socket, + :show_idea, + %{"brainstorming_id" => _brainstorming_id, "idea_id" => idea_id} + ) do + socket + |> assign(:idea, Ideas.get_idea!(idea_id)) + end + defp apply_action( socket, :edit_lane, diff --git a/lib/mindwendel_web/live/brainstorming_live/show.html.heex b/lib/mindwendel_web/live/brainstorming_live/show.html.heex index 0c2ec320..6b2c68be 100644 --- a/lib/mindwendel_web/live/brainstorming_live/show.html.heex +++ b/lib/mindwendel_web/live/brainstorming_live/show.html.heex @@ -21,7 +21,7 @@
<%= if has_moderating_permission(@brainstorming, @current_user) do %> <.link - patch={~p"/brainstormings/#{@brainstorming.id}/show/new_lane"} + patch={~p"/brainstormings/#{@brainstorming.id}/new_lane"} class="btn btn-primary m-1 d-inline-flex align-items-center" title={gettext("New lane")} > @@ -30,7 +30,7 @@ <% end %> <.link - patch={~p"/brainstormings/#{@brainstorming.id}/show/share"} + patch={~p"/brainstormings/#{@brainstorming.id}/share"} class="btn btn-secondary m-1" title={gettext("Share")} > @@ -73,31 +73,33 @@ /> <.modal - :if={@live_action in [:edit]} - id="brainstorming-modal" + :if={@live_action in [:new_idea]} + id="idea-modal" show on_cancel={JS.patch(~p"/brainstormings/#{@brainstorming.id}")} - title={@page_title} + title={gettext("New idea")} > <.live_component - module={MindwendelWeb.BrainstormingLive.FormComponent} - id={@brainstorming.id} - action={@live_action} + module={MindwendelWeb.IdeaLive.FormComponent} + id={:new} + action={:new} brainstorming={@brainstorming} + current_user={@current_user} + idea={@idea} /> <.modal - :if={@live_action in [:new_idea]} + :if={@live_action in [:show_idea]} id="idea-modal" show on_cancel={JS.patch(~p"/brainstormings/#{@brainstorming.id}")} - title={gettext("New idea")} + title={gettext("Show idea")} > <.live_component - module={MindwendelWeb.IdeaLive.FormComponent} - id={:new} - action={:new} + module={MindwendelWeb.IdeaLive.ShowComponent} + id={:show} + action={:show} brainstorming={@brainstorming} current_user={@current_user} idea={@idea} diff --git a/lib/mindwendel_web/live/comment_live/form_component.ex b/lib/mindwendel_web/live/comment_live/form_component.ex new file mode 100644 index 00000000..65ef1a0a --- /dev/null +++ b/lib/mindwendel_web/live/comment_live/form_component.ex @@ -0,0 +1,119 @@ +defmodule MindwendelWeb.CommentLive.FormComponent do + use MindwendelWeb, :live_component + alias Mindwendel.Comments + alias Mindwendel.Brainstormings.Comment + + @impl true + def update( + %{comment: comment} = + assigns, + socket + ) do + {:ok, + socket + |> assign(assigns) + |> assign_new(:form, fn -> + to_form(Comments.change_comment(comment)) + end)} + end + + def update( + %{idea: idea, current_user: current_user} = assigns, + socket + ) do + comment = %Comment{ + idea_id: idea.id, + username: current_user.username + } + + {:ok, + socket + |> assign(assigns) + |> assign_new(:comment, fn -> comment end) + |> assign_new(:form, fn -> + to_form(Comments.change_comment(comment)) + end)} + end + + @impl true + def handle_event("close", _, socket) do + # The close button is either pressed inside the comment component, where a comment might be edited, or inside the "new comment" form. + # Depending on the location, either patch back to the brainstorming or simply change back to view mode inside the comment. + %{brainstorming: brainstorming, comment: comment} = socket.assigns + + case socket.assigns.action do + :new -> + {:noreply, + push_patch( + socket + |> assign(:brainstorming, brainstorming), + to: "/brainstormings/#{brainstorming.id}" + )} + + :update -> + send_update(MindwendelWeb.CommentLive.ShowComponent, id: comment.id, live_action: :show) + {:noreply, socket} + end + end + + def handle_event("validate", %{"comment" => comment_params}, socket) do + changeset = Comments.change_comment(socket.assigns.comment, comment_params) + + {:noreply, assign(socket, form: to_form(changeset, action: :validate))} + end + + def handle_event("save", %{"comment" => comment_params}, socket) do + save_comment(socket, socket.assigns.action, comment_params) + end + + defp save_comment(socket, :update, comment_params) do + %{current_user: current_user, comment: comment, brainstorming: brainstorming} = socket.assigns + + if has_moderating_or_ownership_permission(brainstorming, comment, current_user) do + comment_params_merged = + comment_params + |> Map.put("user_id", comment.user_id || current_user.id) + |> Map.put("idea_id", socket.assigns.idea.id) + + case Comments.update_comment( + comment, + comment_params_merged + ) do + {:ok, _comment} -> + {:noreply, + socket + |> put_flash(:info, gettext("Comment updated"))} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, form: to_form(changeset))} + end + else + {:noreply, socket} + end + end + + defp save_comment(socket, :new, comment_params) do + comment_params_merged = + comment_params + |> Map.put("user_id", socket.assigns.current_user.id) + |> Map.put("idea_id", socket.assigns.idea.id) + + case Comments.create_comment(comment_params_merged) do + {:ok, _comment} -> + # reset the form + new_comment = %Comment{ + idea_id: socket.assigns.idea.id, + username: socket.assigns.current_user.username + } + + {:noreply, + socket + |> assign(comment: new_comment) + |> assign(form: to_form(Comments.change_comment(new_comment))) + |> put_flash(:info, gettext("Comment created successfully"))} + + {:error, %Ecto.Changeset{} = changeset} -> + {:noreply, assign(socket, form: to_form(changeset))} + end + end +end diff --git a/lib/mindwendel_web/live/comment_live/form_component.html.heex b/lib/mindwendel_web/live/comment_live/form_component.html.heex new file mode 100644 index 00000000..8638ae3d --- /dev/null +++ b/lib/mindwendel_web/live/comment_live/form_component.html.heex @@ -0,0 +1,43 @@ +
+ <.simple_form + for={@form} + id={"comment-form-#{@comment.id || :new}"} + phx-target={@myself} + phx-change="validate" + phx-submit="save" + > + <.input + field={@form[:username]} + type="text" + label={gettext("Username")} + phx-debounce={300} + id={"comment-form-username-#{@comment.id || :new}"} + /> + <.input + field={@form[:body]} + type="textarea" + id={"comment-form-body-#{@comment.id || :new}"} + label={gettext("Your comment")} + phx-debounce={300} + /> + <.input field={@form[:id]} type="hidden" id={"comment-form-id-#{@comment.id || :new}"} /> + <.input + field={@form[:idea_id]} + type="hidden" + id={"comment-form-idea-id-#{@comment.id || :new}"} + /> + <:actions> + <.link + phx-click={:close} + phx-target={@myself} + class="btn btn-secondary form-cancel me-2" + title={gettext("Close")} + > + <%= gettext("Close") %> + + <.button class="btn-primary" phx-disable-with={gettext("Saving...")}> + <%= gettext("Save") %> + + + +
diff --git a/lib/mindwendel_web/live/comment_live/show_component.ex b/lib/mindwendel_web/live/comment_live/show_component.ex new file mode 100644 index 00000000..758b4130 --- /dev/null +++ b/lib/mindwendel_web/live/comment_live/show_component.ex @@ -0,0 +1,19 @@ +defmodule MindwendelWeb.CommentLive.ShowComponent do + use MindwendelWeb, :live_component + alias Mindwendel.Comments + + @impl true + def handle_event("edit_comment", _value, socket) do + {:noreply, assign(socket, :live_action, :edit)} + end + + def handle_event("delete_comment", _, socket) do + %{brainstorming: brainstorming, comment: comment, current_user: current_user} = socket.assigns + + if has_moderating_or_ownership_permission(brainstorming, comment, current_user) do + Comments.delete_comment(socket.assigns.comment) + end + + {:noreply, socket} + end +end diff --git a/lib/mindwendel_web/live/comment_live/show_component.html.heex b/lib/mindwendel_web/live/comment_live/show_component.html.heex new file mode 100644 index 00000000..02246ed8 --- /dev/null +++ b/lib/mindwendel_web/live/comment_live/show_component.html.heex @@ -0,0 +1,49 @@ +
+
+ <%= if @live_action == :edit do %> + <.live_component + module={MindwendelWeb.CommentLive.FormComponent} + id={"comment-form-#{@comment.id}"} + brainstorming={@brainstorming} + current_user={@current_user} + comment={@comment} + idea={@idea} + action={:update} + /> + <% else %> +
+ <%= if has_moderating_or_ownership_permission(@brainstorming, @comment, @current_user) do %> + <.link + class="float-end ms-3 mb-3" + phx-click="delete_comment" + phx-target={@myself} + phx-value-id={@comment.id} + title={gettext("Delete comment")} + data-confirm={gettext("Are you sure you want to delete this idea?")} + > + + + <.link + phx-click="edit_comment" + phx-target={@myself} + class="float-end ms-3 mb-3" + title={gettext("Delete idea")} + > + + + <% end %> +
+ <%= @comment.body %> + + + <% end %> +
+
diff --git a/lib/mindwendel_web/live/idea_live/show_component.ex b/lib/mindwendel_web/live/idea_live/show_component.ex new file mode 100644 index 00000000..a2435454 --- /dev/null +++ b/lib/mindwendel_web/live/idea_live/show_component.ex @@ -0,0 +1,3 @@ +defmodule MindwendelWeb.IdeaLive.ShowComponent do + use MindwendelWeb, :live_component +end diff --git a/lib/mindwendel_web/live/idea_live/show_component.html.heex b/lib/mindwendel_web/live/idea_live/show_component.html.heex new file mode 100644 index 00000000..e769f26e --- /dev/null +++ b/lib/mindwendel_web/live/idea_live/show_component.html.heex @@ -0,0 +1,63 @@ +
+ <%= unless @idea.link do %> +

<%= raw(@idea.body) %>

+ <% end %> + + <%= if @idea.link do %> + <.link href={@idea.link.url}> + <%= raw(@idea.body) %> + +
+
+
+ +
+
+

<%= @idea.link.title %>

+

<%= @idea.link.description %>

+
+
+ <% end %> + + <%= if length(@idea.files) > 0 do %> + <%= for attachment <- @idea.files do %> + +

+ <.link href={~p"/files/#{attachment.id}"} download={attachment.name}> + <%= attachment.name || gettext("No filename") %> + +

+ <% end %> + <% end %> + +
+

<%= gettext("Comments") %>

+ <%= for comment <- @idea.comments do %> + <.live_component + module={MindwendelWeb.CommentLive.ShowComponent} + id={comment.id} + comment={comment} + idea={@idea} + brainstorming={@brainstorming} + current_user={@current_user} + live_action={:show} + /> + <% end %> + <%= if length(@idea.comments) == 0 do %> + <%= gettext("No comments available") %> + <% end %> +
+ +
+

<%= gettext("Add a new comment") %>

+ + <.live_component + module={MindwendelWeb.CommentLive.FormComponent} + id={:new} + brainstorming={@brainstorming} + current_user={@current_user} + idea={@idea} + action={:new} + /> +
+
diff --git a/lib/mindwendel_web/live/lane_live/index_component.ex b/lib/mindwendel_web/live/lane_live/index_component.ex index 6821e3c3..b8602112 100644 --- a/lib/mindwendel_web/live/lane_live/index_component.ex +++ b/lib/mindwendel_web/live/lane_live/index_component.ex @@ -22,7 +22,6 @@ defmodule MindwendelWeb.LaneLive.IndexComponent do {:noreply, socket} end - @impl true def handle_event("delete_lane", %{"id" => id}, socket) do lane = Lanes.get_lane!(id) @@ -36,14 +35,12 @@ defmodule MindwendelWeb.LaneLive.IndexComponent do {:noreply, socket} end - @impl true def handle_event("like", %{"id" => id}, socket) do Likes.add_like(id, socket.assigns.current_user.id) {:noreply, socket} end - @impl true def handle_event("unlike", %{"id" => id}, socket) do Likes.delete_like(id, socket.assigns.current_user.id) @@ -131,7 +128,6 @@ defmodule MindwendelWeb.LaneLive.IndexComponent do end end - @impl true def handle_event("sort_by_likes", %{"id" => id, "lane-id" => lane_id}, socket) do brainstorming = Brainstormings.get_brainstorming!(id) diff --git a/lib/mindwendel_web/live/lane_live/index_component.html.heex b/lib/mindwendel_web/live/lane_live/index_component.html.heex index a5f06ef4..aca3f900 100644 --- a/lib/mindwendel_web/live/lane_live/index_component.html.heex +++ b/lib/mindwendel_web/live/lane_live/index_component.html.heex @@ -8,7 +8,7 @@ <.link class="btn btn-primary btn-sm d-inline-flex align-items-center" title={gettext("Add idea")} - patch={~p"/brainstormings/#{@brainstorming.id}/show/lanes/#{lane.id}/new_idea"} + patch={~p"/brainstormings/#{@brainstorming.id}/lanes/#{lane.id}/new_idea"} > <%= gettext("Add idea") %> @@ -122,6 +122,13 @@ <% end %> + <.link + patch={~p"/brainstormings/#{@brainstorming.id}/ideas/#{idea.id}"} + class="float-end ms-3 mb-3" + title={gettext("Show idea")} + > + + <%= for idea_label <- Enum.sort_by(idea.idea_labels, &(&1.position_order)) do %>

- <.link href={~p"/files/#{attachment.id}"}> + <.link href={~p"/files/#{attachment.id}"} download={attachment.name}> <%= attachment.name || gettext("No filename") %>

@@ -167,7 +174,7 @@ <% end %>
-