Skip to content

Commit

Permalink
fix child-node deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
sorax committed Sep 3, 2024
1 parent ea00a2d commit f7c39a8
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 103 deletions.
1 change: 1 addition & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ let liveSocket = new LiveSocket("/live", Socket, {
return {
key: e.key,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
repeat: e.repeat
}
}
Expand Down
52 changes: 17 additions & 35 deletions assets/js/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
// import { input } from "./events/listener/input";
// import { keydown } from "./events/listener";
// import {
// handleInsert,
// handleContentChange,
// handleDelete,
// handleClean,
// } from "./events/handler";
import { getNodeByItem } from "./node";
import { getItemById, setAttribute } from "./item";

export const Hooks = {
outline: {
selector: '.item:not([data-processed="true"]',
selector: '.node:not([data-processed="true"]',
mounted() {
// const container: HTMLDivElement = this.el.querySelector(".nodes");

// container.addEventListener("input", input.bind(this));

// container.addEventListener("keydown", keydown.bind(this));
// // container.addEventListener("keyup", keyup.bind(this))
const nodes = this.el.querySelectorAll(this.selector);
nodes.forEach((item: HTMLDivElement) => {
const { parent_id, prev_id } = getNodeByItem(item);
const parentNode = getItemById(parent_id);
const prevNode = getItemById(prev_id);

// this.handleEvent("insert", handleInsert.bind(this));
// this.handleEvent("change_content", handleContentChange.bind(this));
// this.handleEvent("delete", handleDelete.bind(this));
// this.handleEvent("clean", handleClean.bind(this));
if (prevNode) {
prevNode.after(item);
} else if (parentNode) {
parentNode.querySelector(".children")!.append(item);
}

const items = this.el.querySelectorAll(this.selector);
items.forEach((item: HTMLDivElement) => {
setAttribute(item, "processed", true);
});
},
updated() {
const nodes = this.el.querySelectorAll(this.selector);
nodes.forEach((item: HTMLDivElement) => {
const { parent_id, prev_id } = getNodeByItem(item);
const parentNode = getItemById(parent_id);
const prevNode = getItemById(prev_id);
Expand All @@ -41,18 +37,4 @@ export const Hooks = {
});
},
},
updated() {
// const items = this.el.querySelectorAll(this.selector);
// items.forEach((item: HTMLDivElement) => {
// const { parent_id, prev_id } = getNodeByItem(item);
// const parentNode = getItemById(parent_id);
// const prevNode = getItemById(prev_id);
// if (prevNode) {
// prevNode.after(item);
// } else if (parentNode) {
// parentNode.querySelector(".children")!.append(item);
// }
// setAttribute(item, "processed", true);
// });
},
};
83 changes: 66 additions & 17 deletions lib/radiator_web/live/outline_component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,43 @@ defmodule RadiatorWeb.OutlineComponent do
alias RadiatorWeb.OutlineComponents

@impl true
def update(%{event: %NodeInsertedEvent{node: %{uuid: uuid}}} = _event, socket) do
node = NodeRepository.get_node!(uuid)
def update(%{event: %NodeInsertedEvent{node: node, next_id: next_id}}, socket) do
next_node = NodeRepository.get_node!(next_id)

socket
|> stream_insert(:nodes, to_change_form(node, %{}))
|> stream_insert(:nodes, to_change_form(next_node, %{}))
|> reply(:ok)
end

def update(%{event: %NodeContentChangedEvent{node_id: uuid}} = _event, socket) do
node = NodeRepository.get_node!(uuid)
def update(%{event: %NodeContentChangedEvent{node_id: node_id, content: content}}, socket) do
node = NodeRepository.get_node!(node_id)

socket
|> stream_insert(:nodes, to_change_form(node, %{}))
|> stream_insert(:nodes, to_change_form(node, %{content: content}))
|> reply(:ok)
end

def update(%{event: %NodeMovedEvent{node_id: uuid}} = _event, socket) do
node = NodeRepository.get_node!(uuid)

def update(
%{event: %NodeMovedEvent{node_id: _node_id, parent_id: _parent_id, prev_id: _prev_id}},
socket
) do
socket
|> stream_insert(:nodes, to_change_form(node, %{}))
# |> stream_insert(:nodes, to_change_form(node, %{}))
|> reply(:ok)
end

def update(%{event: %NodeDeletedEvent{node_id: uuid}}, socket) do
node = %Node{uuid: uuid}
def update(
%{event: %NodeDeletedEvent{node_id: node_id, next_id: next_id, children: children}},
socket
) do
next_node = NodeRepository.get_node!(next_id)
children_forms = Enum.map(children, &to_change_form(&1, %{}))

socket
|> stream_delete(:nodes, to_change_form(node, %{}))
|> stream_delete_by_dom_id(:nodes, "nodes-form-#{node_id}")
|> stream_insert(:nodes, to_change_form(next_node, %{}))
|> stream(:nodes, children_forms)
|> reply(:ok)
end

Expand Down Expand Up @@ -89,19 +97,46 @@ defmodule RadiatorWeb.OutlineComponent do
|> reply(:noreply)
end

def handle_event(
"keydown",
%{"key" => "Tab", "shiftKey" => false, "uuid" => uuid, "prev" => prev_id},
socket
) do
socket
|> indent(uuid, prev_id)
|> reply(:noreply)
end

def handle_event("keydown", %{"key" => "Tab", "shiftKey" => false}, socket) do
socket
|> reply(:noreply)
end

def handle_event(
"keydown",
%{"key" => "Tab", "shiftKey" => true, "uuid" => uuid, "parent" => parent_id},
socket
) do
socket
|> outdent(uuid, parent_id)
|> reply(:noreply)
end

def handle_event("keydown", %{"key" => "Tab", "shiftKey" => true}, socket) do
socket
|> reply(:noreply)
end

def handle_event("keydown", _params, socket) do
socket
|> reply(:noreply)
end

def handle_event("save", %{"uuid" => uuid, "node" => params}, socket) do
node = NodeRepository.get_node!(uuid)

user_id = socket.assigns.user_id
Dispatch.change_node_content(uuid, params["content"], user_id, generate_event_id(socket.id))

socket
|> stream_insert(:nodes, to_change_form(node, params, :validate))
|> reply(:noreply)
end

Expand All @@ -120,12 +155,18 @@ defmodule RadiatorWeb.OutlineComponent do
"episode_id" => episode_id
}

new_node = %Node{uuid: new_uuid}
new_node = %Node{
uuid: new_uuid,
parent_id: node.parent_id,
prev_id: node.uuid,
creator_id: user_id,
episode_id: episode_id
}

Dispatch.insert_node(params, user_id, generate_event_id(socket.id))

socket
|> stream_insert(:nodes, to_change_form(new_node, params))
|> stream_insert(:nodes, to_change_form(new_node, %{}))
|> reply(:noreply)
end

Expand All @@ -139,4 +180,12 @@ defmodule RadiatorWeb.OutlineComponent do
end

defp generate_event_id(id), do: Ecto.UUID.generate() <> ":" <> id

defp indent(socket, _uuid, _prev_id) do
socket
end

defp outdent(socket, _uuid, _parent_id) do
socket
end
end
103 changes: 52 additions & 51 deletions lib/radiator_web/live/outline_component.html.heex
Original file line number Diff line number Diff line change
@@ -1,60 +1,61 @@
<div id={@id}>
<OutlineComponents.keyboard_shortcuts />

<div id={[@id, "-nodes"]} class="my-6" phx-hook="outline">
<div id={"#{@id}-stream"} phx-update="stream">
<div
:for={{id, form} <- @streams.nodes}
id={id}
class="relative group item"
data-parent={form.data.parent_id}
data-prev={form.data.prev_id}
data-collapsed={false}
<div id={[@id, "-stream"]} class="my-6" phx-hook="outline" phx-update="stream">
<div
:for={{id, form} <- @streams.nodes}
id={id}
class="relative group node"
data-parent={form.data.parent_id}
data-prev={form.data.prev_id}
data-collapsed={false}
>
<OutlineComponents.outline_form
for={form}
phx-change="save"
phx-submit="new"
phx-value-uuid={form.data.uuid}
phx-target={@myself}
class="ml-5"
>
<OutlineComponents.outline_form
for={form}
phx-change="save"
phx-submit="new"
<.input
type="text"
field={form[:content]}
placeholder="Content..."
phx-mounted={JS.focus()}
phx-focus="focus"
phx-blur="blur"
phx-keydown="keydown"
phx-value-uuid={form.data.uuid}
phx-value-parent={form.data.parent_id}
phx-value-prev={form.data.prev_id}
phx-target={@myself}
class="ml-5"
>
<.input
type="text"
field={form[:content]}
placeholder="Content..."
phx-focus="focus"
phx-blur="blur"
phx-keydown="keydown"
phx-value-uuid={form.data.uuid}
phx-target={@myself}
/>
</OutlineComponents.outline_form>
<div class="ml-4 peer children group-data-[collapsed]:hidden"></div>
<button
class="absolute peer-empty:hidden top-3 left-0 group-data-[collapsed]:-rotate-90 duration-200 z-10"
phx-click="toggle_collapse"
phx-value-uuid={form.data.uuid}
phx-target={@myself}
>
<svg width="20" height="20" viewBox="0 0 20 20" class="text-gray-500 rotate-90">
<path
d="M13.75 9.56879C14.0833 9.76124 14.0833 10.2424 13.75 10.4348L8.5 13.4659C8.16667 13.6584 7.75 13.4178 7.75 13.0329L7.75 6.97072C7.75 6.58582 8.16667 6.34525 8.5 6.5377L13.75 9.56879Z"
stroke="none"
fill="currentColor"
>
</path>
</svg>
</button>
<a
href={"##{form.data.uuid}"}
class="absolute left-0 hidden text-gray-500 peer-empty:block top-3"
>
<svg viewBox="0 0 18 18" fill="currentColor" class="w-5 h-5">
<circle cx="9" cy="9" r="3.5"></circle>
</svg>
</a>
</div>
/>
</OutlineComponents.outline_form>
<div class="ml-4 peer children group-data-[collapsed]:hidden"></div>
<button
class="absolute peer-empty:hidden top-3 left-0 group-data-[collapsed]:-rotate-90 duration-200 z-10"
phx-click="toggle_collapse"
phx-value-uuid={form.data.uuid}
phx-target={@myself}
>
<svg width="20" height="20" viewBox="0 0 20 20" class="text-gray-500 rotate-90">
<path
d="M13.75 9.56879C14.0833 9.76124 14.0833 10.2424 13.75 10.4348L8.5 13.4659C8.16667 13.6584 7.75 13.4178 7.75 13.0329L7.75 6.97072C7.75 6.58582 8.16667 6.34525 8.5 6.5377L13.75 9.56879Z"
stroke="none"
fill="currentColor"
>
</path>
</svg>
</button>
<a
href={"##{form.data.uuid}"}
class="absolute left-0 hidden text-gray-500 peer-empty:block top-3"
>
<svg viewBox="0 0 18 18" fill="currentColor" class="w-5 h-5">
<circle cx="9" cy="9" r="3.5"></circle>
</svg>
</a>
</div>
</div>
</div>

0 comments on commit f7c39a8

Please sign in to comment.