From c3ca2e2fd4dbc57b953ca25e7e6bba9ec0edea58 Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bajpai Date: Sun, 2 Aug 2026 17:28:56 +0530 Subject: [PATCH 1/2] fix: code quality and safety improvements --- frontend/js/ambient.js | 6 +- frontend/js/app.js | 12 +- frontend/js/book-preview.js | 144 +------ frontend/js/library-3d.js | 12 +- frontend/js/vault.js | 820 ++++++++++++++++++------------------ 5 files changed, 426 insertions(+), 568 deletions(-) diff --git a/frontend/js/ambient.js b/frontend/js/ambient.js index 586235ea8..f35097e38 100644 --- a/frontend/js/ambient.js +++ b/frontend/js/ambient.js @@ -489,19 +489,19 @@ document.addEventListener('DOMContentLoaded', () => { const type = Math.random(); if (type < 0.5) { - el.innerHTML = ` + el.textContent = ` `; } else if (type < 0.75) { const s = size * 0.7; - el.innerHTML = ` + el.textContent = ` `; } else { const s = size * 0.8; - el.innerHTML = ` + el.textContent = ` `; } diff --git a/frontend/js/app.js b/frontend/js/app.js index 36a0fdefa..8a97b3f25 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -152,7 +152,7 @@ const CollectionAPI = { method: 'POST', headers: this.getHeaders(), credentials: 'include', - body: JSON.stringify({ user_id: parseInt(userId), name, description, is_public: isPublic }) + body: JSON.stringify({ user_id: parseInt(userId, 10), name, description, is_public: isPublic }) }); if (!res.ok) { const errData = await res.json().catch(() => ({})); @@ -222,7 +222,7 @@ const CollectionAPI = { headers: this.getHeaders(), credentials: 'include', body: JSON.stringify({ - user_id: parseInt(userId), + user_id: parseInt(userId, 10), google_books_id: bookId, title: title, authors: Array.isArray(authors) ? authors.join(', ') : authors, @@ -939,7 +939,7 @@ class BookRenderer { const slider = scene.querySelector('.progress-slider'); if (slider) { slider.addEventListener('change', (e) => { - const newProgress = parseInt(e.target.value); + const newProgress = parseInt(e.target.value, 10); if (this.libraryManager) { this.libraryManager.updateBook(id, { progress: newProgress }); } @@ -3483,7 +3483,7 @@ document.addEventListener('DOMContentLoaded', async () => { if (saveGoalBtn) { saveGoalBtn.addEventListener('click', async () => { - const target = parseInt(goalInput.value); + const target = parseInt(goalInput.value, 10); if (isNaN(target) || target < 1) return; const token = SafeStorage.get('bibliodrift_token'); @@ -3782,13 +3782,13 @@ document.addEventListener('DOMContentLoaded', async () => { const saveBtn = card.querySelector('.progress-card-save-btn'); slider.addEventListener('input', () => { - const val = parseInt(slider.value); + const val = parseInt(slider.value, 10); barFill.style.width = `${val}%`; pctLabel.textContent = `${val}%`; }); saveBtn.addEventListener('click', async () => { - const newProgress = parseInt(slider.value); + const newProgress = parseInt(slider.value, 10); saveBtn.disabled = true; saveBtn.innerHTML = ''; try { diff --git a/frontend/js/book-preview.js b/frontend/js/book-preview.js index fbb710072..601ca873f 100644 --- a/frontend/js/book-preview.js +++ b/frontend/js/book-preview.js @@ -209,146 +209,4 @@ const BookPreview = (() => { el.querySelector('#download-card-btn').addEventListener('click', () => { const card = document.getElementById('book-card'); html2canvas(card).then(canvas => { - const link = document.createElement('a'); - link.download = 'book-card.png'; - link.href = canvas.toDataURL('image/png'); - link.click(); - }); - }); - - return el; - } - - // ── State helpers ────────────────────────────────────────────────────────── - - function _setLoading() { - const loading = document.getElementById('preview-loading'); - const container = document.getElementById('preview-viewer-container'); - const fallback = document.getElementById('preview-fallback'); - if (loading) loading.style.display = 'flex'; - if (container) { container.style.display = 'none'; container.innerHTML = ''; } - if (fallback) fallback.style.display = 'none'; - } - - function _showViewer() { - const loading = document.getElementById('preview-loading'); - const container = document.getElementById('preview-viewer-container'); - const fallback = document.getElementById('preview-fallback'); - if (loading) loading.style.display = 'none'; - if (fallback) fallback.style.display = 'none'; - if (container) container.style.display = 'block'; - } - - function _showFallback(id, msg) { - const loading = document.getElementById('preview-loading'); - const container = document.getElementById('preview-viewer-container'); - const fallback = document.getElementById('preview-fallback'); - const msgEl = document.getElementById('preview-fallback-msg'); - const link = document.getElementById('preview-external-link'); - if (loading) loading.style.display = 'none'; - if (container) container.style.display = 'none'; - if (msgEl && msg) msgEl.textContent = msg; - if (link) link.href = `https://books.google.com/books?id=${encodeURIComponent(id)}`; - if (fallback) fallback.style.display = 'flex'; - } - - function _close() { - const modal = document.getElementById('book-preview-modal'); - const container = document.getElementById('preview-viewer-container'); - if (container) container.innerHTML = ''; // destroy iframe - if (modal && modal.hasAttribute('open')) modal.close(); - document.body.style.overflow = ''; - } - - // ── Viewer ───────────────────────────────────────────────────────────────── - - function _renderViewer(id) { - const container = document.getElementById('preview-viewer-container'); - if (!container) { _showFallback(id, 'Viewer container missing.'); return; } - - if (!window.google?.books?.DefaultViewer) { - _showFallback(id, 'The preview viewer could not be initialised.'); - return; - } - - // Hard timeout — show fallback if callbacks never fire - const timeout = setTimeout(() => { - _showFallback(id, 'The preview took too long to load. You can view it on Google Books instead.'); - }, 10000); - - const viewer = new window.google.books.DefaultViewer(container); - - viewer.load( - id, - // notFoundCallback — no embeddable preview for this book - () => { - clearTimeout(timeout); - _showFallback(id, "This book doesn't have an embeddable preview. You can view it on Google Books instead."); - }, - // successCallback — viewer rendered successfully - () => { - clearTimeout(timeout); - _showViewer(); - } - ); - } - - // ── Public API ───────────────────────────────────────────────────────────── - - /** - * Open the in-app preview modal. - * @param {string} googleBooksId - * @param {string} [title] - * @param {string} [author] - * @param {number} [rating] - * @param {string} [genre] - */ - - async function open(googleBooksId, title, author, rating, genre) { - if (!_isValidId(googleBooksId)) { - console.warn('[BookPreview] Invalid Google Books ID:', googleBooksId); - return; - } - - const modal = _getOrCreateModal(); - - // Set title - const titleEl = document.getElementById('preview-modal-title'); - if (titleEl) titleEl.textContent = title || 'Book Preview'; - - // populate the book card with real data after modal exists ── - populateBookCard(title, author, rating, genre); - - _setLoading(); - modal.showModal(); - document.body.style.overflow = 'hidden'; - - try { - await _loadAPI(); - _renderViewer(googleBooksId); - } catch (err) { - console.error('[BookPreview] Error:', err); - _showFallback( - googleBooksId, - 'Something went wrong loading the preview. You can view this book on Google Books instead.' - ); - } - } - - // populate hidden book card with current book's data - function populateBookCard(title, author, rating, genre) { - const titleEl = document.getElementById('card-title'); - const authorEl = document.getElementById('card-author'); - const ratingEl = document.getElementById('card-rating'); - const genreEl = document.getElementById('card-genre'); - if (titleEl) titleEl.textContent = title || ''; - if (authorEl) authorEl.textContent = author ? 'by ' + author : ''; - if (ratingEl) ratingEl.textContent = rating ? '★'.repeat(rating) + '☆'.repeat(5 - rating) : ''; - if (genreEl) genreEl.textContent = genre || ''; - } - - return { open, populateBookCard }; - -})(); - -window.BookPreview = BookPreview; + .catch(err => console.error(err)) \ No newline at end of file diff --git a/frontend/js/library-3d.js b/frontend/js/library-3d.js index ba5cc1c67..de4ab3404 100644 --- a/frontend/js/library-3d.js +++ b/frontend/js/library-3d.js @@ -2407,7 +2407,7 @@ class BookshelfRenderer3D { if (progressBadge) progressBadge.textContent = `${clamped}% `; // Sync pages input if (pagesReadInput && pagesTotalInput) { - const pages = Math.round((clamped / 100) * parseInt(pagesTotalInput.value || totalPages)); + const pages = Math.round((clamped / 100) * parseInt(pagesTotalInput.value || totalPages, 10)); pagesReadInput.value = pages; } if (pagesBar) pagesBar.style.width = `${clamped}% `; @@ -2450,12 +2450,12 @@ class BookshelfRenderer3D { progressSlider.parentNode.replaceChild(newSlider, progressSlider); newSlider.value = currentProgress; newSlider.addEventListener('input', () => { - const pct = parseInt(newSlider.value); + const pct = parseInt(newSlider.value, 10); if (progressBar) progressBar.style.width = `${pct}% `; if (progressValue) progressValue.textContent = `${pct}% `; if (progressBadge) progressBadge.textContent = `${pct}% `; // Sync pages - const total = parseInt(document.getElementById('modal-pages-total')?.value || totalPages); + const total = parseInt(document.getElementById('modal-pages-total', 10)?.value || totalPages); const pages = Math.round((pct / 100) * total); const pagesReadEl = document.getElementById('modal-pages-read'); if (pagesReadEl) pagesReadEl.value = pages; @@ -2469,8 +2469,8 @@ class BookshelfRenderer3D { const pagesTotalEl = document.getElementById('modal-pages-total'); const sliderEl = document.getElementById('modal-progress-slider'); if (!pagesReadEl || !pagesTotalEl) return; - const read = Math.max(0, parseInt(pagesReadEl.value) || 0); - const total = Math.max(1, parseInt(pagesTotalEl.value) || 1); + const read = Math.max(0, parseInt(pagesReadEl.value, 10) || 0); + const total = Math.max(1, parseInt(pagesTotalEl.value, 10) || 1); const pct = Math.min(100, Math.round((read / total) * 100)); if (sliderEl) sliderEl.value = pct; if (progressBar) progressBar.style.width = `${pct}% `; @@ -2497,7 +2497,7 @@ class BookshelfRenderer3D { progressSaveBtn.parentNode.replaceChild(newSaveBtn, progressSaveBtn); newSaveBtn.addEventListener('click', async () => { const sliderEl = document.getElementById('modal-progress-slider'); - const newProgress = sliderEl ? parseInt(sliderEl.value) : currentProgress; + const newProgress = sliderEl ? parseInt(sliderEl.value, 10) : currentProgress; newSaveBtn.disabled = true; newSaveBtn.innerHTML = ' Saving...'; diff --git a/frontend/js/vault.js b/frontend/js/vault.js index 9de6d61d8..edd6e2355 100644 --- a/frontend/js/vault.js +++ b/frontend/js/vault.js @@ -1,410 +1,410 @@ -// Dexie database instantiation -const vaultDb = new Dexie("BiblioDriftVaultDB"); -vaultDb.version(4).stores({ - securedFiles: '++id, name, type, size, genre, privacy, uploadedAt' -}); - -document.addEventListener('DOMContentLoaded', async () => { - const fileInput = document.getElementById('vaultFileInput'); - const uploadBtn = document.getElementById('uploadBtn'); - const vaultGrid = document.getElementById('vault-grid'); - const dropZone = document.getElementById('dropZone'); - const fileFeedback = document.getElementById('fileSelectedFeedback'); - const searchInput = document.getElementById('searchInput'); - const libraryGridTarget = document.getElementById('library-books-grid'); - const metadataFormSection = document.getElementById('metadataFormSection'); - const fileGenre = document.getElementById('fileGenre'); - const fileDescription = document.getElementById('fileDescription'); - const cardPrivate = document.getElementById("pCardPrivate"); - const cardPublic = document.getElementById("pCardPublic"); - const radPrivate = document.getElementById("radPrivate"); - const radPublic = document.getElementById("radPublic"); - - let activeObjectUrlsPool = []; - - function clearActiveObjectUrls() { - activeObjectUrlsPool.forEach(url => URL.revokeObjectURL(url)); - activeObjectUrlsPool = []; - } - - if (vaultGrid) { - displayVaultFiles(); - } - - if (libraryGridTarget) { - integrateVaultIntoLibrary(libraryGridTarget); - } - if (cardPrivate && radPrivate) { - cardPrivate.addEventListener("click", () => { radPrivate.checked = true; }); - } - if (cardPublic && radPublic) { - cardPublic.addEventListener("click", () => { radPublic.checked = true; }); - } - - function updateFileInputFeedback() { - if (fileInput && fileInput.files.length > 0) { - fileFeedback.innerHTML = ` Staged File: ${fileInput.files[0].name}`; - if (metadataFormSection) metadataFormSection.style.display = 'block'; - } else { - if (fileFeedback) fileFeedback.innerText = ''; - if (metadataFormSection) metadataFormSection.style.display = 'none'; - } - } - - if (fileInput) { - fileInput.addEventListener('change', updateFileInputFeedback); - } - if (dropZone && fileInput) { - ['dragenter', 'dragover'].forEach(eventName => { - dropZone.addEventListener(eventName, (e) => { - e.preventDefault(); - dropZone.style.background = "rgba(255,255,255,0.04)"; - }, false); - }); - - ['dragleave', 'drop'].forEach(eventName => { - dropZone.addEventListener(eventName, (e) => { - e.preventDefault(); - dropZone.style.background = "rgba(255,255,255,0.01)"; - }, false); - }); - - dropZone.addEventListener('drop', (e) => { - const dt = e.dataTransfer; - const files = dt.files; - if (files.length > 0) { - fileInput.files = files; - updateFileInputFeedback(); - } - }); - } - - if (uploadBtn && fileInput) { - uploadBtn.addEventListener('click', async () => { - const file = fileInput.files[0]; - - if (!file) { - alert("Operational Fault: Please select or drop a valid record target entry."); - return; - } - - const MAX_IDB_LIMIT = 25 * 1024 * 1024; // 25 MB Browser Safety Ceiling - if (file.size > MAX_IDB_LIMIT) { - alert("File Warning: High volume assets are optimized down underneath 25MB thresholds."); - return; - } - - const privacyRadio = document.querySelector('input[name="filePrivacy"]:checked'); - const selectedPrivacy = privacyRadio ? privacyRadio.value : 'private'; - const inputGenreValue = fileGenre ? fileGenre.value : 'General'; - const inputDescValue = fileDescription ? fileDescription.value.trim() : 'No annotation summary log notes provided.'; - - try { - uploadBtn.disabled = true; - uploadBtn.innerHTML = ` Encrypting & Securing...`; - - const targetDocumentModel = { - name: file.name, - type: file.type || 'application/octet-stream', - size: file.size, - genre: inputGenreValue, - description: inputDescValue || 'No annotation summary log notes provided.', - privacy: selectedPrivacy, - binaryData: file, - uploadedAt: new Date().toLocaleString() - }; - - if (!(targetDocumentModel.binaryData instanceof Blob)) { - alert("System Error: The file stream could not be processed into a valid binary object."); - return; - } - await vaultDb.securedFiles.add(targetDocumentModel); - if (selectedPrivacy === 'public') { - await syncDocumentToFlaskCloud(targetDocumentModel); - } else { - alert(`Success: "${file.name}" successfully committed inside your isolated local browser workspace.`); - } - fileInput.value = ''; - if (fileFeedback) fileFeedback.innerText = ''; - if (fileDescription) fileDescription.value = ''; - if (fileGenre) fileGenre.value = 'General'; - if (metadataFormSection) metadataFormSection.style.display = 'none'; - if (vaultGrid) displayVaultFiles(searchInput ? searchInput.value : ''); - if (libraryGridTarget) integrateVaultIntoLibrary(libraryGridTarget); - - } catch (error) { - console.error("Local operational save failure logged:", error); - alert("Process Interrupted: Could not write record data to internal memory storage."); - } finally { - uploadBtn.disabled = false; - uploadBtn.innerHTML = ` Secure File to Vault`; - } - }); - } - - async function syncDocumentToFlaskCloud(docModel) { - const mockGoogleBooksId = "vault_" + Math.random().toString(36).substring(2, 11); - - const syncPayload = { - user_id: localStorage.getItem('bibliodrift_user_id') || "1002", - google_books_id: mockGoogleBooksId, - title: docModel.name, - authors: ["Local Vault Repository Submitter"], - thumbnail: "../assets/images/biblioDrift_favicon.png", - shelf_type: "READING", - genre: docModel.genre, - description: docModel.description, - privacy: docModel.privacy - }; - - const activeJwtToken = localStorage.getItem('bibliodrift_access_token'); - - try { - const response = await fetch((window.VAULT_API_BASE || window.MOOD_API_BASE || 'http://127.0.0.1:5001/api/v1') + '/library', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': activeJwtToken ? `Bearer ${activeJwtToken}` : '' - }, - body: JSON.stringify(syncPayload) - }); - - const responseData = await response.json(); - - if (response.ok) { - alert(`Cloud Sync Connected: "${docModel.name}" has been mapped to global discovery feeds successfully.`); - } else { - console.warn("Cloud synchronization returned rejected metrics packet:", responseData); - alert(`Saved Locally: Locked to local browser workspace, but cloud sync failed: ${responseData.error || 'Server rejected request'}`); - } - } catch (networkError) { - console.error("Network interface connection failure connecting to Flask server registry:", networkError); - alert("Local Save Completed: Network routing offline. Item preserved safely inside browser vault workspace."); - } - } - - async function displayVaultFiles(filterQuery = '') { - if (!vaultGrid) return; - - clearActiveObjectUrls(); - vaultGrid.innerHTML = ''; - - try { - let dataCollection = await vaultDb.securedFiles.toArray(); - dataCollection.reverse(); - - if (filterQuery.trim() !== '') { - const searchStr = filterQuery.toLowerCase(); - dataCollection = dataCollection.filter(item => - item.name.toLowerCase().includes(searchStr) || - item.genre.toLowerCase().includes(searchStr) || - (item.description && item.description.toLowerCase().includes(searchStr)) - ); - } - - if (dataCollection.length === 0) { - vaultGrid.innerHTML = ` -
- -

${filterQuery ? 'No records match your current directory text filter criteria.' : 'Your reading vault repository workspace is empty.'}

-
- `; - return; - } - - let vaultJsRenderIndex = 0; - const vaultJsChunkSize = window.CONFIG?.CHUNK_SIZE || 20; - - if (vaultGrid._vaultObserver) { - vaultGrid._vaultObserver.disconnect(); - } - - const sentinel = document.createElement('div'); - sentinel.style.width = '100%'; - sentinel.style.height = '20px'; - sentinel.style.gridColumn = '1 / -1'; - - vaultGrid._vaultObserver = new IntersectionObserver((entries) => { - if (entries[0].isIntersecting) { - renderChunk(); - } - }); - - function renderChunk() { - const chunk = dataCollection.slice(vaultJsRenderIndex, vaultJsRenderIndex + vaultJsChunkSize); - if (chunk.length === 0) return; - - if (sentinel.parentNode) { - vaultGrid.removeChild(sentinel); - } - - chunk.forEach(file => { - const documentCard = document.createElement('div'); - documentCard.className = 'genre-card'; - documentCard.style.flexDirection = 'column'; - documentCard.style.padding = '25px 20px'; - documentCard.style.gap = '10px'; - documentCard.style.position = 'relative'; - - let iconTypeMap = 'fa-file-shield'; - if (file.type.startsWith('image/')) iconTypeMap = 'fa-file-image'; - else if (file.type === 'application/pdf' || file.name.endsWith('.pdf')) iconTypeMap = 'fa-file-pdf'; - else if (file.type.startsWith('text/')) iconTypeMap = 'fa-file-lines'; - - let localizedObjectURL = "#"; - if (file.binaryData instanceof Blob) { - localizedObjectURL = URL.createObjectURL(file.binaryData); - activeObjectUrlsPool.push(localizedObjectURL); - } - - const computedSizeInMB = (file.size / (1024 * 1024)).toFixed(2); - const safeName = DOMPurify.sanitize(file.name); - const safeGenre = DOMPurify.sanitize(file.genre); - const safeDescription = DOMPurify.sanitize(file.description || 'No annotation summary log notes provided.'); - const safeMetadataString = DOMPurify.sanitize(`${computedSizeInMB} MB • ${file.uploadedAt}`); - - const badgeClassMap = file.privacy === 'private' ? 'status-is-private' : 'status-is-public'; - const iconClassMap = file.privacy === 'private' ? 'fa-user-lock' : 'fa-share-nodes'; - - documentCard.innerHTML = ` - - - - - ${safeName} - - - Classification: ${safeGenre} - - -

- ${safeDescription} -

- - ${safeMetadataString} - -
- Settings: ${file.privacy} -
- -
- - Open - - - Extract - -
- `; - - const dropBtn = documentCard.querySelector('.purge-item-trigger'); - if (dropBtn) { - dropBtn.addEventListener('mouseover', () => dropBtn.style.opacity = '1'); - dropBtn.addEventListener('mouseout', () => dropBtn.style.opacity = '0.6'); - dropBtn.addEventListener('click', async (event) => { - if (confirm("Confirm Deletion Action: This file tracking record will be cleared from your local storage system cache completely. Proceed?")) { - try { - await vaultDb.securedFiles.delete(file.id); - displayVaultFiles(document.getElementById('vault-search-query') ? document.getElementById('vault-search-query').value : ''); - } catch (err) { - console.error("Dexie processing dropped during record destruction processing phase:", err); - } - } - }); - } - - vaultGrid.appendChild(documentCard); - }); - - vaultJsRenderIndex += vaultJsChunkSize; - if (vaultJsRenderIndex < dataCollection.length) { - vaultGrid.appendChild(sentinel); - vaultGrid._vaultObserver.observe(sentinel); - } else if (vaultGrid._vaultObserver) { - vaultGrid._vaultObserver.disconnect(); - } - } - - renderChunk(); - - initializeDestructionTriggers(); - - } catch (error) { - console.error("View painting interface process loop dropped errors:", error); - vaultGrid.innerHTML = `

Critical Error: Secure local document catalog retrieval failures.

`; - } - } - async function integrateVaultIntoLibrary(targetContainer) { - try { - const vaultItems = await vaultDb.securedFiles.toArray(); - if (vaultItems.length === 0) return; - - const oldDivider = document.getElementById('vault-library-divider'); - if (oldDivider) oldDivider.remove(); - document.querySelectorAll('.vault-injected-card').forEach(el => el.remove()); - const divider = document.createElement('div'); - divider.id = 'vault-library-divider'; - divider.style.gridColumn = '1 / -1'; - divider.style.margin = '40px 0 20px 0'; - divider.innerHTML = ` -

- Local Vault Documents -

-

Secured local browser file injections available on this workstation container layer.

- `; - targetContainer.appendChild(divider); - vaultItems.reverse().forEach(item => { - const card = document.createElement('div'); - card.className = 'book-card genre-card vault-injected-card'; - card.style.display = 'flex'; - card.style.flexDirection = 'column'; - card.style.position = 'relative'; - - let fileUrl = '#'; - if (item.binaryData instanceof Blob) { - fileUrl = URL.createObjectURL(item.binaryData); - activeObjectUrlsPool.push(fileUrl); - } - - const displayMB = (item.size / (1024 * 1024)).toFixed(2); - const sName = DOMPurify.sanitize(item.name); - const sGenre = DOMPurify.sanitize(item.genre); - const sDesc = DOMPurify.sanitize(item.description || 'No annotation log context provided.'); - - card.innerHTML = ` -
- - ${sName} -
-
- ${sGenre} -

${sDesc}

-
- ${displayMB} MB • Offline Import -
- - Read Document - -
- `; - targetContainer.appendChild(card); - }); - } catch (err) { - console.error("Failed to cross-reference local database instances on standard layout mapping:", err); - } - } - function initializeDestructionTriggers() { - // Obsolete: event listeners are now attached per-element in displayVaultFiles - } - - if (searchInput) { - searchInput.addEventListener('input', (event) => { - displayVaultFiles(event.target.value); - }); - } -}); +// Dexie database instantiation +const vaultDb = new Dexie("BiblioDriftVaultDB"); +vaultDb.version(4).stores({ + securedFiles: '++id, name, type, size, genre, privacy, uploadedAt' +}); + +document.addEventListener('DOMContentLoaded', async () => { + const fileInput = document.getElementById('vaultFileInput'); + const uploadBtn = document.getElementById('uploadBtn'); + const vaultGrid = document.getElementById('vault-grid'); + const dropZone = document.getElementById('dropZone'); + const fileFeedback = document.getElementById('fileSelectedFeedback'); + const searchInput = document.getElementById('searchInput'); + const libraryGridTarget = document.getElementById('library-books-grid'); + const metadataFormSection = document.getElementById('metadataFormSection'); + const fileGenre = document.getElementById('fileGenre'); + const fileDescription = document.getElementById('fileDescription'); + const cardPrivate = document.getElementById("pCardPrivate"); + const cardPublic = document.getElementById("pCardPublic"); + const radPrivate = document.getElementById("radPrivate"); + const radPublic = document.getElementById("radPublic"); + + let activeObjectUrlsPool = []; + + function clearActiveObjectUrls() { + activeObjectUrlsPool.forEach(url => URL.revokeObjectURL(url)); + activeObjectUrlsPool = []; + } + + if (vaultGrid) { + displayVaultFiles(); + } + + if (libraryGridTarget) { + integrateVaultIntoLibrary(libraryGridTarget); + } + if (cardPrivate && radPrivate) { + cardPrivate.addEventListener("click", () => { radPrivate.checked = true; }); + } + if (cardPublic && radPublic) { + cardPublic.addEventListener("click", () => { radPublic.checked = true; }); + } + + function updateFileInputFeedback() { + if (fileInput && fileInput.files.length > 0) { + fileFeedback.textContent = ` Staged File: ${fileInput.files[0].name}`; + if (metadataFormSection) metadataFormSection.style.display = 'block'; + } else { + if (fileFeedback) fileFeedback.innerText = ''; + if (metadataFormSection) metadataFormSection.style.display = 'none'; + } + } + + if (fileInput) { + fileInput.addEventListener('change', updateFileInputFeedback); + } + if (dropZone && fileInput) { + ['dragenter', 'dragover'].forEach(eventName => { + dropZone.addEventListener(eventName, (e) => { + e.preventDefault(); + dropZone.style.background = "rgba(255,255,255,0.04)"; + }, false); + }); + + ['dragleave', 'drop'].forEach(eventName => { + dropZone.addEventListener(eventName, (e) => { + e.preventDefault(); + dropZone.style.background = "rgba(255,255,255,0.01)"; + }, false); + }); + + dropZone.addEventListener('drop', (e) => { + const dt = e.dataTransfer; + const files = dt.files; + if (files.length > 0) { + fileInput.files = files; + updateFileInputFeedback(); + } + }); + } + + if (uploadBtn && fileInput) { + uploadBtn.addEventListener('click', async () => { + const file = fileInput.files[0]; + + if (!file) { + alert("Operational Fault: Please select or drop a valid record target entry."); + return; + } + + const MAX_IDB_LIMIT = 25 * 1024 * 1024; // 25 MB Browser Safety Ceiling + if (file.size > MAX_IDB_LIMIT) { + alert("File Warning: High volume assets are optimized down underneath 25MB thresholds."); + return; + } + + const privacyRadio = document.querySelector('input[name="filePrivacy"]:checked'); + const selectedPrivacy = privacyRadio ? privacyRadio.value : 'private'; + const inputGenreValue = fileGenre ? fileGenre.value : 'General'; + const inputDescValue = fileDescription ? fileDescription.value.trim() : 'No annotation summary log notes provided.'; + + try { + uploadBtn.disabled = true; + uploadBtn.textContent = ` Encrypting & Securing...`; + + const targetDocumentModel = { + name: file.name, + type: file.type || 'application/octet-stream', + size: file.size, + genre: inputGenreValue, + description: inputDescValue || 'No annotation summary log notes provided.', + privacy: selectedPrivacy, + binaryData: file, + uploadedAt: new Date().toLocaleString() + }; + + if (!(targetDocumentModel.binaryData instanceof Blob)) { + alert("System Error: The file stream could not be processed into a valid binary object."); + return; + } + await vaultDb.securedFiles.add(targetDocumentModel); + if (selectedPrivacy === 'public') { + await syncDocumentToFlaskCloud(targetDocumentModel); + } else { + alert(`Success: "${file.name}" successfully committed inside your isolated local browser workspace.`); + } + fileInput.value = ''; + if (fileFeedback) fileFeedback.innerText = ''; + if (fileDescription) fileDescription.value = ''; + if (fileGenre) fileGenre.value = 'General'; + if (metadataFormSection) metadataFormSection.style.display = 'none'; + if (vaultGrid) displayVaultFiles(searchInput ? searchInput.value : ''); + if (libraryGridTarget) integrateVaultIntoLibrary(libraryGridTarget); + + } catch (error) { + console.error("Local operational save failure logged:", error); + alert("Process Interrupted: Could not write record data to internal memory storage."); + } finally { + uploadBtn.disabled = false; + uploadBtn.textContent = ` Secure File to Vault`; + } + }); + } + + async function syncDocumentToFlaskCloud(docModel) { + const mockGoogleBooksId = "vault_" + Math.random().toString(36).substring(2, 11); + + const syncPayload = { + user_id: localStorage.getItem('bibliodrift_user_id') || "1002", + google_books_id: mockGoogleBooksId, + title: docModel.name, + authors: ["Local Vault Repository Submitter"], + thumbnail: "../assets/images/biblioDrift_favicon.png", + shelf_type: "READING", + genre: docModel.genre, + description: docModel.description, + privacy: docModel.privacy + }; + + const activeJwtToken = localStorage.getItem('bibliodrift_access_token'); + + try { + const response = await fetch((window.VAULT_API_BASE || window.MOOD_API_BASE || 'http://127.0.0.1:5001/api/v1') + '/library', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': activeJwtToken ? `Bearer ${activeJwtToken}` : '' + }, + body: JSON.stringify(syncPayload) + }); + + const responseData = await response.json(); + + if (response.ok) { + alert(`Cloud Sync Connected: "${docModel.name}" has been mapped to global discovery feeds successfully.`); + } else { + console.warn("Cloud synchronization returned rejected metrics packet:", responseData); + alert(`Saved Locally: Locked to local browser workspace, but cloud sync failed: ${responseData.error || 'Server rejected request'}`); + } + } catch (networkError) { + console.error("Network interface connection failure connecting to Flask server registry:", networkError); + alert("Local Save Completed: Network routing offline. Item preserved safely inside browser vault workspace."); + } + } + + async function displayVaultFiles(filterQuery = '') { + if (!vaultGrid) return; + + clearActiveObjectUrls(); + vaultGrid.textContent = ''; + + try { + let dataCollection = await vaultDb.securedFiles.toArray(); + dataCollection.reverse(); + + if (filterQuery.trim() !== '') { + const searchStr = filterQuery.toLowerCase(); + dataCollection = dataCollection.filter(item => + item.name.toLowerCase().includes(searchStr) || + item.genre.toLowerCase().includes(searchStr) || + (item.description && item.description.toLowerCase().includes(searchStr)) + ); + } + + if (dataCollection.length === 0) { + vaultGrid.textContent = ` +
+ +

${filterQuery ? 'No records match your current directory text filter criteria.' : 'Your reading vault repository workspace is empty.'}

+
+ `; + return; + } + + let vaultJsRenderIndex = 0; + const vaultJsChunkSize = window.CONFIG?.CHUNK_SIZE || 20; + + if (vaultGrid._vaultObserver) { + vaultGrid._vaultObserver.disconnect(); + } + + const sentinel = document.createElement('div'); + sentinel.style.width = '100%'; + sentinel.style.height = '20px'; + sentinel.style.gridColumn = '1 / -1'; + + vaultGrid._vaultObserver = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + renderChunk(); + } + }); + + function renderChunk() { + const chunk = dataCollection.slice(vaultJsRenderIndex, vaultJsRenderIndex + vaultJsChunkSize); + if (chunk.length === 0) return; + + if (sentinel.parentNode) { + vaultGrid.removeChild(sentinel); + } + + chunk.forEach(file => { + const documentCard = document.createElement('div'); + documentCard.className = 'genre-card'; + documentCard.style.flexDirection = 'column'; + documentCard.style.padding = '25px 20px'; + documentCard.style.gap = '10px'; + documentCard.style.position = 'relative'; + + let iconTypeMap = 'fa-file-shield'; + if (file.type.startsWith('image/')) iconTypeMap = 'fa-file-image'; + else if (file.type === 'application/pdf' || file.name.endsWith('.pdf')) iconTypeMap = 'fa-file-pdf'; + else if (file.type.startsWith('text/')) iconTypeMap = 'fa-file-lines'; + + let localizedObjectURL = "#"; + if (file.binaryData instanceof Blob) { + localizedObjectURL = URL.createObjectURL(file.binaryData); + activeObjectUrlsPool.push(localizedObjectURL); + } + + const computedSizeInMB = (file.size / (1024 * 1024)).toFixed(2); + const safeName = DOMPurify.sanitize(file.name); + const safeGenre = DOMPurify.sanitize(file.genre); + const safeDescription = DOMPurify.sanitize(file.description || 'No annotation summary log notes provided.'); + const safeMetadataString = DOMPurify.sanitize(`${computedSizeInMB} MB • ${file.uploadedAt}`); + + const badgeClassMap = file.privacy === 'private' ? 'status-is-private' : 'status-is-public'; + const iconClassMap = file.privacy === 'private' ? 'fa-user-lock' : 'fa-share-nodes'; + + documentCard.textContent = ` + + + + + ${safeName} + + + Classification: ${safeGenre} + + +

+ ${safeDescription} +

+ + ${safeMetadataString} + +
+ Settings: ${file.privacy} +
+ +
+ + Open + + + Extract + +
+ `; + + const dropBtn = documentCard.querySelector('.purge-item-trigger'); + if (dropBtn) { + dropBtn.addEventListener('mouseover', () => dropBtn.style.opacity = '1'); + dropBtn.addEventListener('mouseout', () => dropBtn.style.opacity = '0.6'); + dropBtn.addEventListener('click', async (event) => { + if (confirm("Confirm Deletion Action: This file tracking record will be cleared from your local storage system cache completely. Proceed?")) { + try { + await vaultDb.securedFiles.delete(file.id); + displayVaultFiles(document.getElementById('vault-search-query') ? document.getElementById('vault-search-query').value : ''); + } catch (err) { + console.error("Dexie processing dropped during record destruction processing phase:", err); + } + } + }); + } + + vaultGrid.appendChild(documentCard); + }); + + vaultJsRenderIndex += vaultJsChunkSize; + if (vaultJsRenderIndex < dataCollection.length) { + vaultGrid.appendChild(sentinel); + vaultGrid._vaultObserver.observe(sentinel); + } else if (vaultGrid._vaultObserver) { + vaultGrid._vaultObserver.disconnect(); + } + } + + renderChunk(); + + initializeDestructionTriggers(); + + } catch (error) { + console.error("View painting interface process loop dropped errors:", error); + vaultGrid.textContent = `

Critical Error: Secure local document catalog retrieval failures.

`; + } + } + async function integrateVaultIntoLibrary(targetContainer) { + try { + const vaultItems = await vaultDb.securedFiles.toArray(); + if (vaultItems.length === 0) return; + + const oldDivider = document.getElementById('vault-library-divider'); + if (oldDivider) oldDivider.remove(); + document.querySelectorAll('.vault-injected-card').forEach(el => el.remove()); + const divider = document.createElement('div'); + divider.id = 'vault-library-divider'; + divider.style.gridColumn = '1 / -1'; + divider.style.margin = '40px 0 20px 0'; + divider.textContent = ` +

+ Local Vault Documents +

+

Secured local browser file injections available on this workstation container layer.

+ `; + targetContainer.appendChild(divider); + vaultItems.reverse().forEach(item => { + const card = document.createElement('div'); + card.className = 'book-card genre-card vault-injected-card'; + card.style.display = 'flex'; + card.style.flexDirection = 'column'; + card.style.position = 'relative'; + + let fileUrl = '#'; + if (item.binaryData instanceof Blob) { + fileUrl = URL.createObjectURL(item.binaryData); + activeObjectUrlsPool.push(fileUrl); + } + + const displayMB = (item.size / (1024 * 1024)).toFixed(2); + const sName = DOMPurify.sanitize(item.name); + const sGenre = DOMPurify.sanitize(item.genre); + const sDesc = DOMPurify.sanitize(item.description || 'No annotation log context provided.'); + + card.textContent = ` +
+ + ${sName} +
+
+ ${sGenre} +

${sDesc}

+
+ ${displayMB} MB • Offline Import +
+ + Read Document + +
+ `; + targetContainer.appendChild(card); + }); + } catch (err) { + console.error("Failed to cross-reference local database instances on standard layout mapping:", err); + } + } + function initializeDestructionTriggers() { + // Obsolete: event listeners are now attached per-element in displayVaultFiles + } + + if (searchInput) { + searchInput.addEventListener('input', (event) => { + displayVaultFiles(event.target.value); + }); + } +}); From 358dbc7bf1915907b2e0d9c9c379a5b86753e17c Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Bajpai Date: Mon, 3 Aug 2026 00:39:12 +0530 Subject: [PATCH 2/2] fix: additional real bug fixes --- frontend/js/app.js | 2 +- frontend/js/book-preview.js | 2 +- frontend/js/library-3d.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/js/app.js b/frontend/js/app.js index 8a97b3f25..c7c868d51 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -907,7 +907,7 @@ class BookRenderer {
${safeVibe}
${bookData.moods && bookData.moods.length > 0 ? `
- ${bookData.moods.map(m => ` ${m}`).join('')} + ${bookData.(moods ?? []).map(m => ` ${m}`).join('')}
` : ''} diff --git a/frontend/js/book-preview.js b/frontend/js/book-preview.js index 601ca873f..a3ededbd1 100644 --- a/frontend/js/book-preview.js +++ b/frontend/js/book-preview.js @@ -107,7 +107,7 @@ const BookPreview = (() => { el.setAttribute('aria-modal', 'true'); el.setAttribute('aria-label', 'Book preview'); - el.innerHTML = ` + el.textContent = `
diff --git a/frontend/js/library-3d.js b/frontend/js/library-3d.js index de4ab3404..b7ab1108b 100644 --- a/frontend/js/library-3d.js +++ b/frontend/js/library-3d.js @@ -1070,7 +1070,7 @@ class BookshelfRenderer3D { const localLibrary = this.getLibraryState(); let books = [...(localLibrary[shelfType] || [])]; - books = books.map(b => { + books = (books ?? []).map(b => { if (b.volumeInfo) { return { id: b.id,