From 7de5043656eca450cfd76055d631147a1e6f2d5f Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Wed, 14 May 2025 16:10:30 +0100 Subject: [PATCH] formatting --- .../static/css/edit-table-of-contents.css | 263 ++++++++ .../static/js/pages/edit-table-of-contents.js | 599 ++++++++++++++++++ .../templates/edit-table-of-contents.html | 383 +---------- 3 files changed, 864 insertions(+), 381 deletions(-) create mode 100644 src/main/resources/static/css/edit-table-of-contents.css create mode 100644 src/main/resources/static/js/pages/edit-table-of-contents.js diff --git a/src/main/resources/static/css/edit-table-of-contents.css b/src/main/resources/static/css/edit-table-of-contents.css new file mode 100644 index 000000000..59ad7fdff --- /dev/null +++ b/src/main/resources/static/css/edit-table-of-contents.css @@ -0,0 +1,263 @@ +/* Main bookmark container styles */ +.bookmark-editor { + margin-top: 20px; + padding: 20px; + border: 1px solid var(--border-color, #ced4da); + border-radius: 0.25rem; + background-color: var(--bg-container, var(--md-sys-color-surface, #edf0f5)); +} + +[data-bs-theme="dark"] .bookmark-editor { + --border-color: #495057; + --bg-container: var(--md-sys-color-surface, #15202a); +} + +/* Bookmark item styles */ +.bookmark-item { + margin-bottom: 12px; + border: 1px solid var(--border-item, #e9ecef); + border-radius: 0.5rem; + background-color: var(--bg-item, var(--md-sys-color-surface-container-lowest, #ffffff)); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + overflow: hidden; +} + +[data-bs-theme="dark"] .bookmark-item { + --border-item: var(--md-sys-color-surface-variant, #444b53); + --bg-item: var(--md-sys-color-surface-container-low, #24282e); +} + +/* Bookmark header (collapsible part) */ +.bookmark-header { + padding: 12px 15px; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + background-color: var(--bg-header, var(--md-sys-color-surface-container, #f1f3f5)); + border-bottom: 1px solid var(--border-header, var(--md-sys-color-outline-variant, #e9ecef)); + transition: background-color 0.15s ease; +} + +.bookmark-header:hover { + background-color: var(--bg-header-hover, var(--md-sys-state-hover-opacity, #e9ecef)); +} + +[data-bs-theme="dark"] .bookmark-header { + --bg-header: var(--md-sys-color-surface-container-high, #3a424a); + --bg-header-hover: var(--md-sys-color-surface-container-highest, #434a52); + --border-header: var(--md-sys-color-outline-variant, #444b53); +} + +/* Bookmark content (inside accordion) */ +.bookmark-content { + padding: 15px; + border-top: 0; +} + +/* Children container */ +.bookmark-children { + margin-top: 10px; + margin-left: 20px; + padding-left: 15px; + border-left: 2px solid var(--border-children, #6c757d); +} + +[data-bs-theme="dark"] .bookmark-children { + --border-children: #6c757d; +} + +/* Level indicators */ +.bookmark-level-indicator { + font-size: 0.8em; + color: var(--text-muted, #6c757d); + font-weight: 500; + text-transform: uppercase; + margin-right: 8px; +} + +[data-bs-theme="dark"] .bookmark-level-indicator { + --text-muted: #adb5bd; +} + +/* Button styles */ +.btn-bookmark-action { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + border-radius: 4px; + padding: 0; + margin-right: 6px; + transition: all 0.2s ease; + position: relative; +} + +.btn-bookmark-action:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Visual distinction for child vs sibling buttons using theme colors */ +.btn-add-child { + background-color: var(--btn-add-child-bg, var(--md-sys-color-surface-container-low, #e9ecef)); + color: var(--btn-add-child-color, var(--md-nav-section-color-other, #72bd54)); + border-color: var(--btn-add-child-border, var(--md-nav-section-color-other, #72bd54)); +} + +.btn-add-child::after { + content: ""; + position: absolute; + left: -2px; + top: 50%; + width: 5px; + height: 1px; + background-color: var(--btn-add-child-border, var(--md-nav-section-color-other, #72bd54)); +} + +[data-bs-theme="dark"] .btn-add-child { + --btn-add-child-bg: var(--md-sys-color-surface-container, #28323a); + --btn-add-child-color: var(--favourite-add, #9ed18c); + --btn-add-child-border: var(--favourite-add, #9ed18c); +} + +.btn-add-sibling { + background-color: var(--btn-add-sibling-bg, var(--md-sys-color-surface-container-low, #e9ecef)); + color: var(--btn-add-sibling-color, var(--md-sys-color-primary, #0060aa)); + border-color: var(--btn-add-sibling-border, var(--md-sys-color-primary, #0060aa)); +} + +[data-bs-theme="dark"] .btn-add-sibling { + --btn-add-sibling-bg: var(--md-sys-color-surface-container, #28323a); + --btn-add-sibling-color: var(--md-sys-color-primary, #a2c9ff); + --btn-add-sibling-border: var(--md-sys-color-primary, #a2c9ff); +} + +.bookmark-actions-header { + display: flex; + margin-left: 8px; +} + +.bookmark-actions-content { + display: flex; + justify-content: space-between; + margin-top: 10px; + padding-top: 10px; + border-top: 1px dashed var(--border-subtle-color, #dee2e6); +} + +[data-bs-theme="dark"] .bookmark-actions-content { + --border-subtle-color: #495057; +} + +/* Main actions section */ +.bookmark-actions { + margin-top: 20px; + display: flex; + justify-content: flex-start; +} + +/* Collapse/expand icons */ +.toggle-icon { + transition: transform 0.3s ease; +} + +.collapsed .toggle-icon { + transform: rotate(-90deg); +} + +/* Title and page display in header */ +.bookmark-title-preview { + font-weight: 500; + margin-right: 10px; + color: var(--text-primary, #212529); +} + +[data-bs-theme="dark"] .bookmark-title-preview { + --text-primary: #e9ecef; +} + +.bookmark-page-preview { + font-size: 0.9em; + color: var(--text-secondary, #6c757d); +} + +[data-bs-theme="dark"] .bookmark-page-preview { + --text-secondary: #adb5bd; +} + +/* We've removed the drag handle since it's not functional */ + +/* Add button at the top level */ +.btn-add-bookmark { + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + position: relative; + padding-left: 24px; +} + +.btn-add-bookmark::before { + content: ""; + position: absolute; + left: 12px; + top: 0; + width: 2px; + height: 50%; + background-color: currentColor; +} + +.btn-add-bookmark.top-level::before { + display: none; +} + +/* Relationship indicators */ +.bookmark-relationship-indicator { + position: absolute; + left: -15px; + top: 0; + height: 100%; + width: 14px; + pointer-events: none; +} + +.relationship-line { + position: absolute; + left: 7px; + top: 0; + height: 100%; + width: 2px; + background-color: var(--relationship-color, var(--md-nav-section-color-other, #72bd54)); +} + +.relationship-arrow { + position: absolute; + left: 5px; + top: 50%; + width: 8px; + height: 2px; + background-color: var(--relationship-color, var(--md-nav-section-color-other, #72bd54)); +} + +[data-bs-theme="dark"] .bookmark-relationship-indicator { + --relationship-color: var(--favourite-add, #9ed18c); +} + +/* Empty state */ +.empty-bookmarks { + padding: 30px; + text-align: center; + color: var(--text-muted, var(--md-sys-color-on-surface-variant, #6c757d)); + background-color: var(--bg-empty, var(--md-sys-color-surface-container-lowest, #ffffff)); + border-radius: 0.375rem; + border: 1px dashed var(--border-empty, var(--md-sys-color-outline, #ced4da)); +} + +[data-bs-theme="dark"] .empty-bookmarks { + --text-muted: var(--md-sys-color-on-surface-variant, #adb5bd); + --bg-empty: var(--md-sys-color-surface-container-low, #24282e); + --border-empty: var(--md-sys-color-outline, #495057); +} \ No newline at end of file diff --git a/src/main/resources/static/js/pages/edit-table-of-contents.js b/src/main/resources/static/js/pages/edit-table-of-contents.js new file mode 100644 index 000000000..957ab3613 --- /dev/null +++ b/src/main/resources/static/js/pages/edit-table-of-contents.js @@ -0,0 +1,599 @@ +document.addEventListener('DOMContentLoaded', function() { + const bookmarksContainer = document.getElementById('bookmarks-container'); + const addBookmarkBtn = document.getElementById('addBookmarkBtn'); + const bookmarkDataInput = document.getElementById('bookmarkData'); + let bookmarks = []; + let counter = 0; // Used for generating unique IDs + + // Add event listener to the file input to extract existing bookmarks + document.getElementById('fileInput-input').addEventListener('change', async function(e) { + if (!e.target.files || e.target.files.length === 0) { + return; + } + + // Reset bookmarks initially + bookmarks = []; + updateBookmarksUI(); + + const formData = new FormData(); + formData.append('file', e.target.files[0]); + + // Show loading indicator + showLoadingIndicator(); + + try { + // Call the API to extract bookmarks using fetchWithCsrf for CSRF protection + const response = await fetchWithCsrf('/api/v1/general/extract-bookmarks', { + method: 'POST', + body: formData + }); + + if (!response.ok) { + throw new Error(`Failed to extract bookmarks: ${response.status} ${response.statusText}`); + } + + const extractedBookmarks = await response.json(); + + // Convert extracted bookmarks to our format with IDs + if (extractedBookmarks && extractedBookmarks.length > 0) { + bookmarks = extractedBookmarks.map(convertExtractedBookmark); + } else { + showEmptyState(); + } + } catch (error) { + // Show error message + showErrorMessage('Failed to extract bookmarks. You can still create new ones.'); + + // Add a default bookmark if no bookmarks and error + if (bookmarks.length === 0) { + showEmptyState(); + } + } finally { + // Remove loading indicator + removeLoadingIndicator(); + + // Update the UI + updateBookmarksUI(); + } + }); + + function showLoadingIndicator() { + const loadingEl = document.createElement('div'); + loadingEl.className = 'alert alert-info'; + loadingEl.textContent = 'Loading bookmarks from PDF...'; + loadingEl.id = 'loading-bookmarks'; + bookmarksContainer.innerHTML = ''; + bookmarksContainer.appendChild(loadingEl); + } + + function removeLoadingIndicator() { + const loadingEl = document.getElementById('loading-bookmarks'); + if (loadingEl) { + loadingEl.remove(); + } + } + + function showErrorMessage(message) { + const errorEl = document.createElement('div'); + errorEl.className = 'alert alert-danger'; + errorEl.textContent = message; + bookmarksContainer.appendChild(errorEl); + } + + function showEmptyState() { + const emptyStateEl = document.createElement('div'); + emptyStateEl.className = 'empty-bookmarks mb-3'; + emptyStateEl.innerHTML = ` + bookmark_add +
This PDF doesn't have any bookmarks yet. Add your first bookmark to get started.
+ + `; + + // Add event listener to the "Add First Bookmark" button + emptyStateEl.querySelector('.btn-add-first-bookmark').addEventListener('click', function() { + addBookmark(null, 'New Bookmark', 1); + emptyStateEl.remove(); + }); + + bookmarksContainer.appendChild(emptyStateEl); + } + + // Function to convert extracted bookmarks to our format with IDs + function convertExtractedBookmark(bookmark) { + counter++; + const result = { + id: Date.now() + counter, // Generate a unique ID + title: bookmark.title || 'Untitled Bookmark', + pageNumber: bookmark.pageNumber || 1, + children: [], + expanded: true // All bookmarks start expanded + }; + + // Convert children recursively + if (bookmark.children && bookmark.children.length > 0) { + result.children = bookmark.children.map(child => { + return convertExtractedBookmark(child); + }); + } + + return result; + } + + // Add bookmark button click handler + addBookmarkBtn.addEventListener('click', function(e) { + e.preventDefault(); + addBookmark(); + }); + + // Add form submit handler to update JSON data + document.getElementById('editTocForm').addEventListener('submit', function() { + updateBookmarkData(); + }); + + function addBookmark(parent = null, title = '', pageNumber = 1) { + counter++; + const newBookmark = { + id: Date.now() + counter, + title: title || 'New Bookmark', + pageNumber: pageNumber || 1, + children: [], + expanded: true + }; + + if (parent === null) { + bookmarks.push(newBookmark); + } else { + const parentBookmark = findBookmark(bookmarks, parent); + if (parentBookmark) { + parentBookmark.children.push(newBookmark); + parentBookmark.expanded = true; // Auto-expand the parent when adding a child + } else { + // Add to root level if parent not found + bookmarks.push(newBookmark); + } + } + + updateBookmarksUI(); + + // After updating UI, find and focus the new bookmark's title field + setTimeout(() => { + const newElement = document.querySelector(`[data-id="${newBookmark.id}"]`); + if (newElement) { + const titleInput = newElement.querySelector('.bookmark-title'); + if (titleInput) { + titleInput.focus(); + titleInput.select(); + } + // Scroll to the new element + newElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + }, 50); + } + + function findBookmark(bookmarkArray, id) { + for (const bookmark of bookmarkArray) { + if (bookmark.id === id) { + return bookmark; + } + if (bookmark.children.length > 0) { + const found = findBookmark(bookmark.children, id); + if (found) return found; + } + } + return null; + } + + // Find the parent bookmark of a given bookmark by ID + function findParentBookmark(bookmarkArray, id, parent = null) { + for (const bookmark of bookmarkArray) { + if (bookmark.id === id) { + return parent; // Return the parent ID (or null if top-level) + } + + if (bookmark.children.length > 0) { + const found = findParentBookmark(bookmark.children, id, bookmark.id); + if (found !== undefined) return found; + } + } + return undefined; // Not found at this level + } + + function removeBookmark(id) { + // Remove from top level + const index = bookmarks.findIndex(b => b.id === id); + if (index !== -1) { + bookmarks.splice(index, 1); + updateBookmarksUI(); + return; + } + + // Remove from children + function removeFromChildren(bookmarkArray, id) { + for (const bookmark of bookmarkArray) { + const childIndex = bookmark.children.findIndex(b => b.id === id); + if (childIndex !== -1) { + bookmark.children.splice(childIndex, 1); + return true; + } + if (removeFromChildren(bookmark.children, id)) { + return true; + } + } + return false; + } + + if (removeFromChildren(bookmarks, id)) { + updateBookmarksUI(); + } + + // If no bookmarks left, show empty state + if (bookmarks.length === 0) { + showEmptyState(); + } + } + + function toggleBookmarkExpanded(id) { + const bookmark = findBookmark(bookmarks, id); + if (bookmark) { + bookmark.expanded = !bookmark.expanded; + updateBookmarksUI(); + } + } + + function updateBookmarkData() { + // Create a clean version without the IDs for submission + const cleanBookmarks = bookmarks.map(cleanBookmark); + bookmarkDataInput.value = JSON.stringify(cleanBookmarks); + } + + function cleanBookmark(bookmark) { + return { + title: bookmark.title, + pageNumber: bookmark.pageNumber, + children: bookmark.children.map(cleanBookmark) + }; + } + + function updateBookmarksUI() { + if (!bookmarksContainer) { + return; + } + + // Only clear the container if there are no error messages or loading indicators + if (!document.querySelector('#bookmarks-container .alert')) { + bookmarksContainer.innerHTML = ''; + } + + // Check if there are bookmarks to display + if (bookmarks.length === 0 && !document.querySelector('.empty-bookmarks')) { + showEmptyState(); + } else { + // Remove empty state if it exists and there are bookmarks + const emptyState = document.querySelector('.empty-bookmarks'); + if (emptyState && bookmarks.length > 0) { + emptyState.remove(); + } + + // Create bookmark elements + bookmarks.forEach(bookmark => { + const bookmarkElement = createBookmarkElement(bookmark); + bookmarksContainer.appendChild(bookmarkElement); + }); + } + + updateBookmarkData(); + } + + // Create the main bookmark element with collapsible interface + function createBookmarkElement(bookmark, level = 0) { + const bookmarkEl = document.createElement('div'); + bookmarkEl.className = 'bookmark-item'; + bookmarkEl.dataset.id = bookmark.id; + bookmarkEl.dataset.level = level; + + // Create the header (always visible part) + const header = createBookmarkHeader(bookmark, level); + bookmarkEl.appendChild(header); + + // Create the content (collapsible part) + const content = document.createElement('div'); + content.className = 'bookmark-content'; + if (!bookmark.expanded) { + content.style.display = 'none'; + } + + // Main input row + const inputRow = createInputRow(bookmark); + content.appendChild(inputRow); + bookmarkEl.appendChild(content); + + // Add children container if has children and expanded + if (bookmark.children && bookmark.children.length > 0) { + const childrenContainer = createChildrenContainer(bookmark, level); + if (bookmark.expanded) { + bookmarkEl.appendChild(childrenContainer); + } + } + + return bookmarkEl; + } + + // Create the header that's always visible + function createBookmarkHeader(bookmark, level) { + const header = document.createElement('div'); + header.className = 'bookmark-header'; + if (!bookmark.expanded) { + header.classList.add('collapsed'); + } + + // Left side of header with expand/collapse and info + const headerLeft = document.createElement('div'); + headerLeft.className = 'd-flex align-items-center'; + + // Toggle expand/collapse icon + const toggleIcon = document.createElement('span'); + toggleIcon.className = 'material-symbols-rounded toggle-icon me-2'; + toggleIcon.textContent = 'expand_more'; + toggleIcon.style.cursor = 'pointer'; + + // Only show toggle if has children + if (bookmark.children && bookmark.children.length > 0) { + headerLeft.appendChild(toggleIcon); + } else { + // Add spacer if no children + const spacer = document.createElement('span'); + spacer.style.width = '24px'; + spacer.style.display = 'inline-block'; + headerLeft.appendChild(spacer); + } + + // Level indicator for nested items + if (level > 0) { + // Add relationship indicator visual line + const relationshipIndicator = document.createElement('div'); + relationshipIndicator.className = 'bookmark-relationship-indicator'; + + const line = document.createElement('div'); + line.className = 'relationship-line'; + relationshipIndicator.appendChild(line); + + const arrow = document.createElement('div'); + arrow.className = 'relationship-arrow'; + relationshipIndicator.appendChild(arrow); + + header.appendChild(relationshipIndicator); + + // Text indicator + const levelIndicator = document.createElement('span'); + levelIndicator.className = 'bookmark-level-indicator'; + levelIndicator.textContent = `Child`; + headerLeft.appendChild(levelIndicator); + } + + // Title preview + const titlePreview = document.createElement('span'); + titlePreview.className = 'bookmark-title-preview'; + titlePreview.textContent = bookmark.title; + headerLeft.appendChild(titlePreview); + + // Page number preview + const pagePreview = document.createElement('span'); + pagePreview.className = 'bookmark-page-preview'; + pagePreview.textContent = `Page ${bookmark.pageNumber}`; + headerLeft.appendChild(pagePreview); + + // Right side of header with action buttons + const headerRight = document.createElement('div'); + headerRight.className = 'bookmark-actions-header'; + + // Quick add buttons with clear visual distinction + const quickAddChildButton = createButton('subdirectory_arrow_right', 'btn-add-child', 'Add child bookmark (nested under this)', function(e) { + e.preventDefault(); + e.stopPropagation(); + addBookmark(bookmark.id); + }); + + const quickAddSiblingButton = createButton('add', 'btn-add-sibling', 'Add sibling bookmark (same level)', function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Find parent of current bookmark + const parentId = findParentBookmark(bookmarks, bookmark.id); + addBookmark(parentId, '', bookmark.pageNumber); // Same level as current bookmark + }); + + // Quick remove button + const quickRemoveButton = createButton('delete', 'btn-outline-danger', 'Remove bookmark', function(e) { + e.preventDefault(); + e.stopPropagation(); + + if (confirm('Are you sure you want to remove this bookmark' + + (bookmark.children.length > 0 ? ' and all its children?' : '?'))) { + removeBookmark(bookmark.id); + } + }); + + headerRight.appendChild(quickAddChildButton); + headerRight.appendChild(quickAddSiblingButton); + headerRight.appendChild(quickRemoveButton); + + // Assemble header + header.appendChild(headerLeft); + header.appendChild(headerRight); + + // Add click handler for expansion toggle + header.addEventListener('click', function(e) { + // Only toggle if not clicking on buttons + if (!e.target.closest('button')) { + toggleBookmarkExpanded(bookmark.id); + } + }); + + return header; + } + + function createInputRow(bookmark) { + const row = document.createElement('div'); + row.className = 'row'; + + // Title input + row.appendChild(createTitleInputElement(bookmark)); + + // Page input + row.appendChild(createPageInputElement(bookmark)); + + return row; + } + + function createTitleInputElement(bookmark) { + const titleCol = document.createElement('div'); + titleCol.className = 'col-md-8'; + + const titleGroup = document.createElement('div'); + titleGroup.className = 'mb-3'; + + const titleLabel = document.createElement('label'); + titleLabel.textContent = 'Title'; + titleLabel.className = 'form-label'; + + const titleInput = document.createElement('input'); + titleInput.type = 'text'; + titleInput.className = 'form-control bookmark-title'; + titleInput.value = bookmark.title; + titleInput.addEventListener('input', function() { + bookmark.title = this.value; + updateBookmarkData(); + + // Also update the preview in the header + const header = titleInput.closest('.bookmark-item').querySelector('.bookmark-title-preview'); + if (header) { + header.textContent = this.value; + } + }); + + titleGroup.appendChild(titleLabel); + titleGroup.appendChild(titleInput); + titleCol.appendChild(titleGroup); + + return titleCol; + } + + function createPageInputElement(bookmark) { + const pageCol = document.createElement('div'); + pageCol.className = 'col-md-4'; + + const pageGroup = document.createElement('div'); + pageGroup.className = 'mb-3'; + + const pageLabel = document.createElement('label'); + pageLabel.textContent = 'Page'; + pageLabel.className = 'form-label'; + + const pageInput = document.createElement('input'); + pageInput.type = 'number'; + pageInput.className = 'form-control bookmark-page'; + pageInput.value = bookmark.pageNumber; + pageInput.min = 1; + pageInput.addEventListener('input', function() { + bookmark.pageNumber = parseInt(this.value) || 1; + updateBookmarkData(); + + // Also update the preview in the header + const header = pageInput.closest('.bookmark-item').querySelector('.bookmark-page-preview'); + if (header) { + header.textContent = `Page ${bookmark.pageNumber}`; + } + }); + + pageGroup.appendChild(pageLabel); + pageGroup.appendChild(pageInput); + pageCol.appendChild(pageGroup); + + return pageCol; + } + + function createButton(icon, className, title, clickHandler) { + const button = document.createElement('button'); + button.type = 'button'; + button.className = `btn ${className} btn-bookmark-action`; + button.innerHTML = `${icon}`; + button.title = title; + button.addEventListener('click', clickHandler); + return button; + } + + function createChildrenContainer(bookmark, level) { + const childrenContainer = document.createElement('div'); + childrenContainer.className = 'bookmark-children'; + + bookmark.children.forEach(child => { + childrenContainer.appendChild(createBookmarkElement(child, level + 1)); + }); + + return childrenContainer; + } + + // 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'; + + // Add tooltip to better explain it + addBookmarkBtn.title = 'Add a new top-level bookmark (not nested under any other bookmark)'; + + // Add icon to empty state button as well + const updateEmptyStateButton = function() { + const emptyStateBtn = document.querySelector('.btn-add-first-bookmark'); + if (emptyStateBtn) { + emptyStateBtn.innerHTML = 'add Add First Bookmark'; + emptyStateBtn.title = 'Add your first bookmark to the document'; + } + }; + + // Initialize with an empty state if no bookmarks + if (bookmarks.length === 0) { + showEmptyState(); + updateEmptyStateButton(); + } + + // Add visual enhancement to clearly show the top-level/child relationship + document.addEventListener('mouseover', function(e) { + // When hovering over add buttons, highlight their relationship targets + const button = e.target.closest('.btn-add-child, .btn-add-sibling'); + if (button) { + if (button.classList.contains('btn-add-child')) { + // Highlight parent-child relationship + const bookmarkItem = button.closest('.bookmark-item'); + if (bookmarkItem) { + bookmarkItem.style.boxShadow = '0 0 0 2px var(--btn-add-child-border, #198754)'; + } + } else if (button.classList.contains('btn-add-sibling')) { + // Highlight sibling relationship + const bookmarkItem = button.closest('.bookmark-item'); + if (bookmarkItem) { + // Find siblings + const parent = bookmarkItem.parentElement; + const siblings = parent.querySelectorAll(':scope > .bookmark-item'); + siblings.forEach(sibling => { + if (sibling !== bookmarkItem) { + sibling.style.boxShadow = '0 0 0 2px var(--btn-add-sibling-border, #0d6efd)'; + } + }); + } + } + } + }); + + document.addEventListener('mouseout', function(e) { + // Remove highlights when not hovering + const button = e.target.closest('.btn-add-child, .btn-add-sibling'); + if (button) { + // Remove all highlights + document.querySelectorAll('.bookmark-item').forEach(item => { + item.style.boxShadow = ''; + }); + } + }); +}); \ No newline at end of file diff --git a/src/main/resources/templates/edit-table-of-contents.html b/src/main/resources/templates/edit-table-of-contents.html index 373ba25f3..82148158b 100644 --- a/src/main/resources/templates/edit-table-of-contents.html +++ b/src/main/resources/templates/edit-table-of-contents.html @@ -5,46 +5,7 @@