From 63434678ceb84c88639befcd03ccf8d5861b84eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sun, 14 Jun 2026 14:37:37 +0200 Subject: [PATCH 1/3] Added menu icon --- static/images/icon_menu.svg | 51 +++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 static/images/icon_menu.svg diff --git a/static/images/icon_menu.svg b/static/images/icon_menu.svg new file mode 100644 index 0000000..e60bdee --- /dev/null +++ b/static/images/icon_menu.svg @@ -0,0 +1,51 @@ + + + + + + + + menu + hamburger + + + From 04c936e7309d91c099e23d71b6bc801c8739fe28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sun, 14 Jun 2026 15:02:27 +0200 Subject: [PATCH 2/3] Fixed drag-and-drop with drag source instead of drag target --- static/js/app.mjs | 10 +++++----- static/js/sidebar.mjs | 14 +++++--------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/static/js/app.mjs b/static/js/app.mjs index 8fc43df..c556341 100644 --- a/static/js/app.mjs +++ b/static/js/app.mjs @@ -370,7 +370,7 @@ class N2DragIcon extends CustomHTMLElement { this.style.top = `${e.clientY}px` }) - this.dragTarget = null + this.dragSource = null }// }}} start() {// {{{ this.style.display = 'block' @@ -383,11 +383,11 @@ class N2DragIcon extends CustomHTMLElement { name = '_' + name this.elIcon.setAttribute('src', `/images/${_VERSION}/icon_drag${name}.svg`) }// }}} - setTarget(t) {// {{{ - this.dragTarget = t + setSource(s) {// {{{ + this.dragSource = s }// }}} - getTarget() {// {{{ - return this.dragTarget + getSource() {// {{{ + return this.dragSource }// }}} } diff --git a/static/js/sidebar.mjs b/static/js/sidebar.mjs index 561de8d..63c4ddc 100644 --- a/static/js/sidebar.mjs +++ b/static/js/sidebar.mjs @@ -585,6 +585,7 @@ export class N2TreeNode extends CustomHTMLElement { e.dataTransfer.setDragImage(blankPixel, 0, 0) e.dataTransfer.allowedEffects = 'none' e.stopPropagation() + _app.dragIcon.setSource(this) _app.dragIcon.start() }// }}} dragEnd(e) {// {{{ @@ -598,13 +599,12 @@ export class N2TreeNode extends CustomHTMLElement { }// }}} async dragDrop(e) {// {{{ e.stopPropagation() - const moveToNode = _app.dragIcon.getTarget() - await _app.moveNode(this.node, moveToNode.node.UUID) - return + const sourceNode = _app.dragIcon.getSource() + await _app.moveNode(sourceNode.node, this.node.UUID) - _app.sidebar.setNodeExpanded(moveToNode, true) + _app.sidebar.setNodeExpanded(this, true) await this.render(true, true) - await moveToNode.render(true, true) + await sourceNode.render(true, true) this.dragLeave(e) }// }}} @@ -615,8 +615,6 @@ export class N2TreeNode extends CustomHTMLElement { e.stopPropagation() _app.dragIcon.icon('ok') this.classList.add('drag-target') - - _app.dragIcon.setTarget(this) }// }}} dragLeave(e) {// {{{ e.stopPropagation() @@ -624,8 +622,6 @@ export class N2TreeNode extends CustomHTMLElement { e.dataTransfer.setDragImage(N2TreeNode.DRAG_ICON, -16, 8) _app.dragIcon.icon('') this.classList.remove('drag-target') - - _app.dragIcon.setTarget(null) }// }}} async fetchChildren(force_fetch) {//{{{ if (this.children_populated && !force_fetch) From 658733b1d879cd2eb137c2954aa655e438972d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sun, 14 Jun 2026 15:22:17 +0200 Subject: [PATCH 3/3] Fixed moving node to root --- sql/00007.sql | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 sql/00007.sql diff --git a/sql/00007.sql b/sql/00007.sql new file mode 100644 index 0000000..0b79d9c --- /dev/null +++ b/sql/00007.sql @@ -0,0 +1,119 @@ +CREATE OR REPLACE PROCEDURE public.add_nodes(IN p_user_id integer, IN p_client_uuid uuid, IN p_nodes jsonb) + LANGUAGE plpgsql +AS $procedure$ + +DECLARE + node_data jsonb; + node_updated timestamptz; + db_updated timestamptz; + db_uuid uuid; + db_client uuid; + db_history_uuid uuid; + node_uuid uuid; + node_parent_uuid uuid; + node_history_uuid uuid; + +BEGIN + FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) + LOOP + node_uuid = (node_data->>'UUID')::uuid; + node_history_uuid = (node_data->>'HistoryUUID')::uuid; + node_updated = (node_data->>'Updated')::timestamptz; + + + + -- Frontend is using an all-zero UUID to define the root node. + -- Database is using NULL. + IF node_data->>'ParentUUID' = '00000000-0000-0000-0000-000000000000' OR node_data->>'ParentUUID' = '' THEN + node_parent_uuid = NULL; + ELSE + node_parent_uuid = (node_data->>'ParentUUID')::uuid; + END IF; + + + + -- Every jode has a new history UUID to keep the history entry uniquely identifiable + -- across clients. A history entry could potentially be sent again, but should be + -- safe to ignore as every change to a node should have a new history UUID. + -- + -- The current node is also stored as history. + INSERT INTO node_history( + user_id, "uuid", "history_uuid", parents, created, updated, + "name", "content", "content_encrypted", + client + ) + VALUES( + p_user_id, -- combined key + node_uuid, -- combined key + node_history_uuid, -- combined key + (jsonb_populate_record(null::json_ancestor_array, node_data))."Ancestors", + COALESCE((node_data->>'Created')::timestamptz, NOW()), + COALESCE((node_data->>'Updated')::timestamptz, NOW()), + (node_data->>'Name')::varchar, + (node_data->>'Content')::text, + '', /* content_encrypted */ + p_client_uuid + ) + ON CONFLICT ("user_id", "uuid", "history_uuid") + DO NOTHING; + + + + -- Retrieve the current modified timestamp for this node from the database. + SELECT + uuid, updated, client + INTO + db_uuid, db_updated, db_client + FROM public."node" + WHERE + user_id = p_user_id AND + uuid::uuid = node_uuid::uuid; + + + + -- Is the node not in database? It needs to be created. + IF db_uuid IS NULL THEN + RAISE NOTICE '01 New node %', node_uuid; + + INSERT INTO public."node" ( + user_id, "uuid", parent_uuid, created, updated, + "name", "content", "content_encrypted", + client + ) + VALUES( + p_user_id, + node_uuid, + node_parent_uuid, + COALESCE((node_data->>'Created')::timestamptz, NOW()), + COALESCE((node_data->>'Updated')::timestamptz, NOW()), + (node_data->>'Name')::varchar, + (node_data->>'Content')::text, + '', /* content_encrypted */ + p_client_uuid + ); + + CONTINUE; + + END IF; + + + + -- Update the public node as well if it was older than incoming node. + IF node_updated > db_updated THEN + UPDATE public."node" + SET + updated = (node_data->>'Updated')::timestamptz, + updated_seq = nextval('node_updates'), + parent_uuid = node_parent_uuid, + name = (node_data->>'Name')::varchar, + content = (node_data->>'Content')::text, + client = p_client_uuid + WHERE + user_id = p_user_id AND + uuid::uuid = node_uuid::uuid; + END IF; + + END LOOP; +END +$procedure$ +;