diff --git a/assets/js/hooks/events/handler.ts b/assets/js/hooks/events/handler.ts
index 7a1e140b..cccd08b7 100644
--- a/assets/js/hooks/events/handler.ts
+++ b/assets/js/hooks/events/handler.ts
@@ -1,16 +1,15 @@
import { Node } from "../types";
import {
- getItemByNode,
+ changeItemContent,
+ cleanItem,
createItem,
- updateItem,
deleteItem,
focusItem,
- getItemById,
- setItemDirty,
+ moveItem,
} from "../item";
export function handleList({ nodes }: { nodes: Node[] }) {
- const container: HTMLOListElement = this.el;
+ const container: HTMLDivElement = this.el.querySelector(".children");
if (nodes.length == 0) {
const node: Node = {
@@ -29,33 +28,24 @@ export function handleList({ nodes }: { nodes: Node[] }) {
// sort & indent all items
nodes.forEach((node) => {
- updateItem(node, container);
+ moveItem(node, container, true);
});
// focus last item
- const lastItem = container.lastElementChild as HTMLLIElement;
+ const lastItem = container.lastElementChild as HTMLDivElement;
focusItem(lastItem);
}
export function handleInsert({ node }: { node: Node }) {
- const container: HTMLOListElement = this.el;
+ const container: HTMLDivElement = this.el.querySelector(".children");
const item = createItem(node);
container.append(item);
- updateItem(node, container);
+ moveItem(node, container);
}
export function handleContentChange({ node }: { node: Node }) {
- const item = getItemById(node.uuid);
- if (!item) {
- console.error("item not found");
- return;
- }
-
- const input = item.firstChild!;
- input.textContent = node.content || "";
-
- setItemDirty(item, false);
+ changeItemContent(node);
}
export function handleDelete({ node }: { node: Node }) {
@@ -63,6 +53,5 @@ export function handleDelete({ node }: { node: Node }) {
}
export function handleClean({ node }: { node: Node }) {
- const item = getItemById(node.uuid)
- item && setItemDirty(item, false);
+ cleanItem(node);
}
diff --git a/assets/js/hooks/events/listener.ts b/assets/js/hooks/events/listener.ts
index ebb46ba1..d2eaf413 100644
--- a/assets/js/hooks/events/listener.ts
+++ b/assets/js/hooks/events/listener.ts
@@ -1,10 +1,11 @@
import { Node } from "../types";
import {
+ changeItemContent,
createItem,
- updateItem,
deleteItem,
- getItemByNode,
focusItem,
+ getItemByNode,
+ moveItem,
setItemDirty,
} from "../item";
import { getNodeByEvent, getNodeByItem } from "../node";
@@ -30,7 +31,7 @@ export function input(event: Event) {
}
export function keydown(event: KeyboardEvent) {
- const container: HTMLOListElement = this.el;
+ const container: HTMLDivElement = this.el.querySelector(".children");
const selection = window.getSelection();
// const range = selection?.getRangeAt(0)
@@ -38,8 +39,8 @@ export function keydown(event: KeyboardEvent) {
const node = getNodeByEvent(event);
const item = getItemByNode(node)!;
- const prevItem = item.previousSibling as HTMLLIElement | null;
- const nextItem = item.nextSibling as HTMLLIElement | null;
+ const prevItem = item.previousSibling as HTMLDivElement | null;
+ const nextItem = item.nextSibling as HTMLDivElement | null;
const prevNode = prevItem && getNodeByItem(prevItem);
const nextNode = nextItem && getNodeByItem(nextItem);
@@ -76,7 +77,7 @@ export function keydown(event: KeyboardEvent) {
node.content = content?.substring(0, splitPos);
node.dirty = true;
- updateItem(node, container);
+ changeItemContent(node);
this.pushEvent("update_node_content", node);
const newNode: Node = {
@@ -86,9 +87,7 @@ export function keydown(event: KeyboardEvent) {
prev_id: node.uuid,
dirty: true,
};
-
this.pushEvent("create_node", newNode);
- // this.pushEvent("create_node", newNode, (node: Node, _ref: number) => { });
const newItem = createItem(newNode);
item.after(newItem);
@@ -103,7 +102,7 @@ export function keydown(event: KeyboardEvent) {
prevNode.content += node.content || "";
prevNode.dirty = true;
- updateItem(prevNode, container);
+ changeItemContent(prevNode);
focusItem(prevItem);
this.pushEvent("update_node_content", prevNode);
@@ -119,7 +118,7 @@ export function keydown(event: KeyboardEvent) {
node.content += nextNode.content || "";
node.dirty = true;
- updateItem(node, container);
+ changeItemContent(node);
focusItem(item);
this.pushEvent("update_node_content", node);
@@ -135,9 +134,9 @@ export function keydown(event: KeyboardEvent) {
if (node.parent_id) {
node.prev_id = node.parent_id;
node.parent_id = prevNode?.parent_id;
- updateItem(node, container);
+ moveItem(node, container);
- // focusItem(item);
+ focusItem(item);
this.pushEvent("move_node", node);
}
} else {
@@ -145,9 +144,9 @@ export function keydown(event: KeyboardEvent) {
if (node.prev_id) {
node.parent_id = node.prev_id;
node.prev_id = undefined; // TODO: prev_id should be the id of the last child of the parent node
- updateItem(node, container);
+ moveItem(node, container);
- // focusItem(item);
+ focusItem(item);
this.pushEvent("move_node", node);
}
}
diff --git a/assets/js/hooks/index.ts b/assets/js/hooks/index.ts
index 6a50c8f6..0a0549a9 100644
--- a/assets/js/hooks/index.ts
+++ b/assets/js/hooks/index.ts
@@ -10,7 +10,7 @@ import {
export const Hooks = {
outline: {
mounted() {
- const container: HTMLOListElement = this.el;
+ const container: HTMLDivElement = this.el.querySelector(".children");
container.addEventListener("focusin", focusin.bind(this));
container.addEventListener("focusout", focusout.bind(this));
diff --git a/assets/js/hooks/item.ts b/assets/js/hooks/item.ts
index 8ef66ce4..38489e36 100644
--- a/assets/js/hooks/item.ts
+++ b/assets/js/hooks/item.ts
@@ -1,51 +1,97 @@
import { Node } from "./types";
+import { getNodeByItem } from "./node";
export function createItem({ uuid, content, parent_id, prev_id, dirty }: Node) {
- const input = document.createElement("div");
- input.textContent = content || "";
- input.contentEditable = "true"; // firefox does not support "plaintext-only"
+ //
+
+ const item = document.createElement("div");
+ item.id = `outline-node-${uuid}`;
+ item.className = "my-1 bg-gray-100 data-[dirty=true]:bg-red-100";
+ item.setAttribute("data-parent", parent_id || "");
+ item.setAttribute("data-prev", prev_id || "");
- const ol = document.createElement("ol");
- ol.className = "list-disc";
+ const link = document.createElement("a");
+ link.className = "block float-left my-0.5 bg-gray-200 rounded-full";
+ link.href = `#${uuid}`;
+ link.innerHTML =
+ '';
+ item.appendChild(link);
- const item = document.createElement("li");
- item.id = "outline-node-" + uuid;
- item.className = "my-1 ml-4 data-[dirty=true]:bg-red-100";
+ const contentWrap = document.createElement("div");
+ contentWrap.className = "ml-5 bg-gray-300 content";
+ contentWrap.contentEditable = "true";
+ item.appendChild(contentWrap);
- item.setAttribute("data-parent", parent_id || "");
- item.setAttribute("data-prev", prev_id || "");
+ const span = document.createElement("span");
+ span.className = "bg-gray-400 innerContent";
+ span.textContent = content || " ";
+ contentWrap.appendChild(span);
+
+ const childContainer = document.createElement("div");
+ childContainer.className = "ml-5 children";
+ item.appendChild(childContainer);
+
+ setItemDirty(item, dirty);
+
+ return item;
+}
+
+export function changeItemContent({ uuid, content, dirty }: Node) {
+ const item = getItemById(uuid);
+ if (!item) return;
- setItemDirty(item, dirty)
+ const newContent = content || "";
- item.appendChild(input);
- item.appendChild(ol);
+ const span = item.querySelector(".innerContent") as HTMLSpanElement;
+ if (span.textContent != newContent) span.textContent = newContent;
+
+ setItemDirty(item, dirty);
return item;
}
-export function updateItem(
- { uuid, content, parent_id, prev_id, dirty }: Node,
- container: HTMLOListElement
+export function moveItem(
+ { uuid, parent_id, prev_id, dirty }: Node,
+ container: HTMLDivElement,
+ force: boolean = false
) {
const item = getItemById(uuid);
if (!item) return;
- const input = item.firstChild!;
- input.textContent = content || "";
+ const currentNode = getNodeByItem(item);
+ if (
+ !force &&
+ currentNode.parent_id == parent_id &&
+ currentNode.prev_id == prev_id
+ )
+ return;
item.setAttribute("data-parent", parent_id || "");
item.setAttribute("data-prev", prev_id || "");
- setItemDirty(item, dirty)
-
const prevItem = getItemById(prev_id);
const parentItem = getItemById(parent_id);
if (prevItem) {
prevItem.after(item);
} else if (parentItem) {
- parentItem.querySelector("ol")?.append(item);
+ parentItem.querySelector(".children")?.append(item);
+ } else {
+ container.prepend(item);
}
+
+ setItemDirty(item, dirty);
+
+ return item;
}
export function deleteItem({ uuid }: Node) {
@@ -55,30 +101,37 @@ export function deleteItem({ uuid }: Node) {
item.parentNode!.removeChild(item);
}
+export function cleanItem({ uuid }: Node) {
+ const item = getItemById(uuid);
+ item && setItemDirty(item, false);
+
+ return item;
+}
+
export function getItemByNode({ uuid }: Node) {
return getItemById(uuid);
}
-export function getItemById(uuid: string | undefined) {
+function getItemById(uuid: string | undefined) {
if (!uuid) return null;
- return document.getElementById("outline-node-" + uuid) as HTMLLIElement;
+ return document.getElementById(`outline-node-${uuid}`) as HTMLDivElement;
}
-export function getItemByEvent(event: Event): HTMLLIElement {
+export function getItemByEvent(event: Event): HTMLDivElement {
const target = event.target;
- const item = target.parentElement!;
+ const item = target.parentElement!;
return item;
}
-export function focusItem(item: HTMLLIElement, toEnd: boolean = true) {
- const input = item.firstChild as HTMLDivElement;
- input.focus();
+export function focusItem(item: HTMLDivElement, toEnd: boolean = true) {
+ const contentWrap = item.querySelector(".innerContent") as HTMLDivElement;
+ contentWrap.focus();
if (toEnd) {
const range = document.createRange();
- range.setStart(input, 0);
+ range.setStart(contentWrap, 0);
range.collapse(true);
const selection = window.getSelection();
@@ -87,14 +140,6 @@ export function focusItem(item: HTMLLIElement, toEnd: boolean = true) {
}
}
-// export function indentNode(node: Node) {
-// // const node = event.target.parentNode
-// // const parentNode = event.target.parentNode.previousSibling
-// }
-
-// export function outdentNode(node: Node) {
-// }
-
-export function setItemDirty(item: HTMLLIElement, dirty: boolean) {
+export function setItemDirty(item: HTMLDivElement, dirty: boolean) {
item.setAttribute("data-dirty", dirty ? "true" : "false");
}
diff --git a/assets/js/hooks/node.ts b/assets/js/hooks/node.ts
index 41168f40..f19dcea5 100644
--- a/assets/js/hooks/node.ts
+++ b/assets/js/hooks/node.ts
@@ -1,19 +1,20 @@
-import { UUID, Node } from "./types"
-import { getItemByEvent } from "./item"
+import { UUID, Node } from "./types";
+import { getItemByEvent } from "./item";
export function getNodeByEvent(event: Event): Node {
- const item = getItemByEvent(event)
+ const item = getItemByEvent(event);
- return getNodeByItem(item)
+ return getNodeByItem(item);
}
-export function getNodeByItem(item: HTMLLIElement): Node {
- const uuid = item.id.split("outline-node-")[1] as UUID
- const input = item.firstChild as HTMLDivElement
- const content = input.textContent || ""
+export function getNodeByItem(item: HTMLDivElement): Node {
+ const uuid = item.id.split("outline-node-")[1] as UUID;
+ const span = item.querySelector(".innerContent") as HTMLSpanElement;
+ const content = span.textContent || "";
- const parent_id = item.getAttribute("data-parent") as UUID || undefined
- const prev_id = item.getAttribute("data-prev") as UUID || undefined
+ const parent_id = (item.getAttribute("data-parent") as UUID) || undefined;
+ const prev_id = (item.getAttribute("data-prev") as UUID) || undefined;
+ const dirty = item.getAttribute("data-dirty") == "true" ? true : false;
- return { uuid, content, parent_id, prev_id }
+ return { uuid, content, parent_id, prev_id, dirty };
}
diff --git a/lib/radiator_web/live/episode_live/index.html.heex b/lib/radiator_web/live/episode_live/index.html.heex
index 0dd0099f..f365dc0c 100644
--- a/lib/radiator_web/live/episode_live/index.html.heex
+++ b/lib/radiator_web/live/episode_live/index.html.heex
@@ -13,7 +13,7 @@
:for={{episode, i} <- Enum.with_index(@episodes)}
class={[episode.id == @selected_episode.id && "bg-[#f0f4f4]"]}
>
- <.link patch={~p"/admin/podcast/#{@show}/#{episode}"} class="flex gap-4 my-4">
+ <.link navigate={~p"/admin/podcast/#{@show}/#{episode}"} class="flex gap-4 my-4">
<%= episode.number %>
<%= episode.title %>
@@ -44,6 +44,8 @@
<%= @selected_episode.number %> <%= @selected_episode.title %>
-
+