refactor: formatting, indentation

This commit is contained in:
Lukas 2025-08-03 10:13:00 +02:00
parent 538e1534d5
commit 139c6ccfca
No known key found for this signature in database
2 changed files with 276 additions and 243 deletions

View File

@ -1,12 +1,12 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener("DOMContentLoaded", function () {
const bookmarksContainer = document.getElementById('bookmarks-container'); const bookmarksContainer = document.getElementById("bookmarks-container");
const addBookmarkBtn = document.getElementById('addBookmarkBtn'); const addBookmarkBtn = document.getElementById("addBookmarkBtn");
const bookmarkDataInput = document.getElementById('bookmarkData'); const bookmarkDataInput = document.getElementById("bookmarkData");
let bookmarks = []; let bookmarks = [];
let counter = 0; // Used for generating unique IDs let counter = 0; // Used for generating unique IDs
// Add event listener to the file input to extract existing bookmarks // Add event listener to the file input to extract existing bookmarks
document.getElementById('fileInput-input').addEventListener('change', async function(e) { document.getElementById("fileInput-input").addEventListener("change", async function (e) {
if (!e.target.files || e.target.files.length === 0) { if (!e.target.files || e.target.files.length === 0) {
return; return;
} }
@ -16,16 +16,16 @@ document.addEventListener('DOMContentLoaded', function() {
updateBookmarksUI(); updateBookmarksUI();
const formData = new FormData(); const formData = new FormData();
formData.append('file', e.target.files[0]); formData.append("file", e.target.files[0]);
// Show loading indicator // Show loading indicator
showLoadingIndicator(); showLoadingIndicator();
try { try {
// Call the API to extract bookmarks using fetchWithCsrf for CSRF protection // Call the API to extract bookmarks using fetchWithCsrf for CSRF protection
const response = await fetchWithCsrf('/api/v1/general/extract-bookmarks', { const response = await fetchWithCsrf("/api/v1/general/extract-bookmarks", {
method: 'POST', method: "POST",
body: formData body: formData,
}); });
if (!response.ok) { if (!response.ok) {
@ -42,7 +42,7 @@ document.addEventListener('DOMContentLoaded', function() {
} }
} catch (error) { } catch (error) {
// Show error message // Show error message
showErrorMessage('Failed to extract bookmarks. You can still create new ones.'); showErrorMessage("Failed to extract bookmarks. You can still create new ones.");
// Add a default bookmark if no bookmarks and error // Add a default bookmark if no bookmarks and error
if (bookmarks.length === 0) { if (bookmarks.length === 0) {
@ -58,31 +58,31 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
function showLoadingIndicator() { function showLoadingIndicator() {
const loadingEl = document.createElement('div'); const loadingEl = document.createElement("div");
loadingEl.className = 'alert alert-info'; loadingEl.className = "alert alert-info";
loadingEl.textContent = 'Loading bookmarks from PDF...'; loadingEl.textContent = "Loading bookmarks from PDF...";
loadingEl.id = 'loading-bookmarks'; loadingEl.id = "loading-bookmarks";
bookmarksContainer.innerHTML = ''; bookmarksContainer.innerHTML = "";
bookmarksContainer.appendChild(loadingEl); bookmarksContainer.appendChild(loadingEl);
} }
function removeLoadingIndicator() { function removeLoadingIndicator() {
const loadingEl = document.getElementById('loading-bookmarks'); const loadingEl = document.getElementById("loading-bookmarks");
if (loadingEl) { if (loadingEl) {
loadingEl.remove(); loadingEl.remove();
} }
} }
function showErrorMessage(message) { function showErrorMessage(message) {
const errorEl = document.createElement('div'); const errorEl = document.createElement("div");
errorEl.className = 'alert alert-danger'; errorEl.className = "alert alert-danger";
errorEl.textContent = message; errorEl.textContent = message;
bookmarksContainer.appendChild(errorEl); bookmarksContainer.appendChild(errorEl);
} }
function showEmptyState() { function showEmptyState() {
const emptyStateEl = document.createElement('div'); const emptyStateEl = document.createElement("div");
emptyStateEl.className = 'empty-bookmarks mb-3'; emptyStateEl.className = "empty-bookmarks mb-3";
emptyStateEl.innerHTML = ` emptyStateEl.innerHTML = `
<span class="material-symbols-rounded mb-2" style="font-size: 48px;">bookmark_add</span> <span class="material-symbols-rounded mb-2" style="font-size: 48px;">bookmark_add</span>
<h5>No bookmarks found</h5> <h5>No bookmarks found</h5>
@ -93,8 +93,8 @@ document.addEventListener('DOMContentLoaded', function() {
`; `;
// Add event listener to the "Add First Bookmark" button // Add event listener to the "Add First Bookmark" button
emptyStateEl.querySelector('.btn-add-first-bookmark').addEventListener('click', function() { emptyStateEl.querySelector(".btn-add-first-bookmark").addEventListener("click", function () {
addBookmark(null, 'New Bookmark', 1); addBookmark(null, "New Bookmark", 1);
emptyStateEl.remove(); emptyStateEl.remove();
}); });
@ -106,15 +106,15 @@ document.addEventListener('DOMContentLoaded', function() {
counter++; counter++;
const result = { const result = {
id: Date.now() + counter, // Generate a unique ID id: Date.now() + counter, // Generate a unique ID
title: bookmark.title || 'Untitled Bookmark', title: bookmark.title || "Untitled Bookmark",
pageNumber: bookmark.pageNumber || 1, pageNumber: bookmark.pageNumber || 1,
children: [], children: [],
expanded: false // All bookmarks start collapsed for better visibility expanded: false, // All bookmarks start collapsed for better visibility
}; };
// Convert children recursively // Convert children recursively
if (bookmark.children && bookmark.children.length > 0) { if (bookmark.children && bookmark.children.length > 0) {
result.children = bookmark.children.map(child => { result.children = bookmark.children.map((child) => {
return convertExtractedBookmark(child); return convertExtractedBookmark(child);
}); });
} }
@ -123,24 +123,24 @@ document.addEventListener('DOMContentLoaded', function() {
} }
// Add bookmark button click handler // Add bookmark button click handler
addBookmarkBtn.addEventListener('click', function(e) { addBookmarkBtn.addEventListener("click", function (e) {
e.preventDefault(); e.preventDefault();
addBookmark(); addBookmark();
}); });
// Add form submit handler to update JSON data // Add form submit handler to update JSON data
document.getElementById('editTocForm').addEventListener('submit', function() { document.getElementById("editTocForm").addEventListener("submit", function () {
updateBookmarkData(); updateBookmarkData();
}); });
function addBookmark(parent = null, title = '', pageNumber = 1) { function addBookmark(parent = null, title = "", pageNumber = 1) {
counter++; counter++;
const newBookmark = { const newBookmark = {
id: Date.now() + counter, id: Date.now() + counter,
title: title || 'New Bookmark', title: title || "New Bookmark",
pageNumber: pageNumber || 1, pageNumber: pageNumber || 1,
children: [], children: [],
expanded: false // New bookmarks start collapsed expanded: false, // New bookmarks start collapsed
}; };
if (parent === null) { if (parent === null) {
@ -162,13 +162,13 @@ document.addEventListener('DOMContentLoaded', function() {
setTimeout(() => { setTimeout(() => {
const newElement = document.querySelector(`[data-id="${newBookmark.id}"]`); const newElement = document.querySelector(`[data-id="${newBookmark.id}"]`);
if (newElement) { if (newElement) {
const titleInput = newElement.querySelector('.bookmark-title'); const titleInput = newElement.querySelector(".bookmark-title");
if (titleInput) { if (titleInput) {
titleInput.focus(); titleInput.focus();
titleInput.select(); titleInput.select();
} }
// Scroll to the new element // Scroll to the new element
newElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); newElement.scrollIntoView({ behavior: "smooth", block: "center" });
} }
}, 50); }, 50);
} }
@ -203,7 +203,7 @@ document.addEventListener('DOMContentLoaded', function() {
function removeBookmark(id) { function removeBookmark(id) {
// Remove from top level // Remove from top level
const index = bookmarks.findIndex(b => b.id === id); const index = bookmarks.findIndex((b) => b.id === id);
if (index !== -1) { if (index !== -1) {
bookmarks.splice(index, 1); bookmarks.splice(index, 1);
updateBookmarksUI(); updateBookmarksUI();
@ -213,7 +213,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Remove from children // Remove from children
function removeFromChildren(bookmarkArray, id) { function removeFromChildren(bookmarkArray, id) {
for (const bookmark of bookmarkArray) { for (const bookmark of bookmarkArray) {
const childIndex = bookmark.children.findIndex(b => b.id === id); const childIndex = bookmark.children.findIndex((b) => b.id === id);
if (childIndex !== -1) { if (childIndex !== -1) {
bookmark.children.splice(childIndex, 1); bookmark.children.splice(childIndex, 1);
return true; return true;
@ -253,7 +253,7 @@ document.addEventListener('DOMContentLoaded', function() {
return { return {
title: bookmark.title, title: bookmark.title,
pageNumber: bookmark.pageNumber, pageNumber: bookmark.pageNumber,
children: bookmark.children.map(cleanBookmark) children: bookmark.children.map(cleanBookmark),
}; };
} }
@ -263,22 +263,22 @@ document.addEventListener('DOMContentLoaded', function() {
} }
// Only clear the container if there are no error messages or loading indicators // Only clear the container if there are no error messages or loading indicators
if (!document.querySelector('#bookmarks-container .alert')) { if (!document.querySelector("#bookmarks-container .alert")) {
bookmarksContainer.innerHTML = ''; bookmarksContainer.innerHTML = "";
} }
// Check if there are bookmarks to display // Check if there are bookmarks to display
if (bookmarks.length === 0 && !document.querySelector('.empty-bookmarks')) { if (bookmarks.length === 0 && !document.querySelector(".empty-bookmarks")) {
showEmptyState(); showEmptyState();
} else { } else {
// Remove empty state if it exists and there are bookmarks // Remove empty state if it exists and there are bookmarks
const emptyState = document.querySelector('.empty-bookmarks'); const emptyState = document.querySelector(".empty-bookmarks");
if (emptyState && bookmarks.length > 0) { if (emptyState && bookmarks.length > 0) {
emptyState.remove(); emptyState.remove();
} }
// Create bookmark elements // Create bookmark elements
bookmarks.forEach(bookmark => { bookmarks.forEach((bookmark) => {
const bookmarkElement = createBookmarkElement(bookmark); const bookmarkElement = createBookmarkElement(bookmark);
bookmarksContainer.appendChild(bookmarkElement); bookmarksContainer.appendChild(bookmarkElement);
}); });
@ -287,15 +287,15 @@ document.addEventListener('DOMContentLoaded', function() {
updateBookmarkData(); updateBookmarkData();
// Initialize tooltips for dynamically added elements // Initialize tooltips for dynamically added elements
if (typeof $ !== 'undefined') { if (typeof $ !== "undefined") {
$('[data-bs-toggle="tooltip"]').tooltip(); $('[data-bs-toggle="tooltip"]').tooltip();
} }
} }
// Create the main bookmark element with collapsible interface // Create the main bookmark element with collapsible interface
function createBookmarkElement(bookmark, level = 0) { function createBookmarkElement(bookmark, level = 0) {
const bookmarkEl = document.createElement('div'); const bookmarkEl = document.createElement("div");
bookmarkEl.className = 'bookmark-item'; bookmarkEl.className = "bookmark-item";
bookmarkEl.dataset.id = bookmark.id; bookmarkEl.dataset.id = bookmark.id;
bookmarkEl.dataset.level = level; bookmarkEl.dataset.level = level;
@ -304,10 +304,10 @@ document.addEventListener('DOMContentLoaded', function() {
bookmarkEl.appendChild(header); bookmarkEl.appendChild(header);
// Create the content (collapsible part) // Create the content (collapsible part)
const content = document.createElement('div'); const content = document.createElement("div");
content.className = 'bookmark-content'; content.className = "bookmark-content";
if (!bookmark.expanded) { if (!bookmark.expanded) {
content.style.display = 'none'; content.style.display = "none";
} }
// Main input row // Main input row
@ -328,48 +328,48 @@ document.addEventListener('DOMContentLoaded', function() {
// Create the header that's always visible // Create the header that's always visible
function createBookmarkHeader(bookmark, level) { function createBookmarkHeader(bookmark, level) {
const header = document.createElement('div'); const header = document.createElement("div");
header.className = 'bookmark-header'; header.className = "bookmark-header";
if (!bookmark.expanded) { if (!bookmark.expanded) {
header.classList.add('collapsed'); header.classList.add("collapsed");
} }
// Left side of header with expand/collapse and info // Left side of header with expand/collapse and info
const headerLeft = document.createElement('div'); const headerLeft = document.createElement("div");
headerLeft.className = 'd-flex align-items-center'; headerLeft.className = "d-flex align-items-center";
// Toggle expand/collapse icon with child count // Toggle expand/collapse icon with child count
const toggleContainer = document.createElement('div'); const toggleContainer = document.createElement("div");
toggleContainer.className = 'd-flex align-items-center'; toggleContainer.className = "d-flex align-items-center";
toggleContainer.style.marginRight = '8px'; toggleContainer.style.marginRight = "8px";
// Only show toggle if has children // Only show toggle if has children
if (bookmark.children && bookmark.children.length > 0) { if (bookmark.children && bookmark.children.length > 0) {
// Create toggle icon // Create toggle icon
const toggleIcon = document.createElement('span'); const toggleIcon = document.createElement("span");
toggleIcon.className = 'material-symbols-rounded toggle-icon me-1'; toggleIcon.className = "material-symbols-rounded toggle-icon me-1";
toggleIcon.textContent = 'expand_more'; toggleIcon.textContent = "expand_more";
toggleIcon.style.cursor = 'pointer'; toggleIcon.style.cursor = "pointer";
toggleContainer.appendChild(toggleIcon); toggleContainer.appendChild(toggleIcon);
// Add child count indicator // Add child count indicator
const childCount = document.createElement('span'); const childCount = document.createElement("span");
childCount.className = 'badge rounded-pill'; childCount.className = "badge rounded-pill";
// Use theme-appropriate badge color // Use theme-appropriate badge color
const isDarkMode = document.documentElement.getAttribute('data-bs-theme') === 'dark'; const isDarkMode = document.documentElement.getAttribute("data-bs-theme") === "dark";
childCount.classList.add(isDarkMode ? 'bg-info' : 'bg-secondary'); childCount.classList.add(isDarkMode ? "bg-info" : "bg-secondary");
childCount.style.fontSize = '0.7rem'; childCount.style.fontSize = "0.7rem";
childCount.style.padding = '0.2em 0.5em'; childCount.style.padding = "0.2em 0.5em";
childCount.textContent = bookmark.children.length; childCount.textContent = bookmark.children.length;
childCount.setAttribute('data-bs-toggle', 'tooltip'); childCount.setAttribute("data-bs-toggle", "tooltip");
childCount.setAttribute('data-bs-placement', 'top'); childCount.setAttribute("data-bs-placement", "top");
childCount.title = `${bookmark.children.length} child bookmark${bookmark.children.length > 1 ? 's' : ''}`; childCount.title = `${bookmark.children.length} child bookmark${bookmark.children.length > 1 ? "s" : ""}`;
toggleContainer.appendChild(childCount); toggleContainer.appendChild(childCount);
} else { } else {
// Add spacer if no children // Add spacer if no children
const spacer = document.createElement('span'); const spacer = document.createElement("span");
spacer.style.width = '24px'; spacer.style.width = "24px";
spacer.style.display = 'inline-block'; spacer.style.display = "inline-block";
toggleContainer.appendChild(spacer); toggleContainer.appendChild(spacer);
} }
@ -378,65 +378,68 @@ document.addEventListener('DOMContentLoaded', function() {
// Level indicator for nested items // Level indicator for nested items
if (level > 0) { if (level > 0) {
// Add relationship indicator visual line // Add relationship indicator visual line
const relationshipIndicator = document.createElement('div'); const relationshipIndicator = document.createElement("div");
relationshipIndicator.className = 'bookmark-relationship-indicator'; relationshipIndicator.className = "bookmark-relationship-indicator";
const line = document.createElement('div'); const line = document.createElement("div");
line.className = 'relationship-line'; line.className = "relationship-line";
relationshipIndicator.appendChild(line); relationshipIndicator.appendChild(line);
const arrow = document.createElement('div'); const arrow = document.createElement("div");
arrow.className = 'relationship-arrow'; arrow.className = "relationship-arrow";
relationshipIndicator.appendChild(arrow); relationshipIndicator.appendChild(arrow);
header.appendChild(relationshipIndicator); header.appendChild(relationshipIndicator);
// Text indicator // Text indicator
const levelIndicator = document.createElement('span'); const levelIndicator = document.createElement("span");
levelIndicator.className = 'bookmark-level-indicator'; levelIndicator.className = "bookmark-level-indicator";
levelIndicator.textContent = `Child`; levelIndicator.textContent = `Child`;
headerLeft.appendChild(levelIndicator); headerLeft.appendChild(levelIndicator);
} }
// Title preview // Title preview
const titlePreview = document.createElement('span'); const titlePreview = document.createElement("span");
titlePreview.className = 'bookmark-title-preview'; titlePreview.className = "bookmark-title-preview";
titlePreview.textContent = bookmark.title; titlePreview.textContent = bookmark.title;
headerLeft.appendChild(titlePreview); headerLeft.appendChild(titlePreview);
// Page number preview // Page number preview
const pagePreview = document.createElement('span'); const pagePreview = document.createElement("span");
pagePreview.className = 'bookmark-page-preview'; pagePreview.className = "bookmark-page-preview";
pagePreview.textContent = `Page ${bookmark.pageNumber}`; pagePreview.textContent = `Page ${bookmark.pageNumber}`;
headerLeft.appendChild(pagePreview); headerLeft.appendChild(pagePreview);
// Right side of header with action buttons // Right side of header with action buttons
const headerRight = document.createElement('div'); const headerRight = document.createElement("div");
headerRight.className = 'bookmark-actions-header'; headerRight.className = "bookmark-actions-header";
// Quick add buttons with clear visual distinction - using Stirling-PDF's tooltip system // Quick add buttons with clear visual distinction - using Stirling-PDF's tooltip system
const quickAddChildButton = createButton('subdirectory_arrow_right', 'btn-add-child', 'Add child bookmark', function(e) { const quickAddChildButton = createButton("subdirectory_arrow_right", "btn-add-child", "Add child bookmark", function (e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
addBookmark(bookmark.id); addBookmark(bookmark.id);
}); });
const quickAddSiblingButton = createButton('add', 'btn-add-sibling', 'Add sibling bookmark', function(e) { const quickAddSiblingButton = createButton("add", "btn-add-sibling", "Add sibling bookmark", function (e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
// Find parent of current bookmark // Find parent of current bookmark
const parentId = findParentBookmark(bookmarks, bookmark.id); const parentId = findParentBookmark(bookmarks, bookmark.id);
addBookmark(parentId, '', bookmark.pageNumber); // Same level as current bookmark addBookmark(parentId, "", bookmark.pageNumber); // Same level as current bookmark
}); });
// Quick remove button // Quick remove button
const quickRemoveButton = createButton('delete', 'btn-outline-danger', 'Remove bookmark', function(e) { const quickRemoveButton = createButton("delete", "btn-outline-danger", "Remove bookmark", function (e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (confirm('Are you sure you want to remove this bookmark' + if (
(bookmark.children.length > 0 ? ' and all its children?' : '?'))) { confirm(
"Are you sure you want to remove this bookmark" + (bookmark.children.length > 0 ? " and all its children?" : "?")
)
) {
removeBookmark(bookmark.id); removeBookmark(bookmark.id);
} }
}); });
@ -450,9 +453,9 @@ document.addEventListener('DOMContentLoaded', function() {
header.appendChild(headerRight); header.appendChild(headerRight);
// Add click handler for expansion toggle // Add click handler for expansion toggle
header.addEventListener('click', function(e) { header.addEventListener("click", function (e) {
// Only toggle if not clicking on buttons // Only toggle if not clicking on buttons
if (!e.target.closest('button')) { if (!e.target.closest("button")) {
toggleBookmarkExpanded(bookmark.id); toggleBookmarkExpanded(bookmark.id);
} }
}); });
@ -461,8 +464,8 @@ document.addEventListener('DOMContentLoaded', function() {
} }
function createInputRow(bookmark) { function createInputRow(bookmark) {
const row = document.createElement('div'); const row = document.createElement("div");
row.className = 'row'; row.className = "row";
// Title input // Title input
row.appendChild(createTitleInputElement(bookmark)); row.appendChild(createTitleInputElement(bookmark));
@ -474,26 +477,26 @@ document.addEventListener('DOMContentLoaded', function() {
} }
function createTitleInputElement(bookmark) { function createTitleInputElement(bookmark) {
const titleCol = document.createElement('div'); const titleCol = document.createElement("div");
titleCol.className = 'col-md-8'; titleCol.className = "col-md-8";
const titleGroup = document.createElement('div'); const titleGroup = document.createElement("div");
titleGroup.className = 'mb-3'; titleGroup.className = "mb-3";
const titleLabel = document.createElement('label'); const titleLabel = document.createElement("label");
titleLabel.textContent = 'Title'; titleLabel.textContent = "Title";
titleLabel.className = 'form-label'; titleLabel.className = "form-label";
const titleInput = document.createElement('input'); const titleInput = document.createElement("input");
titleInput.type = 'text'; titleInput.type = "text";
titleInput.className = 'form-control bookmark-title'; titleInput.className = "form-control bookmark-title";
titleInput.value = bookmark.title; titleInput.value = bookmark.title;
titleInput.addEventListener('input', function() { titleInput.addEventListener("input", function () {
bookmark.title = this.value; bookmark.title = this.value;
updateBookmarkData(); updateBookmarkData();
// Also update the preview in the header // Also update the preview in the header
const header = titleInput.closest('.bookmark-item').querySelector('.bookmark-title-preview'); const header = titleInput.closest(".bookmark-item").querySelector(".bookmark-title-preview");
if (header) { if (header) {
header.textContent = this.value; header.textContent = this.value;
} }
@ -507,27 +510,27 @@ document.addEventListener('DOMContentLoaded', function() {
} }
function createPageInputElement(bookmark) { function createPageInputElement(bookmark) {
const pageCol = document.createElement('div'); const pageCol = document.createElement("div");
pageCol.className = 'col-md-4'; pageCol.className = "col-md-4";
const pageGroup = document.createElement('div'); const pageGroup = document.createElement("div");
pageGroup.className = 'mb-3'; pageGroup.className = "mb-3";
const pageLabel = document.createElement('label'); const pageLabel = document.createElement("label");
pageLabel.textContent = 'Page'; pageLabel.textContent = "Page";
pageLabel.className = 'form-label'; pageLabel.className = "form-label";
const pageInput = document.createElement('input'); const pageInput = document.createElement("input");
pageInput.type = 'number'; pageInput.type = "number";
pageInput.className = 'form-control bookmark-page'; pageInput.className = "form-control bookmark-page";
pageInput.value = bookmark.pageNumber; pageInput.value = bookmark.pageNumber;
pageInput.min = 1; pageInput.min = 1;
pageInput.addEventListener('input', function() { pageInput.addEventListener("input", function () {
bookmark.pageNumber = parseInt(this.value) || 1; bookmark.pageNumber = parseInt(this.value) || 1;
updateBookmarkData(); updateBookmarkData();
// Also update the preview in the header // Also update the preview in the header
const header = pageInput.closest('.bookmark-item').querySelector('.bookmark-page-preview'); const header = pageInput.closest(".bookmark-item").querySelector(".bookmark-page-preview");
if (header) { if (header) {
header.textContent = `Page ${bookmark.pageNumber}`; header.textContent = `Page ${bookmark.pageNumber}`;
} }
@ -541,25 +544,25 @@ document.addEventListener('DOMContentLoaded', function() {
} }
function createButton(icon, className, title, clickHandler) { function createButton(icon, className, title, clickHandler) {
const button = document.createElement('button'); const button = document.createElement("button");
button.type = 'button'; button.type = "button";
button.className = `btn ${className} btn-bookmark-action`; button.className = `btn ${className} btn-bookmark-action`;
button.innerHTML = `<span class="material-symbols-rounded">${icon}</span>`; button.innerHTML = `<span class="material-symbols-rounded">${icon}</span>`;
// Use Bootstrap tooltips // Use Bootstrap tooltips
button.setAttribute('data-bs-toggle', 'tooltip'); button.setAttribute("data-bs-toggle", "tooltip");
button.setAttribute('data-bs-placement', 'top'); button.setAttribute("data-bs-placement", "top");
button.title = title; button.title = title;
button.addEventListener('click', clickHandler); button.addEventListener("click", clickHandler);
return button; return button;
} }
function createChildrenContainer(bookmark, level) { function createChildrenContainer(bookmark, level) {
const childrenContainer = document.createElement('div'); const childrenContainer = document.createElement("div");
childrenContainer.className = 'bookmark-children'; childrenContainer.className = "bookmark-children";
bookmark.children.forEach(child => { bookmark.children.forEach((child) => {
childrenContainer.appendChild(createBookmarkElement(child, level + 1)); childrenContainer.appendChild(createBookmarkElement(child, level + 1));
}); });
@ -568,24 +571,24 @@ document.addEventListener('DOMContentLoaded', function() {
// Update the add bookmark button appearance with clear visual cue // Update the add bookmark button appearance with clear visual cue
addBookmarkBtn.innerHTML = '<span class="material-symbols-rounded">add</span> Add Top-level Bookmark'; addBookmarkBtn.innerHTML = '<span class="material-symbols-rounded">add</span> Add Top-level Bookmark';
addBookmarkBtn.className = 'btn btn-primary btn-add-bookmark top-level'; addBookmarkBtn.className = "btn btn-primary btn-add-bookmark top-level";
// Use Bootstrap tooltips // Use Bootstrap tooltips
addBookmarkBtn.setAttribute('data-bs-toggle', 'tooltip'); addBookmarkBtn.setAttribute("data-bs-toggle", "tooltip");
addBookmarkBtn.setAttribute('data-bs-placement', 'top'); addBookmarkBtn.setAttribute("data-bs-placement", "top");
addBookmarkBtn.title = 'Add a new top-level bookmark'; addBookmarkBtn.title = "Add a new top-level bookmark";
// Add icon to empty state button as well // Add icon to empty state button as well
const updateEmptyStateButton = function() { const updateEmptyStateButton = function () {
const emptyStateBtn = document.querySelector('.btn-add-first-bookmark'); const emptyStateBtn = document.querySelector(".btn-add-first-bookmark");
if (emptyStateBtn) { if (emptyStateBtn) {
emptyStateBtn.innerHTML = '<span class="material-symbols-rounded">add</span> Add First Bookmark'; emptyStateBtn.innerHTML = '<span class="material-symbols-rounded">add</span> Add First Bookmark';
emptyStateBtn.setAttribute('data-bs-toggle', 'tooltip'); emptyStateBtn.setAttribute("data-bs-toggle", "tooltip");
emptyStateBtn.setAttribute('data-bs-placement', 'top'); emptyStateBtn.setAttribute("data-bs-placement", "top");
emptyStateBtn.title = 'Add first bookmark'; emptyStateBtn.title = "Add first bookmark";
// Initialize tooltips for the empty state button // Initialize tooltips for the empty state button
if (typeof $ !== 'undefined') { if (typeof $ !== "undefined") {
$('[data-bs-toggle="tooltip"]').tooltip(); $('[data-bs-toggle="tooltip"]').tooltip();
} }
} }
@ -601,8 +604,8 @@ document.addEventListener('DOMContentLoaded', function() {
function flashButtonSuccess(button) { function flashButtonSuccess(button) {
const originalClass = button.className; const originalClass = button.className;
button.classList.remove('btn-outline-primary'); button.classList.remove("btn-outline-primary");
button.classList.add('btn-success', 'success-flash'); button.classList.add("btn-success", "success-flash");
setTimeout(() => { setTimeout(() => {
button.className = originalClass; button.className = originalClass;
@ -610,7 +613,7 @@ document.addEventListener('DOMContentLoaded', function() {
} }
async function importBookmarkStringFromClipboard() { async function importBookmarkStringFromClipboard() {
const button = document.getElementById('importBookmarksBtn'); const button = document.getElementById("importBookmarksBtn");
try { try {
const newBookmarkDataString = await navigator.clipboard.readText(); const newBookmarkDataString = await navigator.clipboard.readText();
const newBookmarkData = JSON.parse(newBookmarkDataString); const newBookmarkData = JSON.parse(newBookmarkDataString);
@ -629,7 +632,7 @@ document.addEventListener('DOMContentLoaded', function() {
} }
async function exportBookmarkStringToClipboard() { async function exportBookmarkStringToClipboard() {
const button = document.getElementById('exportBookmarksBtn'); const button = document.getElementById("exportBookmarksBtn");
const bookmarkData = bookmarkDataInput.value; const bookmarkData = bookmarkDataInput.value;
try { try {
await navigator.clipboard.writeText(bookmarkData); await navigator.clipboard.writeText(bookmarkData);
@ -640,25 +643,25 @@ document.addEventListener('DOMContentLoaded', function() {
} }
// Add event listeners for import/export buttons // Add event listeners for import/export buttons
const importBookmarksBtn = document.getElementById('importBookmarksBtn'); const importBookmarksBtn = document.getElementById("importBookmarksBtn");
const exportBookmarksBtn = document.getElementById('exportBookmarksBtn'); const exportBookmarksBtn = document.getElementById("exportBookmarksBtn");
importBookmarksBtn.addEventListener('click', importBookmarkStringFromClipboard); importBookmarksBtn.addEventListener("click", importBookmarkStringFromClipboard);
exportBookmarksBtn.addEventListener('click', exportBookmarkStringToClipboard); exportBookmarksBtn.addEventListener("click", exportBookmarkStringToClipboard);
// display import/export buttons if supported // display import/export buttons if supported
if (navigator.clipboard && navigator.clipboard.writeText && navigator.clipboard.readText) { if (navigator.clipboard && navigator.clipboard.writeText && navigator.clipboard.readText) {
importBookmarksBtn.classList.remove('d-none'); importBookmarksBtn.classList.remove("d-none");
exportBookmarksBtn.classList.remove('d-none'); exportBookmarksBtn.classList.remove("d-none");
} }
// Listen for theme changes to update badge colors // Listen for theme changes to update badge colors
const observer = new MutationObserver(function(mutations) { const observer = new MutationObserver(function (mutations) {
mutations.forEach(function(mutation) { mutations.forEach(function (mutation) {
if (mutation.attributeName === 'data-bs-theme') { if (mutation.attributeName === "data-bs-theme") {
const isDarkMode = document.documentElement.getAttribute('data-bs-theme') === 'dark'; const isDarkMode = document.documentElement.getAttribute("data-bs-theme") === "dark";
document.querySelectorAll('.badge').forEach(badge => { document.querySelectorAll(".badge").forEach((badge) => {
badge.classList.remove('bg-secondary', 'bg-info'); badge.classList.remove("bg-secondary", "bg-info");
badge.classList.add(isDarkMode ? 'bg-info' : 'bg-secondary'); badge.classList.add(isDarkMode ? "bg-info" : "bg-secondary");
}); });
} }
}); });
@ -667,26 +670,26 @@ document.addEventListener('DOMContentLoaded', function() {
observer.observe(document.documentElement, { attributes: true }); observer.observe(document.documentElement, { attributes: true });
// Add visual enhancement to clearly show the top-level/child relationship // Add visual enhancement to clearly show the top-level/child relationship
document.addEventListener('mouseover', function(e) { document.addEventListener("mouseover", function (e) {
// When hovering over add buttons, highlight their relationship targets // When hovering over add buttons, highlight their relationship targets
const button = e.target.closest('.btn-add-child, .btn-add-sibling'); const button = e.target.closest(".btn-add-child, .btn-add-sibling");
if (button) { if (button) {
if (button.classList.contains('btn-add-child')) { if (button.classList.contains("btn-add-child")) {
// Highlight parent-child relationship // Highlight parent-child relationship
const bookmarkItem = button.closest('.bookmark-item'); const bookmarkItem = button.closest(".bookmark-item");
if (bookmarkItem) { if (bookmarkItem) {
bookmarkItem.style.boxShadow = '0 0 0 2px var(--btn-add-child-border, #198754)'; bookmarkItem.style.boxShadow = "0 0 0 2px var(--btn-add-child-border, #198754)";
} }
} else if (button.classList.contains('btn-add-sibling')) { } else if (button.classList.contains("btn-add-sibling")) {
// Highlight sibling relationship // Highlight sibling relationship
const bookmarkItem = button.closest('.bookmark-item'); const bookmarkItem = button.closest(".bookmark-item");
if (bookmarkItem) { if (bookmarkItem) {
// Find siblings // Find siblings
const parent = bookmarkItem.parentElement; const parent = bookmarkItem.parentElement;
const siblings = parent.querySelectorAll(':scope > .bookmark-item'); const siblings = parent.querySelectorAll(":scope > .bookmark-item");
siblings.forEach(sibling => { siblings.forEach((sibling) => {
if (sibling !== bookmarkItem) { if (sibling !== bookmarkItem) {
sibling.style.boxShadow = '0 0 0 2px var(--btn-add-sibling-border, #0d6efd)'; sibling.style.boxShadow = "0 0 0 2px var(--btn-add-sibling-border, #0d6efd)";
} }
}); });
} }
@ -694,13 +697,13 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
document.addEventListener('mouseout', function(e) { document.addEventListener("mouseout", function (e) {
// Remove highlights when not hovering // Remove highlights when not hovering
const button = e.target.closest('.btn-add-child, .btn-add-sibling'); const button = e.target.closest(".btn-add-child, .btn-add-sibling");
if (button) { if (button) {
// Remove all highlights // Remove all highlights
document.querySelectorAll('.bookmark-item').forEach(item => { document.querySelectorAll(".bookmark-item").forEach((item) => {
item.style.boxShadow = ''; item.style.boxShadow = "";
}); });
} }
}); });

View File

@ -1,93 +1,123 @@
<!DOCTYPE html> <!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" <html th:lang="${#locale.language}"
th:dir="#{language.direction}"
th:data-language="${#locale.toString()}"
xmlns:th="https://www.thymeleaf.org"> xmlns:th="https://www.thymeleaf.org">
<head> <head>
<th:block th:insert="~{fragments/common :: head(title=#{editTableOfContents.title}, header=#{editTableOfContents.header})}"> <th:block
</th:block> th:insert="~{fragments/common :: head(title=#{editTableOfContents.title}, header=#{editTableOfContents.header})}">
<link rel="stylesheet" th:href="@{'/css/edit-table-of-contents.css'}"> </th:block>
</head> <link rel="stylesheet"
th:href="@{'/css/edit-table-of-contents.css'}">
</head>
<body> <body>
<div id="page-container"> <div id="page-container">
<div id="content-wrap"> <div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block> <th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br> <br><br>
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8 bg-card"> <div class="col-md-8 bg-card">
<div class="tool-header"> <div class="tool-header">
<span class="material-symbols-rounded tool-header-icon edit">bookmark_add</span> <span class="material-symbols-rounded tool-header-icon edit">bookmark_add</span>
<span class="tool-header-text" th:text="#{editTableOfContents.header}"></span> <span class="tool-header-text"
</div> th:text="#{editTableOfContents.header}"></span>
<form th:action="@{'/api/v1/general/edit-table-of-contents'}" method="post" enctype="multipart/form-data" id="editTocForm">
<div
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div> </div>
<form th:action="@{'/api/v1/general/edit-table-of-contents'}"
<div class="mb-3 form-check"> method="post"
<input type="checkbox" class="form-check-input" id="replaceExisting" name="replaceExisting" checked> enctype="multipart/form-data"
<label class="form-check-label" for="replaceExisting" id="editTocForm">
th:text="#{editTableOfContents.replaceExisting}"></label> <div
<input type="hidden" name="replaceExisting" value="false" /> th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
</div>
<div class="bookmark-editor">
<h5 th:text="#{editTableOfContents.editorTitle}"></h5>
<p th:text="#{editTableOfContents.editorDesc}"></p>
<div id="bookmarks-container">
<!-- Bookmarks will be added here dynamically -->
</div> </div>
<div class="bookmark-actions"> <div class="mb-3 form-check">
<button type="button" id="addBookmarkBtn" class="btn btn-outline-primary" th:text="#{editTableOfContents.addBookmark}"></button> <input type="checkbox"
<div class="d-flex flex-wrap justify-content-end gap-2"> class="form-check-input"
<button type="button" id="replaceExisting"
id="importBookmarksBtn" name="replaceExisting"
class="d-none btn btn-outline-primary" checked>
th:text="#{editTableOfContents.importBookmarks}" <label class="form-check-label"
th:data-bs-original-title="#{editTableOfContents.importBookmarksHint}" for="replaceExisting"
data-bs-toggle="tooltip" th:text="#{editTableOfContents.replaceExisting}"></label>
data-bs-placement="top"> <input type="hidden"
</button> name="replaceExisting"
<button type="button" value="false" />
id="exportBookmarksBtn" </div>
class="d-none btn btn-outline-primary"
th:text="#{editTableOfContents.exportBookmarks}" <div class="bookmark-editor">
th:data-bs-original-title="#{editTableOfContents.exportBookmarksHint}" <h5 th:text="#{editTableOfContents.editorTitle}"></h5>
data-bs-toggle="tooltip" <p th:text="#{editTableOfContents.editorDesc}"></p>
data-bs-placement="top">
</button> <div id="bookmarks-container">
<!-- Bookmarks will be added here dynamically -->
</div> </div>
<div class="bookmark-actions">
<button type="button"
id="addBookmarkBtn"
class="btn btn-outline-primary"
th:text="#{editTableOfContents.addBookmark}"></button>
<div class="d-flex flex-wrap justify-content-end gap-2">
<button type="button"
id="importBookmarksBtn"
class="d-none btn btn-outline-primary"
th:text="#{editTableOfContents.importBookmarks}"
th:data-bs-original-title="#{editTableOfContents.importBookmarksHint}"
data-bs-toggle="tooltip"
data-bs-placement="top">
</button>
<button type="button"
id="exportBookmarksBtn"
class="d-none btn btn-outline-primary"
th:text="#{editTableOfContents.exportBookmarks}"
th:data-bs-original-title="#{editTableOfContents.exportBookmarksHint}"
data-bs-toggle="tooltip"
data-bs-placement="top">
</button>
</div>
</div>
<!-- Hidden field to store JSON data -->
<input type="hidden"
id="bookmarkData"
name="bookmarkData"
value="[]">
</div> </div>
<!-- Hidden field to store JSON data -->
<input type="hidden" id="bookmarkData" name="bookmarkData" value="[]">
</div>
<p> <p>
<a class="btn btn-outline-primary" data-bs-toggle="collapse" href="#info" role="button" <a class="btn btn-outline-primary"
aria-expanded="false" aria-controls="info" th:text="#{info}"></a> data-bs-toggle="collapse"
</p> href="#info"
<div class="collapse" id="info"> role="button"
<p th:text="#{editTableOfContents.desc.1}"></p> aria-expanded="false"
<p th:text="#{editTableOfContents.desc.2}"></p> aria-controls="info"
<p th:text="#{editTableOfContents.desc.3}"></p> th:text="#{info}"></a>
</div> </p>
<div class="collapse"
id="info">
<p th:text="#{editTableOfContents.desc.1}"></p>
<p th:text="#{editTableOfContents.desc.2}"></p>
<p th:text="#{editTableOfContents.desc.3}"></p>
</div>
<br> <br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{editTableOfContents.submit}"></button> <button type="submit"
</form> id="submitBtn"
class="btn btn-primary"
th:text="#{editTableOfContents.submit}"></button>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
<script th:src="@{'/js/pages/edit-table-of-contents.js'}"></script> <script th:src="@{'/js/pages/edit-table-of-contents.js'}"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Initialize Bootstrap tooltips // Initialize Bootstrap tooltips
if (typeof $ !== 'undefined') { if (typeof $ !== 'undefined') {
@ -95,6 +125,6 @@
} }
}); });
</script> </script>
</body> </body>
</html> </html>