410 lines
14 KiB
JavaScript
410 lines
14 KiB
JavaScript
// Copyright 2026 Umbra Tools (umbratools.com)
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
/**
|
|
* BD BookShelfHelper Admin JavaScript
|
|
* Enhanced Data Entry Interface
|
|
*/
|
|
|
|
(function($) {
|
|
'use strict';
|
|
|
|
const BDDataEntry = {
|
|
currentLanguage: 'en',
|
|
autoSaveInterval: null,
|
|
isDirty: false,
|
|
saveInProgress: false,
|
|
|
|
init: function() {
|
|
this.initLanguageTabs();
|
|
this.initSectionCollapse();
|
|
this.initFieldValidation();
|
|
this.initAutoSave();
|
|
this.initPreviewButton();
|
|
this.initDirtyTracking();
|
|
this.initSaveButton();
|
|
this.initFieldBlurSave();
|
|
},
|
|
|
|
/**
|
|
* Initialize language tabs
|
|
*/
|
|
initLanguageTabs: function() {
|
|
const tabs = $('.bd-lang-tab');
|
|
const contents = $('.bd-language-content');
|
|
|
|
tabs.on('click', function() {
|
|
const lang = $(this).data('lang');
|
|
|
|
tabs.removeClass('active');
|
|
$(this).addClass('active');
|
|
|
|
contents.removeClass('active');
|
|
$('#bd-lang-' + lang).addClass('active');
|
|
|
|
BDDataEntry.currentLanguage = lang;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialize section collapse/expand
|
|
*/
|
|
initSectionCollapse: function() {
|
|
$('.bd-section-header').on('click', function() {
|
|
const header = $(this);
|
|
const content = header.next('.bd-section-content');
|
|
|
|
header.toggleClass('collapsed');
|
|
content.toggleClass('collapsed');
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialize field validation
|
|
*/
|
|
initFieldValidation: function() {
|
|
// ISBN-13 validation
|
|
$('input[name="isbn"]').on('blur', function() {
|
|
const value = $(this).val().replace(/[-\s]/g, '');
|
|
if (value && !/^\d{13}$/.test(value)) {
|
|
BDDataEntry.setFieldInvalid($(this), 'ISBN-13 must be exactly 13 digits');
|
|
} else if (value) {
|
|
BDDataEntry.setFieldValid($(this));
|
|
}
|
|
});
|
|
|
|
// ASIN validation
|
|
$('input[name="asin"]').on('blur', function() {
|
|
const value = $(this).val().replace(/[-\s]/g, '');
|
|
if (value && !/^[A-Z0-9]{10}$/i.test(value)) {
|
|
BDDataEntry.setFieldInvalid($(this), 'ASIN must be exactly 10 alphanumeric characters');
|
|
} else if (value) {
|
|
BDDataEntry.setFieldValid($(this));
|
|
}
|
|
});
|
|
|
|
// URL validation
|
|
$('input[type="url"]').on('blur', function() {
|
|
const value = $(this).val();
|
|
if (value && !BDDataEntry.isValidUrl(value)) {
|
|
BDDataEntry.setFieldInvalid($(this), 'Please enter a valid URL');
|
|
} else if (value) {
|
|
BDDataEntry.setFieldValid($(this));
|
|
}
|
|
});
|
|
|
|
// Date validation
|
|
$('input[type="date"]').on('blur', function() {
|
|
const value = $(this).val();
|
|
if (value && !BDDataEntry.isValidDate(value)) {
|
|
BDDataEntry.setFieldInvalid($(this), 'Please enter a valid date (YYYY-MM-DD)');
|
|
} else if (value) {
|
|
BDDataEntry.setFieldValid($(this));
|
|
}
|
|
});
|
|
|
|
// Required field validation on submit
|
|
$('#bd-data-entry-form').on('submit', function(e) {
|
|
let isValid = true;
|
|
|
|
$('.bd-field.required input, .bd-field.required select, .bd-field.required textarea').each(function() {
|
|
if (!$(this).val() || !$(this).val().trim()) {
|
|
BDDataEntry.setFieldInvalid($(this), 'This field is required');
|
|
isValid = false;
|
|
}
|
|
});
|
|
|
|
// Validate ISBN
|
|
const isbn = $('input[name="isbn"]').val().replace(/[-\s]/g, '');
|
|
if (isbn && !/^\d{13}$/.test(isbn)) {
|
|
BDDataEntry.setFieldInvalid($('input[name="isbn"]'), 'ISBN-13 must be exactly 13 digits');
|
|
isValid = false;
|
|
}
|
|
|
|
// Validate ASIN
|
|
const asin = $('input[name="asin"]').val().replace(/[-\s]/g, '');
|
|
if (asin && !/^[A-Z0-9]{10}$/i.test(asin)) {
|
|
BDDataEntry.setFieldInvalid($('input[name="asin"]'), 'ASIN must be exactly 10 alphanumeric characters');
|
|
isValid = false;
|
|
}
|
|
|
|
if (!isValid) {
|
|
e.preventDefault();
|
|
BDDataEntry.showStatus('Please fix the validation errors before saving.', 'error');
|
|
$('html, body').animate({
|
|
scrollTop: $('.bd-data-entry input.invalid').first().offset().top - 100
|
|
}, 300);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Set field as valid
|
|
*/
|
|
setFieldValid: function($field) {
|
|
$field.removeClass('invalid').addClass('valid');
|
|
$field.next('.bd-validation-message').remove();
|
|
},
|
|
|
|
/**
|
|
* Set field as invalid
|
|
*/
|
|
setFieldInvalid: function($field, message) {
|
|
$field.removeClass('valid').addClass('invalid');
|
|
|
|
let $msg = $field.next('.bd-validation-message');
|
|
if (!$msg.length) {
|
|
$msg = $('<span class="bd-validation-message"></span>');
|
|
$field.after($msg);
|
|
}
|
|
$msg.text(message);
|
|
},
|
|
|
|
/**
|
|
* Check if URL is valid
|
|
*/
|
|
isValidUrl: function(string) {
|
|
try {
|
|
new URL(string);
|
|
return true;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Check if date is valid
|
|
*/
|
|
isValidDate: function(string) {
|
|
return /^\d{4}-\d{2}-\d{2}$/.test(string) && !isNaN(Date.parse(string));
|
|
},
|
|
|
|
/**
|
|
* Initialize auto-save on interval
|
|
*/
|
|
initAutoSave: function() {
|
|
// Auto-save every 30 seconds
|
|
this.autoSaveInterval = setInterval(function() {
|
|
if (BDDataEntry.isDirty && !BDDataEntry.saveInProgress) {
|
|
BDDataEntry.autoSave();
|
|
}
|
|
}, 30000);
|
|
|
|
// Save on page unload (beforeunload)
|
|
$(window).on('beforeunload', function() {
|
|
if (BDDataEntry.isDirty && !BDDataEntry.saveInProgress) {
|
|
BDDataEntry.autoSave(true);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialize save on field blur
|
|
*/
|
|
initFieldBlurSave: function() {
|
|
// Save individual field on blur
|
|
$('#bd-data-entry-form').on('blur', 'input, select, textarea', function() {
|
|
const $field = $(this);
|
|
const fieldName = $field.attr('name');
|
|
|
|
// Skip certain fields
|
|
if (!fieldName || fieldName === 'post_id' || fieldName === '_wpnonce' || fieldName === '_wp_http_referer') {
|
|
return;
|
|
}
|
|
|
|
if (BDDataEntry.isDirty && !BDDataEntry.saveInProgress) {
|
|
// Small delay to allow multiple field changes
|
|
clearTimeout(BDDataEntry.blurSaveTimeout);
|
|
BDDataEntry.blurSaveTimeout = setTimeout(function() {
|
|
BDDataEntry.autoSave();
|
|
}, 500);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Initialize save button
|
|
*/
|
|
initSaveButton: function() {
|
|
$('#bd-data-entry-form').on('submit', function(e) {
|
|
// Don't prevent default - let the form submit normally for full save
|
|
// But do show loading state
|
|
BDDataEntry.showSaveIndicator('saving');
|
|
});
|
|
|
|
// Listen for the submit button click
|
|
$('#bd_save_data_entry').on('click', function() {
|
|
BDDataEntry.saveInProgress = true;
|
|
BDDataEntry.showSaveIndicator('saving');
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Show save indicator
|
|
*/
|
|
showSaveIndicator: function(status) {
|
|
const $indicator = $('.bd-autosave-indicator');
|
|
if (!$indicator.length) {
|
|
// Create indicator if it doesn't exist
|
|
$('#bd-data-entry-form').after('<div class="bd-autosave-indicator"></div>');
|
|
}
|
|
|
|
const $ind = $('.bd-autosave-indicator');
|
|
|
|
if (status === 'saving') {
|
|
$ind.removeClass('saved error').addClass('saving visible').text('Saving...');
|
|
} else if (status === 'saved') {
|
|
$ind.removeClass('saving error').addClass('saved visible').text('Saved successfully!');
|
|
BDDataEntry.isDirty = false;
|
|
setTimeout(function() {
|
|
$ind.removeClass('visible saved');
|
|
}, 3000);
|
|
} else if (status === 'error') {
|
|
$ind.removeClass('saving saved').addClass('error visible').text('Save failed. Please try again.');
|
|
setTimeout(function() {
|
|
$ind.removeClass('visible error');
|
|
}, 5000);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Perform auto-save
|
|
*/
|
|
autoSave: function(sync) {
|
|
if (this.saveInProgress || !this.isDirty) {
|
|
return;
|
|
}
|
|
|
|
this.saveInProgress = true;
|
|
this.showSaveIndicator('saving');
|
|
|
|
const formData = $('#bd-data-entry-form').serialize();
|
|
const postId = this.getPostId();
|
|
|
|
if (!postId) {
|
|
this.showSaveIndicator('error');
|
|
this.saveInProgress = false;
|
|
return;
|
|
}
|
|
|
|
formData += '&action=bdm_autosave&post_id=' + postId;
|
|
|
|
$.ajax({
|
|
url: ajaxurl,
|
|
type: 'POST',
|
|
data: formData,
|
|
success: function(response) {
|
|
if (response.success) {
|
|
BDDataEntry.isDirty = false;
|
|
BDDataEntry.showSaveIndicator('saved');
|
|
|
|
// Mark the form as saved (removes dirty state)
|
|
$('#bd-data-entry-form').data('saved', true);
|
|
} else {
|
|
BDDataEntry.showSaveIndicator('error');
|
|
}
|
|
},
|
|
error: function() {
|
|
BDDataEntry.showSaveIndicator('error');
|
|
},
|
|
complete: function() {
|
|
BDDataEntry.saveInProgress = false;
|
|
},
|
|
async: sync !== true
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Get current post ID
|
|
*/
|
|
getPostId: function() {
|
|
// Try to get from hidden post_id input (data entry form)
|
|
const postIdFromForm = $('input[name="post_id"]').val();
|
|
if (postIdFromForm) {
|
|
return postIdFromForm;
|
|
}
|
|
|
|
// Fallback to post_ID (standard WordPress)
|
|
return $('#post_ID').val() || 0;
|
|
},
|
|
|
|
/**
|
|
* Initialize preview button
|
|
*/
|
|
initPreviewButton: function() {
|
|
$('#bd-preview-btn').on('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
// Save first
|
|
$('#bd-data-entry-form').trigger('submit');
|
|
|
|
const postId = BDDataEntry.getPostId();
|
|
if (postId) {
|
|
window.open(
|
|
post.previewUrl + '&preview=true',
|
|
'_blank'
|
|
);
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Track dirty state
|
|
*/
|
|
initDirtyTracking: function() {
|
|
const form = $('#bd-data-entry-form');
|
|
|
|
form.on('change input', 'input, select, textarea', function() {
|
|
BDDataEntry.isDirty = true;
|
|
form.data('saved', false);
|
|
});
|
|
|
|
// Also track on keyup for immediate feedback
|
|
form.on('keyup', 'input, textarea', function() {
|
|
BDDataEntry.isDirty = true;
|
|
form.data('saved', false);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Show status message
|
|
*/
|
|
showStatus: function(message, type) {
|
|
// Remove any existing messages
|
|
$('.bd-status-message').remove();
|
|
|
|
const $status = $('<div class="bd-status-message ' + type + '">' + message + '</div>');
|
|
$('.wrap h1').after($status);
|
|
|
|
$status.fadeIn();
|
|
|
|
setTimeout(function() {
|
|
$status.fadeOut(function() {
|
|
$(this).remove();
|
|
});
|
|
}, 5000);
|
|
}
|
|
};
|
|
|
|
// Initialize on DOM ready
|
|
$(document).ready(function() {
|
|
if ($('#bd-data-entry-form').length) {
|
|
BDDataEntry.init();
|
|
}
|
|
|
|
// Also handle successful form submission
|
|
$(document).ajaxComplete(function(event, xhr, settings) {
|
|
if (settings.url === ajaxurl && xhr.responseJSON && xhr.responseJSON.success) {
|
|
// Form was saved successfully
|
|
$('.bd-autosave-indicator').removeClass('saving').addClass('saved visible').text('Saved successfully!');
|
|
BDDataEntry.isDirty = false;
|
|
|
|
setTimeout(function() {
|
|
$('.bd-autosave-indicator').removeClass('visible saved');
|
|
}, 3000);
|
|
}
|
|
});
|
|
});
|
|
|
|
})(jQuery);
|