From 72f7eed8797f266e95c8dd8a5f4422c04903b35e Mon Sep 17 00:00:00 2001 From: sorax Date: Tue, 21 May 2024 21:56:02 +0200 Subject: [PATCH] fix: node insert --- assets/js/hooks/events/handler.ts | 6 +-- assets/js/hooks/events/listener.ts | 11 +++-- assets/js/hooks/item.ts | 14 ++---- assets/js/hooks/types.ts | 2 +- lib/radiator/outline.ex | 4 +- lib/radiator_web/live/episode_live/index.ex | 15 +++--- test/radiator/outline_test.exs | 52 +++++++++++++------- test/radiator_web/live/episode_live_test.exs | 32 +++++++----- 8 files changed, 79 insertions(+), 57 deletions(-) diff --git a/assets/js/hooks/events/handler.ts b/assets/js/hooks/events/handler.ts index a512160e..4c950bff 100644 --- a/assets/js/hooks/events/handler.ts +++ b/assets/js/hooks/events/handler.ts @@ -68,8 +68,6 @@ export function handleDelete({ node }: { node: Node }) { } export function handleClean({ node }: { node: Node }) { - const container: HTMLOListElement = this.el; - - node.dirty = false; - updateItem(node, container); + const item = getItemById(node.uuid) + item && setItemDirty(item, false); } diff --git a/assets/js/hooks/events/listener.ts b/assets/js/hooks/events/listener.ts index 307235c2..aae5c102 100644 --- a/assets/js/hooks/events/listener.ts +++ b/assets/js/hooks/events/listener.ts @@ -5,6 +5,7 @@ import { deleteItem, getItemByNode, focusItem, + setItemDirty, } from "../item"; import { getNodeByEvent, getNodeByItem } from "../node"; @@ -22,7 +23,9 @@ export function focusout(event: FocusEvent) { export function input(event: Event) { const node = getNodeByEvent(event); - node.dirty = true; + const item = getItemByNode(node); + item && setItemDirty(item, true); + this.pushEvent("update_node", node); } @@ -99,13 +102,12 @@ export function keydown(event: KeyboardEvent) { if (!prevItem || !prevNode) return; prevNode.content += node.content || ""; + prevNode.dirty = true; updateItem(prevNode, container); focusItem(prevItem); - prevNode.dirty = true; this.pushEvent("update_node", prevNode); deleteItem(node); - node.dirty = true; this.pushEvent("delete_node", node); break; @@ -116,13 +118,12 @@ export function keydown(event: KeyboardEvent) { if (!nextItem || !nextNode) return; node.content += nextNode.content || ""; + node.dirty = true; updateItem(node, container); focusItem(item); - node.dirty = true; this.pushEvent("update_node", node); deleteItem(nextNode); - nextNode.dirty = true; this.pushEvent("delete_node", nextNode); break; diff --git a/assets/js/hooks/item.ts b/assets/js/hooks/item.ts index d1c74311..8ef66ce4 100644 --- a/assets/js/hooks/item.ts +++ b/assets/js/hooks/item.ts @@ -10,11 +10,12 @@ export function createItem({ uuid, content, parent_id, prev_id, dirty }: Node) { const item = document.createElement("li"); item.id = "outline-node-" + uuid; - item.className = dirty ? "my-1 ml-4 bg-red-100" : "my-1 ml-4"; + item.className = "my-1 ml-4 data-[dirty=true]:bg-red-100"; item.setAttribute("data-parent", parent_id || ""); item.setAttribute("data-prev", prev_id || ""); - item.setAttribute("data-dirty", dirty ? "true" : "false"); + + setItemDirty(item, dirty) item.appendChild(input); item.appendChild(ol); @@ -27,17 +28,15 @@ export function updateItem( container: HTMLOListElement ) { const item = getItemById(uuid); - console.log({ item, uuid, content, parent_id, prev_id, dirty }); if (!item) return; const input = item.firstChild!; input.textContent = content || ""; - item.className = dirty ? "my-1 ml-4 bg-red-100" : "my-1 ml-4"; - item.setAttribute("data-parent", parent_id || ""); item.setAttribute("data-prev", prev_id || ""); - item.setAttribute("data-dirty", dirty ? "true" : "false"); + + setItemDirty(item, dirty) const prevItem = getItemById(prev_id); const parentItem = getItemById(parent_id); @@ -46,8 +45,6 @@ export function updateItem( prevItem.after(item); } else if (parentItem) { parentItem.querySelector("ol")?.append(item); - } else { - container.append(item); } } @@ -99,6 +96,5 @@ export function focusItem(item: HTMLLIElement, toEnd: boolean = true) { // } export function setItemDirty(item: HTMLLIElement, dirty: boolean) { - item.className = dirty ? "my-1 ml-4 bg-red-100" : "my-1 ml-4"; item.setAttribute("data-dirty", dirty ? "true" : "false"); } diff --git a/assets/js/hooks/types.ts b/assets/js/hooks/types.ts index ebe87604..14939e7c 100644 --- a/assets/js/hooks/types.ts +++ b/assets/js/hooks/types.ts @@ -6,5 +6,5 @@ export interface Node { creator_id?: number; parent_id?: UUID; prev_id?: UUID; - dirty?: boolean; + dirty: boolean; } diff --git a/lib/radiator/outline.ex b/lib/radiator/outline.ex index aa7449ed..adbc63b3 100644 --- a/lib/radiator/outline.ex +++ b/lib/radiator/outline.ex @@ -30,8 +30,8 @@ defmodule Radiator.Outline do # if no previous node is given, the new node will be inserted as the first child of the parent node def insert_node(attrs) do Repo.transaction(fn -> - prev_node_id = attrs["prev_node"] - parent_node_id = attrs["parent_node"] + prev_node_id = attrs["prev_id"] + parent_node_id = attrs["parent_id"] episode_id = attrs["episode_id"] # find Node which has been previously connected to prev_node node_to_move = diff --git a/lib/radiator_web/live/episode_live/index.ex b/lib/radiator_web/live/episode_live/index.ex index cf8dda68..9fc481f1 100644 --- a/lib/radiator_web/live/episode_live/index.ex +++ b/lib/radiator_web/live/episode_live/index.ex @@ -91,7 +91,6 @@ defmodule RadiatorWeb.EpisodeLive.Index do |> reply(:noreply) end - @impl true def handle_event("validate", %{"episode" => params}, socket) do changeset = socket.assigns.episode |> Episode.changeset(params) |> Map.put(:action, :validate) @@ -100,7 +99,6 @@ defmodule RadiatorWeb.EpisodeLive.Index do |> reply(:noreply) end - @impl true def handle_event("save", %{"episode" => params}, socket) do show_id = socket.assigns.show.id @@ -126,12 +124,15 @@ defmodule RadiatorWeb.EpisodeLive.Index do end @impl true - def handle_info( - %{node: node, event_id: <<_::binary-size(36)>> <> ":" <> id}, - %{id: id} = socket - ) do + def handle_info(%{event_id: <<_::binary-size(36)>> <> ":" <> id} = payload, %{id: id} = socket) do + id = + case payload do + %{node: %{uuid: id}} -> id + %{node_id: id} -> id + end + socket - |> push_event("clean", %{node: node}) + |> push_event("clean", %{node: %{uuid: id}}) |> reply(:noreply) end diff --git a/test/radiator/outline_test.exs b/test/radiator/outline_test.exs index e9b6561a..8d0beaf5 100644 --- a/test/radiator/outline_test.exs +++ b/test/radiator/outline_test.exs @@ -59,6 +59,24 @@ defmodule Radiator.OutlineTest do describe "insert_node/1" do setup :complex_node_fixture + test "node can be inserted after another node", %{node_3: node_3, node_4: node_4} do + node_attrs = %{ + "content" => "node 3.1", + "episode_id" => node_3.episode_id, + "parent_id" => node_3.parent_id, + "prev_id" => node_3.uuid + } + + assert {:ok, %Node{uuid: node3_1_uuid} = node} = Outline.insert_node(node_attrs) + + assert node.parent_id == node_3.parent_id + assert node.prev_id == node_3.uuid + + node4 = Repo.reload!(node_4) + + assert node4.prev_id == node3_1_uuid + end + test "creates a new node in the tree", %{ node_3: node_3, nested_node_1: nested_node_1 @@ -68,8 +86,8 @@ defmodule Radiator.OutlineTest do node_attrs = %{ "content" => "new node", "episode_id" => node_3.episode_id, - "parent_node" => node_3.uuid, - "prev_node" => nested_node_1.uuid + "parent_id" => node_3.uuid, + "prev_id" => nested_node_1.uuid } Outline.insert_node(node_attrs) @@ -84,8 +102,8 @@ defmodule Radiator.OutlineTest do node_attrs = %{ "content" => "new node", "episode_id" => node_3.episode_id, - "parent_node" => node_3.uuid, - "prev_node" => nested_node_1.uuid + "parent_id" => node_3.uuid, + "prev_id" => nested_node_1.uuid } {:ok, new_node} = Outline.insert_node(node_attrs) @@ -99,8 +117,8 @@ defmodule Radiator.OutlineTest do node_attrs = %{ "content" => "new node", "episode_id" => node_3.episode_id, - "parent_node" => node_3.uuid, - "prev_node" => nested_node_1.uuid + "parent_id" => node_3.uuid, + "prev_id" => nested_node_1.uuid } {:ok, new_node} = Outline.insert_node(node_attrs) @@ -115,8 +133,8 @@ defmodule Radiator.OutlineTest do node_attrs = %{ "content" => "new node", "episode_id" => node_3.episode_id, - "parent_node" => node_3.uuid, - "prev_node" => nested_node_1.uuid + "parent_id" => node_3.uuid, + "prev_id" => nested_node_1.uuid } {:ok, new_node} = Outline.insert_node(node_attrs) @@ -134,8 +152,8 @@ defmodule Radiator.OutlineTest do node_attrs = %{ "content" => "new node", "episode_id" => node_3.episode_id, - "parent_node" => node_3.uuid, - "prev_node" => nested_node_2.uuid + "parent_id" => node_3.uuid, + "prev_id" => nested_node_2.uuid } {:ok, new_node} = Outline.insert_node(node_attrs) @@ -153,7 +171,7 @@ defmodule Radiator.OutlineTest do node_attrs = %{ "content" => "new node", "episode_id" => node_3.episode_id, - "parent_node" => node_3.uuid + "parent_id" => node_3.uuid } {:ok, new_node} = Outline.insert_node(node_attrs) @@ -184,8 +202,8 @@ defmodule Radiator.OutlineTest do node_attrs = %{ "content" => "new node", "episode_id" => parent_node.episode_id, - "parent_node" => parent_node.uuid, - "prev_node" => nested_node_1.uuid + "parent_id" => parent_node.uuid, + "prev_id" => nested_node_1.uuid } {:error, "Insert node failed. Parent and prev node are not consistent."} = @@ -201,8 +219,8 @@ defmodule Radiator.OutlineTest do node_attrs = %{ "content" => "new node", "episode_id" => parent_node.episode_id, - "parent_node" => parent_node.uuid, - "prev_node" => bad_parent_node.uuid + "parent_id" => parent_node.uuid, + "prev_id" => bad_parent_node.uuid } {:error, _error_message} = @@ -218,8 +236,8 @@ defmodule Radiator.OutlineTest do node_attrs = %{ "content" => "new node", "episode_id" => parent_node.episode_id, - "parent_node" => parent_node.uuid, - "prev_node" => nested_node_1.uuid + "parent_id" => parent_node.uuid, + "prev_id" => nested_node_1.uuid } {:error, _error_message} = Outline.insert_node(node_attrs) diff --git a/test/radiator_web/live/episode_live_test.exs b/test/radiator_web/live/episode_live_test.exs index 410dc798..3b122fef 100644 --- a/test/radiator_web/live/episode_live_test.exs +++ b/test/radiator_web/live/episode_live_test.exs @@ -55,7 +55,7 @@ defmodule RadiatorWeb.EpisodeLiveTest do node_2 = node_fixture(%{episode_id: episode.id, prev_id: node_1.uuid}) _node_21 = node_fixture(%{episode_id: episode.id, parent_id: node_2.uuid}) - %{conn: log_in_user(conn, user), show: show, episode: episode} + %{conn: log_in_user(conn, user), show: show, episode: episode, nodes: [node_1, node_2]} end test "lists all nodes", %{conn: conn, show: show} do @@ -66,20 +66,28 @@ defmodule RadiatorWeb.EpisodeLiveTest do assert_push_event(live, "list", %{nodes: ^nodes}) end - # test "insert a new node", %{conn: conn, show: show} do - # {:ok, live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") - # {:ok, other_live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") + test "insert a new node", %{conn: conn, show: show, nodes: [node_1 | _]} do + {:ok, live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") + {:ok, other_live, _html} = live(conn, ~p"/admin/podcast/#{show.id}") + + uuid = Ecto.UUID.generate() - # uuid = Ecto.UUID.generate() - # event_id = Ecto.UUID.generate() + params = %{ + "uuid" => uuid, + "content" => "new node temp content", + # "parent_id" => nil, + "prev_id" => node_1.uuid + } - # params = %{"uuid" => uuid, "event_id" => event_id, "content" => "new node temp content"} - # assert live |> render_hook(:create_node, params) + assert live |> render_hook(:create_node, params) - # node = NodeRepository.get_node!(uuid) - # assert_push_event(live, "insert", %{node: ^node, event_id: ^event_id}) - # assert_push_event(other_live, "insert", %{node: ^node, event_id: ^event_id}) - # end + node = NodeRepository.get_node!(uuid) + assert node.parent_id == nil + assert node.prev_id == node_1.uuid + + assert_push_event(live, "clean", %{node: %{uuid: ^uuid}}) + assert_push_event(other_live, "insert", %{node: ^node}) + end # test "receive node inserted event after inserting a node", %{conn: conn, show: show} do # {:ok, live, _html} = live(conn, ~p"/admin/podcast/#{show.id}")