diff --git a/assets/js/app.js b/assets/js/app.js index 24962d0e..a8342683 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -22,7 +22,7 @@ import { Socket } from "phoenix" import { LiveSocket } from "phoenix_live_view" import topbar from "../vendor/topbar" -import { Hooks } from "./hooks" +import { Hooks } from "./hooks2" let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") let liveSocket = new LiveSocket("/live", Socket, { diff --git a/assets/js/hooks2/index.ts b/assets/js/hooks2/index.ts new file mode 100644 index 00000000..05ecd805 --- /dev/null +++ b/assets/js/hooks2/index.ts @@ -0,0 +1,24 @@ +import { + moveHtmlChildNodesToDataPosition, + setCursorToEndOfFirstChildNode, +} from "./tree"; + +import { toggleCollapse } from "./node"; +import { restoreCollapsedStatus } from "./store"; + +import { DomContainer } from "./types"; + +export const Hooks = { + outline: { + mounted() { + const container: DomContainer = this.el; + + container.addEventListener("toggle_collapse", toggleCollapse); + + moveHtmlChildNodesToDataPosition(container); + restoreCollapsedStatus(container); + setCursorToEndOfFirstChildNode(container); + }, + //updated() {}, + }, +}; diff --git a/assets/js/hooks2/node/index.ts b/assets/js/hooks2/node/index.ts new file mode 100644 index 00000000..2ad04f42 --- /dev/null +++ b/assets/js/hooks2/node/index.ts @@ -0,0 +1,60 @@ +import { saveCollapseStatus } from "../store"; +import { DataNode, DomNode, UUID } from "../types"; + +export function toggleCollapse(event: Event) { + const target = event.target as HTMLDivElement; + const node = getDomNodeByTarget(target); + node.classList.toggle("collapsed"); + const { uuid, collapsed } = getDataNodeFromDomNode(node); + + saveCollapseStatus(this.id, uuid, collapsed); +} + +function getDomNodeByTarget(target: HTMLDivElement) { + return target.closest(".node") as DomNode; +} + +export function moveDomNodeToDataPosition(node: DomNode) { + const { parent_id, prev_id } = getDataNodeFromDomNode(node); + + const parentNode = getDomNodeById(parent_id); + const prevNode = getDomNodeById(prev_id); + + if (prevNode) { + prevNode.after(node); + //insertBefore + } else if (parentNode) { + parentNode.querySelector(".children")!.prepend(node); + //appendChild + } + + return node; +} + +function getDomNodeById(uuid: UUID | undefined) { + return document.getElementById(`nodes-form-${uuid}`) as DomNode | null; +} + +export function getDataNodeFromDomNode(node: DomNode): DataNode { + const uuid = getUUID(node); + const parent_id = getAttribute(node, "parent"); + const prev_id = getAttribute(node, "prev"); + const content = getContent(node); + const collapsed = node.classList.contains("collapsed"); + + return { uuid, parent_id, prev_id, content, collapsed }; +} + +function getUUID(node: DomNode) { + return getAttribute(node, "uuid")!; +} + +function getAttribute(node: DomNode, attribute: string) { + return node.dataset[attribute] as UUID | undefined; +} + +function getContent(node: DomNode) { + const content = node.querySelector(".content") as HTMLDivElement; + + return content.innerHTML; +} diff --git a/assets/js/hooks2/store.ts b/assets/js/hooks2/store.ts new file mode 100644 index 00000000..caa28cac --- /dev/null +++ b/assets/js/hooks2/store.ts @@ -0,0 +1,23 @@ +import { getDataNodeFromDomNode } from "./node"; +import { DomContainer, DomNode, UUID } from "./types"; + +export function restoreCollapsedStatus(container: DomContainer) { + const status = loadCollapsedStatus(container.id); + + container.querySelectorAll(".node").forEach((node) => { + const { uuid } = getDataNodeFromDomNode(node as DomNode); + status[uuid] && node.classList.add("collapsed"); + }); +} + +export function saveCollapseStatus(id: string, uuid: UUID, collapsed: boolean) { + const status = loadCollapsedStatus(id); + status[uuid] = collapsed; + localStorage.setItem(id, JSON.stringify(status)); +} + +export function loadCollapsedStatus(id: string) { + const status = localStorage.getItem(id) || "{}"; + + return JSON.parse(status); +} diff --git a/assets/js/hooks2/tree/cursor.ts b/assets/js/hooks2/tree/cursor.ts new file mode 100644 index 00000000..c643d5f9 --- /dev/null +++ b/assets/js/hooks2/tree/cursor.ts @@ -0,0 +1,24 @@ +import { DomContainer, DomNode } from "../types"; + +export function setCursorToEndOfFirstChildNode(container: DomContainer) { + const node = container.querySelector(".node:first-child") as DomNode; + + setCursorPosition(node); +} + +function setCursorPosition(node: DomNode, position: number | null = null) { + const content = node.querySelector(".content"); + const range = document.createRange(); + + if (position) { + range.setStart(content!.childNodes[0], position!); + range.collapse(true); + } else { + range.selectNodeContents(content!); + range.collapse(); + } + + const selection = window.getSelection(); + selection?.removeAllRanges(); + selection?.addRange(range); +} diff --git a/assets/js/hooks2/tree/index.ts b/assets/js/hooks2/tree/index.ts new file mode 100644 index 00000000..19b5b04c --- /dev/null +++ b/assets/js/hooks2/tree/index.ts @@ -0,0 +1,10 @@ +import { DomContainer, DomNode, DataNode, UUID } from "../types"; +import { moveDomNodeToDataPosition } from "../node"; + +export { setCursorToEndOfFirstChildNode } from "./cursor"; + +export function moveHtmlChildNodesToDataPosition(container: DomContainer) { + container.querySelectorAll(".node").forEach((node) => { + moveDomNodeToDataPosition(node as DomNode); + }); +} diff --git a/assets/js/hooks2/types.ts b/assets/js/hooks2/types.ts new file mode 100644 index 00000000..337ac33f --- /dev/null +++ b/assets/js/hooks2/types.ts @@ -0,0 +1,15 @@ +export interface DataNode { + uuid: UUID; + parent_id?: UUID; + prev_id?: UUID; + content: string; + collapsed: boolean; + // selected: boolean; + // creator_id?: number; +} + +export type DomContainer = HTMLDivElement; + +export type DomNode = HTMLDivElement; + +export type UUID = `${string}-${string}-${string}-${string}-${string}`;