Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions frontend/js/ambient.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,19 +489,19 @@ document.addEventListener('DOMContentLoaded', () => {

const type = Math.random();
if (type < 0.5) {
el.innerHTML = `<svg width="${size}" height="${size}" viewBox="0 0 20 20">
el.textContent = `<svg width="${size}" height="${size}" viewBox="0 0 20 20">
<path d="M10 1 L11.5 8.5 L19 10 L11.5 11.5 L10 19 L8.5 11.5 L1 10 L8.5 8.5 Z"
fill="${color}" opacity="0.9"/></svg>`;
} else if (type < 0.75) {
const s = size * 0.7;
el.innerHTML = `<svg width="${s}" height="${s}" viewBox="0 0 14 14">
el.textContent = `<svg width="${s}" height="${s}" viewBox="0 0 14 14">
<circle cx="7" cy="7" r="5" fill="${color}" opacity="0.85"/>
<line x1="7" y1="0" x2="7" y2="14" stroke="${color}" stroke-width="1.5" opacity="0.5"/>
<line x1="0" y1="7" x2="14" y2="7" stroke="${color}" stroke-width="1.5" opacity="0.5"/>
</svg>`;
} else {
const s = size * 0.8;
el.innerHTML = `<svg width="${s}" height="${s}" viewBox="0 0 20 20">
el.textContent = `<svg width="${s}" height="${s}" viewBox="0 0 20 20">
<polygon points="10,2 12,8 18,8 13,12 15,18 10,14 5,18 7,12 2,8 8,8"
fill="${color}" opacity="0.9"/></svg>`;
}
Expand Down
14 changes: 7 additions & 7 deletions frontend/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => ({}));
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -907,7 +907,7 @@ class BookRenderer {
<div class="handwritten-note" style="margin-bottom: 0.8rem; font-style: italic; color: #5d4037;">${safeVibe}</div>
${bookData.moods && bookData.moods.length > 0 ? `
<div class="book-mood-tags" style="margin-bottom: 0.8rem; display: flex; flex-wrap: wrap; gap: 4px;">
${bookData.moods.map(m => `<span style="font-size: 0.6rem; background: rgba(0,0,0,0.1); padding: 2px 6px; border-radius: 10px;"><i class="fa-solid ${this.getMoodIcon(m)}"></i> ${m}</span>`).join('')}
${bookData.(moods ?? []).map(m => `<span style="font-size: 0.6rem; background: rgba(0,0,0,0.1); padding: 2px 6px; border-radius: 10px;"><i class="fa-solid ${this.getMoodIcon(m)}"></i> ${m}</span>`).join('')}
</div>
` : ''}
</div>
Expand Down Expand Up @@ -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 });
}
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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 = '<i class="fa-solid fa-spinner fa-spin"></i>';
try {
Expand Down
146 changes: 2 additions & 144 deletions frontend/js/book-preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const BookPreview = (() => {
el.setAttribute('aria-modal', 'true');
el.setAttribute('aria-label', 'Book preview');

el.innerHTML = `
el.textContent = `
<div class="preview-modal-inner">
<div class="preview-modal-header">
<div class="preview-modal-title-wrap">
Expand Down Expand Up @@ -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))
14 changes: 7 additions & 7 deletions frontend/js/library-3d.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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}% `;
Expand Down Expand Up @@ -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;
Expand All @@ -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}% `;
Expand All @@ -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 = '<i class="fa-solid fa-spinner fa-spin"></i> Saving...';
Expand Down
Loading