From 94c05e90e8412085feba67e0d30be81d6426a9ee Mon Sep 17 00:00:00 2001
From: Lukas <38840142+lukasstorck@users.noreply.github.com>
Date: Mon, 29 Sep 2025 16:37:36 +0200
Subject: [PATCH] refactor: tooltips behavior and tooltips in bookmark editor
 (#4136)
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
---
 .../src/main/resources/static/js/navbar.js    | 61 +++++++++++--------
 .../static/js/pages/edit-table-of-contents.js | 20 +-----
 2 files changed, 36 insertions(+), 45 deletions(-)
diff --git a/app/core/src/main/resources/static/js/navbar.js b/app/core/src/main/resources/static/js/navbar.js
index 1fd46ed70..be9d2df12 100644
--- a/app/core/src/main/resources/static/js/navbar.js
+++ b/app/core/src/main/resources/static/js/navbar.js
@@ -75,39 +75,46 @@ function setupDropdowns() {
   });
 }
 
-window.tooltipSetup = () => {
-  const tooltipElements = document.querySelectorAll('[title]');
+function tooltipSetup() {
+  // initialize global tooltip element or get reference
+  let customTooltip = document.getElementById("customTooltip");
+  if (!customTooltip) {
+    customTooltip = document.createElement("div");
+    customTooltip.id = "customTooltip";
+    customTooltip.className = "btn-tooltip";
+    document.body.appendChild(customTooltip);
+  }
+
+  function updateTooltipPosition(event, text) {
+    if (window.innerWidth >= 1200) {
+      customTooltip.textContent = text;
+      customTooltip.style.display = "block";
+      customTooltip.style.left = `${event.pageX + 10}px`;
+      customTooltip.style.top = `${event.pageY + 10}px`;
+    }
+  }
+
+  function hideTooltip() {
+    customTooltip.style.display = "none";
+  }
+
+  // find uninitialized tooltips and set up event listeners
+  const tooltipElements = document.querySelectorAll("[title]");
 
   tooltipElements.forEach((element) => {
-    const tooltipText = element.getAttribute('title');
-    element.removeAttribute('title');
-    element.setAttribute('data-title', tooltipText);
-    const customTooltip = document.createElement('div');
-    customTooltip.className = 'btn-tooltip';
-    customTooltip.textContent = tooltipText;
+    const tooltipText = element.getAttribute("title");
+    element.removeAttribute("title");
+    element.setAttribute("data-title", tooltipText);  // no UI effect, just for reference
 
-    document.body.appendChild(customTooltip);
+    element.addEventListener("mouseenter", (event) => updateTooltipPosition(event, tooltipText));
+    element.addEventListener("mousemove", (event) => updateTooltipPosition(event, tooltipText));
+    element.addEventListener("mouseleave", hideTooltip);
 
-    element.addEventListener('mouseenter', (event) => {
-      if (window.innerWidth >= 1200) {
-        customTooltip.style.display = 'block';
-        customTooltip.style.left = `${event.pageX + 10}px`;
-        customTooltip.style.top = `${event.pageY + 10}px`;
-      }
-    });
-
-    element.addEventListener('mousemove', (event) => {
-      if (window.innerWidth >= 1200) {
-        customTooltip.style.left = `${event.pageX + 10}px`;
-        customTooltip.style.top = `${event.pageY + 10}px`;
-      }
-    });
-
-    element.addEventListener('mouseleave', () => {
-      customTooltip.style.display = 'none';
-    });
+    // in case UI moves and mouseleave is not triggered, tooltip is readded when mouse is moved over the element
+    element.addEventListener("click", hideTooltip);
   });
 };
+window.tooltipSetup = tooltipSetup;
 
 // Override the bootstrap dropdown styles for mobile
 function fixNavbarDropdownStyles() {
diff --git a/app/core/src/main/resources/static/js/pages/edit-table-of-contents.js b/app/core/src/main/resources/static/js/pages/edit-table-of-contents.js
index 82c92a50e..9b8cb5cdd 100644
--- a/app/core/src/main/resources/static/js/pages/edit-table-of-contents.js
+++ b/app/core/src/main/resources/static/js/pages/edit-table-of-contents.js
@@ -316,9 +316,7 @@ document.addEventListener("DOMContentLoaded", function () {
     updateBookmarkData();
 
     // Initialize tooltips for dynamically added elements
-    if (typeof $ !== "undefined") {
-      $('[data-bs-toggle="tooltip"]').tooltip();
-    }
+    window.tooltipSetup();
   }
 
   // Create the main bookmark element with collapsible interface
@@ -390,8 +388,6 @@ document.addEventListener("DOMContentLoaded", function () {
       childCount.style.fontSize = "0.7rem";
       childCount.style.padding = "0.2em 0.5em";
       childCount.textContent = bookmark.children.length;
-      childCount.setAttribute("data-bs-toggle", "tooltip");
-      childCount.setAttribute("data-bs-placement", "top");
       childCount.title = `${bookmark.children.length} child bookmark${bookmark.children.length > 1 ? "s" : ""}`;
       toggleContainer.appendChild(childCount);
     } else {
@@ -577,10 +573,6 @@ document.addEventListener("DOMContentLoaded", function () {
     button.type = "button";
     button.className = `btn ${className} btn-bookmark-action`;
     button.innerHTML = `${icon}`;
-
-    // Use Bootstrap tooltips
-    button.setAttribute("data-bs-toggle", "tooltip");
-    button.setAttribute("data-bs-placement", "top");
     button.title = title;
 
     button.addEventListener("click", clickHandler);
@@ -601,10 +593,6 @@ document.addEventListener("DOMContentLoaded", function () {
   // Update the add bookmark button appearance with clear visual cue
   addBookmarkBtn.innerHTML = 'add Add Top-level Bookmark';
   addBookmarkBtn.className = "btn btn-primary btn-add-bookmark top-level";
-
-  // Use Bootstrap tooltips
-  addBookmarkBtn.setAttribute("data-bs-toggle", "tooltip");
-  addBookmarkBtn.setAttribute("data-bs-placement", "top");
   addBookmarkBtn.title = "Add a new top-level bookmark";
 
   // Add icon to empty state button as well
@@ -612,14 +600,10 @@ document.addEventListener("DOMContentLoaded", function () {
     const emptyStateBtn = document.querySelector(".btn-add-first-bookmark");
     if (emptyStateBtn) {
       emptyStateBtn.innerHTML = 'add Add First Bookmark';
-      emptyStateBtn.setAttribute("data-bs-toggle", "tooltip");
-      emptyStateBtn.setAttribute("data-bs-placement", "top");
       emptyStateBtn.title = "Add first bookmark";
 
       // Initialize tooltips for the empty state button
-      if (typeof $ !== "undefined") {
-        $('[data-bs-toggle="tooltip"]').tooltip();
-      }
+      window.tooltipSetup();
     }
   };