/** * Custom Book Library JavaScript * Version: 3.1.0 (with Location, Dropdown Filters, Selective Fetch) */ jQuery(document).ready(function($) { // --- Helper function to display messages --- function displayMessage($container, message, type, autoHide) { autoHide = typeof autoHide !== 'undefined' ? autoHide : true; // Default to true $container.removeClass('success error info loading') .addClass(type) .html(message) // Use .html() if message contains HTML .show(); if (type === 'success' && autoHide) { setTimeout(function() { $container.fadeOut(function() { $(this).html('').removeClass(type); }); }, 5000); } } // --- AJAX for Manual Book Add Form (#custom-book-input-form) --- $('#custom-book-input-form').on('submit', function(e) { e.preventDefault(); var $form = $(this); var $submitButton = $form.find('button[type="submit"]'); var $spinner = $form.find('#cbi-form-spinner'); var $responseDiv = $('#form-response'); var formData = new FormData(this); formData.append('action', 'cbl_add_book_action'); formData.append('nonce', cblLibraryData.nonce_input_form); $submitButton.prop('disabled', true); $spinner.addClass('is-active').css('visibility', 'visible'); displayMessage($responseDiv, cblLibraryData.text_processing || 'Processing...', 'loading', false); $.ajax({ url: cblLibraryData.ajax_url, type: 'POST', data: formData, processData: false, contentType: false, dataType: 'json', success: function(response) { if (response.success) { displayMessage($responseDiv, response.data.message, 'success'); $form[0].reset(); if (response.data.redirect_url) { window.location.href = response.data.redirect_url; } else if (typeof cblRefreshLibraryView === 'function') { // Placeholder for a refresh function if library is on same page cblRefreshLibraryView(); } } else { displayMessage($responseDiv, response.data.message, 'error', false); } }, error: function(jqXHR, textStatus, errorThrown) { var errorMessage = cblLibraryData.text_error || 'An error occurred.'; if (jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.message) { errorMessage = jqXHR.responseJSON.data.message; } else if (errorThrown) { errorMessage += ' (' + errorThrown + ')'; } displayMessage($responseDiv, errorMessage, 'error', false); }, complete: function() { $submitButton.prop('disabled', false); $spinner.removeClass('is-active').css('visibility', 'hidden'); } }); }); // --- ISBN Search Functionality (for manual add form) --- $('#search-by-isbn-button').on('click', function() { var isbn = $('#cbi_isbn').val().trim(); var $spinner = $('#isbn-search-spinner'); var $responseDiv = $('#form-response'); if (!isbn) { displayMessage($responseDiv, 'Please enter an ISBN.', 'error', false); return; } $spinner.addClass('is-active').css('visibility', 'visible'); displayMessage($responseDiv, 'Searching by ISBN...', 'loading', false); $.ajax({ url: cblLibraryData.ajax_url, type: 'POST', data: { action: 'cbl_fetch_book_info_by_isbn', isbn: isbn, nonce: cblLibraryData.fetch_isbn_nonce }, dataType: 'json', success: function(response) { if (response.success && response.data) { var book = response.data; $('#cbi_title').val(book.title || ''); $('#cbi_author').val(book.author || ''); $('#cbi_cover_url').val(book.cover_url || ''); if(book.publisher) $('#cbi_edition').val( (book.publisher + (book.year_published ? ', ' + book.year_published : '')).trim() ); // Potentially set cbi_book_location if API provides it, though unlikely displayMessage($responseDiv, 'Book details populated.', 'success'); } else { var errorMsg = response.data && response.data.message ? response.data.message : 'Could not find book details for this ISBN.'; displayMessage($responseDiv, errorMsg, 'error', false); } }, error: function() { displayMessage($responseDiv, 'Error during ISBN search.', 'error', false); }, complete: function() { $spinner.removeClass('is-active').css('visibility', 'hidden'); } }); }); // --- QuaggaJS ISBN Scanner (for manual add form) --- var $scanButton = $('#scan-isbn-button'); var $stopScanButton = $('#stop-scan-button'); var $cameraPreviewContainer = $('#camera-preview-container'); var $cameraPreview = $('#camera-preview'); if ($scanButton.length && typeof Quagga !== 'undefined') { var quaggaIsRunning = false; $scanButton.on('click', function() { if (!quaggaIsRunning) { $cameraPreviewContainer.show(); $cameraPreview.show(); $stopScanButton.show(); Quagga.init({ inputStream: { name: "Live", type: "LiveStream", target: $cameraPreview[0], constraints: { width: { min: 640 }, height: { min: 480 }, facingMode: "environment", aspectRatio: { min: 1, max: 2 }}, }, decoder: { readers: ["ean_reader", "ean_8_reader", "code_128_reader", "code_39_reader", "upc_reader", "upc_e_reader", "codabar_reader"] }, // Added codabar locate: true, numOfWorkers: navigator.hardwareConcurrency || 4, frequency: 10, locator: { patchSize: "medium", halfSample: true } }, function(err) { if (err) { console.error("QuaggaJS initialization error:", err); displayMessage($('#form-response'), 'Error initializing camera: ' + err.message, 'error', false); $cameraPreviewContainer.hide(); $stopScanButton.hide(); return; } Quagga.start(); quaggaIsRunning = true; $scanButton.text('Scanning...').prop('disabled', true); }); } }); Quagga.onDetected(function(result) { var code = result.codeResult.code; if (code && (code.length === 10 || code.length === 13) && /^[0-9X]+$/.test(code)) { $('#cbi_isbn').val(code); stopQuaggaScan(); $('#search-by-isbn-button').trigger('click'); } }); Quagga.onProcessed(function(result) { var drawingCtx = Quagga.canvas.ctx.overlay; var drawingCanvas = Quagga.canvas.dom.overlay; if (result) { if (result.boxes) { drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height"))); result.boxes.filter(function (box) { return box !== result.box; }).forEach(function (box) { Quagga.ImageDebug.drawPath(box, {x: 0, y: 1}, drawingCtx, {color: "green", lineWidth: 2}); }); } if (result.box) { Quagga.ImageDebug.drawPath(result.box, {x: 0, y: 1}, drawingCtx, {color: "#00F", lineWidth: 2}); } } }); function stopQuaggaScan() { if (quaggaIsRunning) { Quagga.stop(); quaggaIsRunning = false; $cameraPreviewContainer.hide(); $cameraPreview.attr('src', ''); $stopScanButton.hide(); $scanButton.text('Scan ISBN').prop('disabled', false); } } $stopScanButton.on('click', stopQuaggaScan); } else if ($scanButton.length) { $scanButton.on('click', function() { displayMessage($('#form-response'), 'ISBN Scanner library (QuaggaJS) not loaded.', 'error', false); }); } // --- Library View Specific JS --- if ($('#custom-user-library').length) { var $libraryActionMessages = $('#library-action-messages'); // General message area for library actions // Toggle Filters & Options Panel $('#toggle-filters-panel-button').on('click', function() { var $panel = $('#filters-and-options-panel'); var $icon = $(this).find('.dashicons'); $panel.slideToggle(200, function() { if ($panel.is(':visible')) { $icon.removeClass('dashicons-arrow-down-alt2').addClass('dashicons-arrow-up-alt2'); $(this).attr('aria-expanded', 'true'); } else { $icon.removeClass('dashicons-arrow-up-alt2').addClass('dashicons-arrow-down-alt2'); $(this).attr('aria-expanded', 'false'); } }); }); // Handle submission of the combined filters and display options form $('#cbl-main-filters-and-display-form').on('submit', function(e) { var selectedFields = []; $(this).find('input[name="display_fields_options_temp[]"]:checked').each(function() { selectedFields.push($(this).val()); }); $('#hidden_display_fields_for_submit').val(selectedFields.join(',')); // Allow default GET submission }); // --- Batch Fetch ALL Missing Covers & ISBNs (in library view) --- $('#fetch-all-covers-button').on('click', function() { var $button = $(this); var $statusDiv = $('#batch-fetch-status'); var $spinner = $('#library-batch-spinner'); if (!confirm(cblLibraryData.text_confirm_fetch_all || 'This will attempt to find missing covers/ISBNs for ALL books in your library. This can take a long time and may strain server resources. Continue?')) { return; } $button.prop('disabled', true); $('#fetch-selected-covers-button').prop('disabled', true); $spinner.addClass('is-active').css('visibility', 'visible'); $statusDiv.text(cblLibraryData.text_processing || 'Processing all...').removeClass('error success info'); displayMessage($libraryActionMessages, cblLibraryData.text_processing || 'Processing all books...', 'loading', false); $.ajax({ url: cblLibraryData.ajax_url, type: 'POST', data: { action: 'cbl_batch_fetch_book_covers_isbns', nonce: cblLibraryData.nonce_batch_fetch }, dataType: 'json', success: function(response) { if (response.success) { $statusDiv.text(response.data.message || 'Batch process completed.').addClass('success'); displayMessage($libraryActionMessages, response.data.message || 'Batch process completed.', 'success'); if(response.data.updated_count > 0 && confirm('Batch process complete. ' + response.data.message + ' Reload page to see changes?')) { window.location.reload(); } } else { $statusDiv.text(response.data.message || 'Error in batch processing.').addClass('error'); displayMessage($libraryActionMessages, response.data.message || 'Error in batch processing.', 'error', false); } }, error: function() { $statusDiv.text('A critical error occurred during the batch request.').addClass('error'); displayMessage($libraryActionMessages, 'A critical error occurred during the batch request.', 'error', false); }, complete: function() { $button.prop('disabled', false); $('#fetch-selected-covers-button').prop('disabled', false); $spinner.removeClass('is-active').css('visibility', 'hidden'); } }); }); // --- Selective Fetch Covers/ISBNs --- $('#fetch-selected-covers-button').on('click', function() { var $button = $(this); var $statusDiv = $('#batch-fetch-status'); var $spinner = $('#library-batch-spinner'); var selectedBookKeys = []; $('.select-book-checkbox:checked').each(function() { selectedBookKeys.push($(this).val()); }); if (selectedBookKeys.length === 0) { displayMessage($libraryActionMessages, cblLibraryData.text_select_books_fetch || 'Please select at least one book to fetch information.', 'info'); return; } if (!confirm( (cblLibraryData.text_confirm_fetch_selected || 'Fetch info for %d selected books? This might take a moment.').replace('%d', selectedBookKeys.length) )) { return; } $button.prop('disabled', true); $('#fetch-all-covers-button').prop('disabled', true); $spinner.addClass('is-active').css('visibility', 'visible'); $statusDiv.text(cblLibraryData.text_processing || 'Processing selected...').removeClass('error success info'); displayMessage($libraryActionMessages, cblLibraryData.text_processing || 'Processing selected...', 'loading', false); $.ajax({ url: cblLibraryData.ajax_url, type: 'POST', data: { action: 'cbl_fetch_selected_book_covers_isbns', book_keys: selectedBookKeys, nonce: cblLibraryData.nonce_selective_fetch }, dataType: 'json', success: function(response) { if (response.success) { displayMessage($libraryActionMessages, response.data.message, 'success'); $statusDiv.text(response.data.message).addClass('success'); // Dynamically update rows if data is provided if (response.data.processed_books) { $.each(response.data.processed_books, function(bookKey, details) { var $row = $('#book-row-' + bookKey); if ($row.length) { if (details.isbn) $row.find('input[name="book_data['+bookKey+'][isbn]"]').val(details.isbn); if (details.cover_url) { $row.find('input[name="book_data['+bookKey+'][cover_url]"]').val(details.cover_url); $row.find('.library-cover-thumb').attr('src', details.cover_url); } // Could update title/author if necessary, but typically not changed by cover fetch } }); } if (response.data.updated_count > 0 && confirm('Selective fetch complete. ' + response.data.message + ' Some books were updated. Reload page to ensure all data is current? (Or save changes manually if you edited other fields).')) { // window.location.reload(); // Or just inform the user } } else { displayMessage($libraryActionMessages, response.data.message || 'Could not update selected books.', 'error', false); $statusDiv.text(response.data.message || 'Error processing selected.').addClass('error'); } }, error: function() { displayMessage($libraryActionMessages, 'A critical error occurred during selective fetch.', 'error', false); $statusDiv.text('Critical error during selective fetch.').addClass('error'); }, complete: function() { $button.prop('disabled', false); $('#fetch-all-covers-button').prop('disabled', false); $spinner.removeClass('is-active').css('visibility', 'hidden'); // Uncheck all after processing $('#select-all-books-checkbox').prop('checked', false); $('.select-book-checkbox').prop('checked', false); } }); }); // "Select All" checkbox for books in table view $('#select-all-books-checkbox').on('change', function() { $('.select-book-checkbox').prop('checked', $(this).prop('checked')); }); $('.user-library-table').on('change', '.select-book-checkbox', function() { if ($('.select-book-checkbox:checked').length === $('.select-book-checkbox').length) { $('#select-all-books-checkbox').prop('checked', true); } else { $('#select-all-books-checkbox').prop('checked', false); } }); // --- Individual Book "Find Info" Button (in library table) --- $('.user-library-table').on('click', '.search-book-info-button', function() { var $button = $(this); var bookKey = $button.data('book-key'); var nonce = $button.data('nonce'); var $row = $('#book-row-' + bookKey); var $spinner = $row.find('#spinner-' + bookKey); var $titleInput = $row.find('input[name="book_data[' + bookKey + '][title]"]'); var $authorInput = $row.find('input[name="book_data[' + bookKey + '][author]"]'); var $isbnInput = $row.find('input[name="book_data[' + bookKey + '][isbn]"]'); var $coverInput = $row.find('input[name="book_data[' + bookKey + '][cover_url]"]'); var $coverImg = $row.find('.library-cover-thumb'); $button.prop('disabled', true); $spinner.addClass('is-active').css('visibility', 'visible'); displayMessage($libraryActionMessages, 'Fetching info for ' + $titleInput.val() + '...', 'loading', false); $.ajax({ url: cblLibraryData.ajax_url, type: 'POST', data: { action: 'cbl_fetch_individual_book_info', book_key: bookKey, nonce: nonce, title: $titleInput.val(), author: $authorInput.val(), isbn: $isbnInput.val() }, dataType: 'json', success: function(response) { if (response.success && response.data.book_details) { var details = response.data.book_details; if (details.title && !$titleInput.val()) $titleInput.val(details.title); if (details.author && !$authorInput.val()) $authorInput.val(details.author); if (details.isbn && !$isbnInput.val()) $isbnInput.val(details.isbn); var newCoverUrl = details.cover_url || cblLibraryData.default_cover_url; $coverInput.val(details.cover_url || ''); $coverImg.attr('src', newCoverUrl); if(details.publisher && $row.find('input[name="book_data['+bookKey+'][edition]"]').length && !$row.find('input[name="book_data['+bookKey+'][edition]"]').val()){ var editionText = details.publisher; if(details.year_published) editionText += ', ' + details.year_published; $row.find('input[name="book_data['+bookKey+'][edition]"]').val(editionText.trim()); } displayMessage($libraryActionMessages, response.data.message || 'Book info updated.', 'success'); } else { displayMessage($libraryActionMessages, response.data.message || 'Could not fetch info.', 'error', false); } }, error: function() { displayMessage($libraryActionMessages, 'Error fetching book info.', 'error', false); }, complete: function() { $button.prop('disabled', false); $spinner.removeClass('is-active').css('visibility', 'hidden');} }); }); // --- Gallery View Modal --- var $bookDetailModal = $('#cbl-book-modal'); var $modalBody = $('#cbl-modal-body'); if ($bookDetailModal.length) { $('.cbl-gallery-view').on('click keypress', '.cbl-book-item', function(e) { if (e.type === 'keypress' && (e.key !== 'Enter' && e.key !== ' ')) return; e.preventDefault(); var bookDataString = $(this).data('book-details'); if (bookDataString) { var book = typeof bookDataString === 'string' ? JSON.parse(bookDataString) : bookDataString; var modalHtml = '
' + escapeHtml(book.title) + '
'; modalHtml += '
'; modalHtml += '

' + escapeHtml(book.title) + '

'; modalHtml += '

' + (cblLibraryData.text_author || 'Author') + ': ' + escapeHtml(book.author) + '

'; if (book.series_name) modalHtml += '

' + (cblLibraryData.text_series || 'Series') + ': ' + escapeHtml(book.series_name) + (book.series_number ? ' #' + escapeHtml(book.series_number) : '') + '

'; if (book.isbn) modalHtml += '

ISBN: ' + escapeHtml(book.isbn) + '

'; if (book.book_location) modalHtml += '

' + (cblLibraryData.text_location || 'Location') + ': ' + escapeHtml(book.book_location) + '

'; // Display Location if (book.format) modalHtml += '

' + (cblLibraryData.text_format || 'Format') + ': ' + escapeHtml(book.format) + '

'; if (book.edition) modalHtml += '

' + (cblLibraryData.text_edition || 'Edition') + ': ' + escapeHtml(book.edition) + '

'; if (book.chilli_rating && parseInt(book.chilli_rating) > 0) modalHtml += '

' + (cblLibraryData.text_spice || 'Spice Rating') + ': ' + '🌶️'.repeat(parseInt(book.chilli_rating)) + '

'; if (book.read_status) modalHtml += '

' + (cblLibraryData.text_status || 'Status') + ': ' + escapeHtml(book.read_status.replace('-', ' ').replace(/\b\w/g, l => l.toUpperCase())) + '

'; if (book.date_finished) modalHtml += '

' + (cblLibraryData.text_date_finished || 'Date Finished') + ': ' + escapeHtml(book.date_finished) + '

'; if (book.notes) modalHtml += '

' + (cblLibraryData.text_notes || 'Notes/Review') + ':

' + escapeHtml(book.notes).replace(/\n/g, '
') + '
'; if (book.favorite_quotes) modalHtml += '

' + (cblLibraryData.text_quotes || 'Favorite Quotes') + ':

' + escapeHtml(book.favorite_quotes).replace(/\n/g, '
') + '
'; modalHtml += '
'; $modalBody.html(modalHtml); $bookDetailModal.show().attr('aria-hidden', 'false'); $bookDetailModal.find('.cbl-modal-close').focus(); } }); function closeBookDetailModal() { $bookDetailModal.hide().attr('aria-hidden', 'true'); $modalBody.html(''); } $bookDetailModal.on('click', '.cbl-modal-close', closeBookDetailModal); $bookDetailModal.on('click', function(event) { if (event.target == $bookDetailModal[0]) closeBookDetailModal(); }); $(document).on('keydown', function(event) { if (event.key === "Escape" && $bookDetailModal.is(':visible')) closeBookDetailModal(); }); } // End if modal exists } // End if #custom-user-library exists // Helper to escape HTML (basic) function escapeHtml(unsafe) { if (typeof unsafe !== 'string') { if (unsafe === null || typeof unsafe === 'undefined') return ''; if (typeof unsafe === 'number' || typeof unsafe === 'boolean') return unsafe.toString(); console.warn("escapeHtml received non-string, non-primitive type:", unsafe); return ''; } return unsafe.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } }); // End jQuery(document).ready
Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/bookshive.co.uk/httpdocs/wp-content/plugins/Custom Book Input/includes/cbl-shortcodes.php:1) in /var/www/vhosts/bookshive.co.uk/httpdocs/wp-includes/pluggable.php on line 1450

Warning: Cannot modify header information - headers already sent by (output started at /var/www/vhosts/bookshive.co.uk/httpdocs/wp-content/plugins/Custom Book Input/includes/cbl-shortcodes.php:1) in /var/www/vhosts/bookshive.co.uk/httpdocs/wp-includes/pluggable.php on line 1453