From 45380aebca9ade3bef196b6c39b7c285c418c7eb Mon Sep 17 00:00:00 2001 From: sorax Date: Tue, 19 Aug 2025 09:18:15 +0200 Subject: [PATCH 1/6] add parallel hook structure --- assets/js/app.js | 2 +- assets/js/hooks2/index.ts | 10 ++++++++++ assets/js/hooks2/types.ts | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 assets/js/hooks2/index.ts create mode 100644 assets/js/hooks2/types.ts diff --git a/assets/js/app.js b/assets/js/app.js index 24962d0ee..a83426830 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 000000000..037a87857 --- /dev/null +++ b/assets/js/hooks2/index.ts @@ -0,0 +1,10 @@ +import { DomContainer } from "./types"; + +export const Hooks = { + outline: { + mounted() { + const container: DomContainer = this.el; + }, + //updated() {}, + }, +}; diff --git a/assets/js/hooks2/types.ts b/assets/js/hooks2/types.ts new file mode 100644 index 000000000..dd8e5f058 --- /dev/null +++ b/assets/js/hooks2/types.ts @@ -0,0 +1 @@ +export type DomContainer = HTMLDivElement; From 399466189c3dfbac36c69747e22dcc723fd39258 Mon Sep 17 00:00:00 2001 From: sorax Date: Tue, 19 Aug 2025 09:31:16 +0200 Subject: [PATCH 2/6] put nodes in tree structure --- assets/js/hooks2/index.ts | 4 ++++ assets/js/hooks2/node/index.ts | 38 ++++++++++++++++++++++++++++++++++ assets/js/hooks2/tree/index.ts | 11 ++++++++++ assets/js/hooks2/types.ts | 14 +++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 assets/js/hooks2/node/index.ts create mode 100644 assets/js/hooks2/tree/index.ts diff --git a/assets/js/hooks2/index.ts b/assets/js/hooks2/index.ts index 037a87857..9d3db7009 100644 --- a/assets/js/hooks2/index.ts +++ b/assets/js/hooks2/index.ts @@ -1,9 +1,13 @@ +import { moveHtmlChildNodesToDataPosition } from "./tree"; + import { DomContainer } from "./types"; export const Hooks = { outline: { mounted() { const container: DomContainer = this.el; + + moveHtmlChildNodesToDataPosition(container); }, //updated() {}, }, diff --git a/assets/js/hooks2/node/index.ts b/assets/js/hooks2/node/index.ts new file mode 100644 index 000000000..995fbc7d6 --- /dev/null +++ b/assets/js/hooks2/node/index.ts @@ -0,0 +1,38 @@ +import { DataNode, DomNode, UUID } from "../types"; + +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; +} + +function getDataNodeFromDomNode(node: DomNode): DataNode { + const uuid = getUUID(node); + const parent_id = getAttribute(node, "parent"); + const prev_id = getAttribute(node, "prev"); + + return { uuid, parent_id, prev_id }; +} + +function getUUID(node: DomNode) { + return getAttribute(node, "uuid")!; +} + +function getAttribute(node: DomNode, attribute: string) { + return node.dataset[attribute] as UUID | undefined; +} diff --git a/assets/js/hooks2/tree/index.ts b/assets/js/hooks2/tree/index.ts new file mode 100644 index 000000000..593c94a46 --- /dev/null +++ b/assets/js/hooks2/tree/index.ts @@ -0,0 +1,11 @@ +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 index dd8e5f058..8884802df 100644 --- a/assets/js/hooks2/types.ts +++ b/assets/js/hooks2/types.ts @@ -1 +1,15 @@ +export interface DataNode { + uuid: UUID; + // content?: string; + // creator_id?: number; + parent_id?: UUID; + prev_id?: UUID; + collapsed?: boolean; + // selected?: boolean; +} + export type DomContainer = HTMLDivElement; + +export type DomNode = HTMLDivElement; + +export type UUID = `${string}-${string}-${string}-${string}-${string}`; From 8e29399d4cdce5be2d5176bc9b587dfb0cf1f8fa Mon Sep 17 00:00:00 2001 From: sorax Date: Tue, 19 Aug 2025 19:06:25 +0200 Subject: [PATCH 3/6] set cursor position --- assets/js/hooks2/index.ts | 6 +++++- assets/js/hooks2/tree/cursor.ts | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 assets/js/hooks2/tree/cursor.ts diff --git a/assets/js/hooks2/index.ts b/assets/js/hooks2/index.ts index 9d3db7009..cba6e83c2 100644 --- a/assets/js/hooks2/index.ts +++ b/assets/js/hooks2/index.ts @@ -1,4 +1,7 @@ -import { moveHtmlChildNodesToDataPosition } from "./tree"; +import { + moveHtmlChildNodesToDataPosition, + setCursorToEndOfFirstChildNode, +} from "./tree"; import { DomContainer } from "./types"; @@ -8,6 +11,7 @@ export const Hooks = { const container: DomContainer = this.el; moveHtmlChildNodesToDataPosition(container); + setCursorToEndOfFirstChildNode(container); }, //updated() {}, }, diff --git a/assets/js/hooks2/tree/cursor.ts b/assets/js/hooks2/tree/cursor.ts new file mode 100644 index 000000000..c643d5f93 --- /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); +} From 7d4411a15ab3b66febba2639547902564985d1a3 Mon Sep 17 00:00:00 2001 From: sorax Date: Tue, 19 Aug 2025 19:18:13 +0200 Subject: [PATCH 4/6] add collapse event listener --- assets/js/hooks2/index.ts | 4 ++++ assets/js/hooks2/node/index.ts | 28 +++++++++++++++++++++++++++- assets/js/hooks2/types.ts | 6 +++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/assets/js/hooks2/index.ts b/assets/js/hooks2/index.ts index cba6e83c2..0f7ad5c3e 100644 --- a/assets/js/hooks2/index.ts +++ b/assets/js/hooks2/index.ts @@ -3,6 +3,8 @@ import { setCursorToEndOfFirstChildNode, } from "./tree"; +import { toggleCollapse } from "./node"; + import { DomContainer } from "./types"; export const Hooks = { @@ -10,6 +12,8 @@ export const Hooks = { mounted() { const container: DomContainer = this.el; + container.addEventListener("toggle_collapse", toggleCollapse); + moveHtmlChildNodesToDataPosition(container); setCursorToEndOfFirstChildNode(container); }, diff --git a/assets/js/hooks2/node/index.ts b/assets/js/hooks2/node/index.ts index 995fbc7d6..6885c4a36 100644 --- a/assets/js/hooks2/node/index.ts +++ b/assets/js/hooks2/node/index.ts @@ -1,5 +1,23 @@ 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); + // setCollapse.call(this, uuid, collapsed); +} + +function getDomNodeByTarget(target: HTMLDivElement) { + return target.closest(".node") as DomNode; +} + +// function setCollapseStatus(uuid: UUID, collapsed: boolean) { +// // const status = getCollapsedStatus(this.el.id); +// // status[uuid] = collapsed; +// // localStorage.setItem(this.el.id, JSON.stringify(status)); +// } + export function moveDomNodeToDataPosition(node: DomNode) { const { parent_id, prev_id } = getDataNodeFromDomNode(node); @@ -25,8 +43,10 @@ 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 }; + return { uuid, parent_id, prev_id, content, collapsed }; } function getUUID(node: DomNode) { @@ -36,3 +56,9 @@ function getUUID(node: DomNode) { 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/types.ts b/assets/js/hooks2/types.ts index 8884802df..c073652f5 100644 --- a/assets/js/hooks2/types.ts +++ b/assets/js/hooks2/types.ts @@ -1,11 +1,11 @@ export interface DataNode { uuid: UUID; - // content?: string; - // creator_id?: number; parent_id?: UUID; prev_id?: UUID; + content?: string; collapsed?: boolean; - // selected?: boolean; + selected?: boolean; + // creator_id?: number; } export type DomContainer = HTMLDivElement; From db0d5e05b9d5433c51b80267da5fc893d7d25252 Mon Sep 17 00:00:00 2001 From: sorax Date: Tue, 19 Aug 2025 19:35:30 +0200 Subject: [PATCH 5/6] save collapse status --- assets/js/hooks2/node/index.ts | 10 +++------- assets/js/hooks2/store.ts | 13 +++++++++++++ assets/js/hooks2/types.ts | 6 +++--- 3 files changed, 19 insertions(+), 10 deletions(-) create mode 100644 assets/js/hooks2/store.ts diff --git a/assets/js/hooks2/node/index.ts b/assets/js/hooks2/node/index.ts index 6885c4a36..af21a40be 100644 --- a/assets/js/hooks2/node/index.ts +++ b/assets/js/hooks2/node/index.ts @@ -1,3 +1,4 @@ +import { saveCollapseStatus } from "../store"; import { DataNode, DomNode, UUID } from "../types"; export function toggleCollapse(event: Event) { @@ -5,19 +6,14 @@ export function toggleCollapse(event: Event) { const node = getDomNodeByTarget(target); node.classList.toggle("collapsed"); const { uuid, collapsed } = getDataNodeFromDomNode(node); - // setCollapse.call(this, uuid, collapsed); + + saveCollapseStatus(this.id, uuid, collapsed); } function getDomNodeByTarget(target: HTMLDivElement) { return target.closest(".node") as DomNode; } -// function setCollapseStatus(uuid: UUID, collapsed: boolean) { -// // const status = getCollapsedStatus(this.el.id); -// // status[uuid] = collapsed; -// // localStorage.setItem(this.el.id, JSON.stringify(status)); -// } - export function moveDomNodeToDataPosition(node: DomNode) { const { parent_id, prev_id } = getDataNodeFromDomNode(node); diff --git a/assets/js/hooks2/store.ts b/assets/js/hooks2/store.ts new file mode 100644 index 000000000..012f19bbe --- /dev/null +++ b/assets/js/hooks2/store.ts @@ -0,0 +1,13 @@ +import { UUID } from "./types"; + +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/types.ts b/assets/js/hooks2/types.ts index c073652f5..337ac33f4 100644 --- a/assets/js/hooks2/types.ts +++ b/assets/js/hooks2/types.ts @@ -2,9 +2,9 @@ export interface DataNode { uuid: UUID; parent_id?: UUID; prev_id?: UUID; - content?: string; - collapsed?: boolean; - selected?: boolean; + content: string; + collapsed: boolean; + // selected: boolean; // creator_id?: number; } From 4433a0c96c72d5b4bea4aee0c0d825d5ccb1dbe3 Mon Sep 17 00:00:00 2001 From: sorax Date: Tue, 19 Aug 2025 20:23:04 +0200 Subject: [PATCH 6/6] restore collapse status --- assets/js/hooks2/index.ts | 2 ++ assets/js/hooks2/node/index.ts | 2 +- assets/js/hooks2/store.ts | 12 +++++++++++- assets/js/hooks2/tree/index.ts | 1 - 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/assets/js/hooks2/index.ts b/assets/js/hooks2/index.ts index 0f7ad5c3e..05ecd8053 100644 --- a/assets/js/hooks2/index.ts +++ b/assets/js/hooks2/index.ts @@ -4,6 +4,7 @@ import { } from "./tree"; import { toggleCollapse } from "./node"; +import { restoreCollapsedStatus } from "./store"; import { DomContainer } from "./types"; @@ -15,6 +16,7 @@ export const Hooks = { 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 index af21a40be..2ad04f42e 100644 --- a/assets/js/hooks2/node/index.ts +++ b/assets/js/hooks2/node/index.ts @@ -35,7 +35,7 @@ function getDomNodeById(uuid: UUID | undefined) { return document.getElementById(`nodes-form-${uuid}`) as DomNode | null; } -function getDataNodeFromDomNode(node: DomNode): DataNode { +export function getDataNodeFromDomNode(node: DomNode): DataNode { const uuid = getUUID(node); const parent_id = getAttribute(node, "parent"); const prev_id = getAttribute(node, "prev"); diff --git a/assets/js/hooks2/store.ts b/assets/js/hooks2/store.ts index 012f19bbe..caa28cac6 100644 --- a/assets/js/hooks2/store.ts +++ b/assets/js/hooks2/store.ts @@ -1,4 +1,14 @@ -import { UUID } from "./types"; +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); diff --git a/assets/js/hooks2/tree/index.ts b/assets/js/hooks2/tree/index.ts index 593c94a46..19b5b04cf 100644 --- a/assets/js/hooks2/tree/index.ts +++ b/assets/js/hooks2/tree/index.ts @@ -8,4 +8,3 @@ export function moveHtmlChildNodesToDataPosition(container: DomContainer) { moveDomNodeToDataPosition(node as DomNode); }); } -