Skip to content

Commit

Permalink
refine js-handler
Browse files Browse the repository at this point in the history
  • Loading branch information
sorax committed Jun 10, 2024
1 parent a08c1e9 commit 54427fe
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 87 deletions.
31 changes: 10 additions & 21 deletions assets/js/hooks/events/handler.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -29,40 +28,30 @@ 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 }) {
deleteItem(node);
}

export function handleClean({ node }: { node: Node }) {
const item = getItemById(node.uuid)
item && setItemDirty(item, false);
cleanItem(node);
}
27 changes: 13 additions & 14 deletions assets/js/hooks/events/listener.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -30,16 +31,16 @@ 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)

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);
Expand Down Expand Up @@ -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 = {
Expand All @@ -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);
Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -135,19 +134,19 @@ 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 {
// indent
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);
}
}
Expand Down
2 changes: 1 addition & 1 deletion assets/js/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
121 changes: 83 additions & 38 deletions assets/js/hooks/item.ts
Original file line number Diff line number Diff line change
@@ -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"
// <div id="outline-node-uuid" class="item">
// <a class="bullet" href="/#/UUID">
// <svg viewBox="0 0 18 18" fill="currentColor" class="">
// <circle cx="9" cy="9" r="3.5"></circle>
// </svg></a>
// <div class="content" contenteditable="true">
// <span class="innerContent">Node Content</span>
// </div>
// <div class="children"></div>
// </div>

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 =
'<svg viewBox="0 0 18 18" fill="currentColor" class="w-5 h-5"><circle cx="9" cy="9" r="3.5"></circle></svg>';
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) {
Expand All @@ -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 = <HTMLDivElement>event.target;
const item = <HTMLLIElement>target.parentElement!;
const item = <HTMLDivElement>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();
Expand All @@ -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");
}
23 changes: 12 additions & 11 deletions assets/js/hooks/node.ts
Original file line number Diff line number Diff line change
@@ -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 };
}
Loading

0 comments on commit 54427fe

Please sign in to comment.