feat(web): update frontend TypeScript modules and API types
This commit updates the web frontend TypeScript modules: Core modules: - admin.ts: Admin panel functionality and user management - analytics.ts: Analytics dashboard and data visualization - api-explorer.ts: Interactive API documentation explorer - api.ts: Core API client with request/response handling - collections.ts: Book collection management UI - conflicts.ts: Sync conflict resolution interface - custom-section-builder.ts: Dynamic section builder for UI - docs.ts: Documentation viewer and navigation - dom.ts: DOM manipulation utilities and helpers - header.ts: Application header with navigation - library.ts: Library view and book grid management - linking.ts: Device-book linking interface - password_validation.ts: Client-side password strength validation - queue.ts: Device sync queue management UI - search.ts: Full-text search with Lunr integration - storage.ts: Local storage and cache management - theme.ts: Theme management and CSS variable updates - themeDropdown.ts: Theme selector dropdown component - toast.ts: Toast notification system - woodPaneling.ts: Visual theme effects - woodPanelingInit.ts: Visual effects initialization Type definitions: - api.d.ts: Updated TypeScript definitions for API responses These updates enhance the frontend with improved functionality for book management, device synchronization, and user experience.
This commit is contained in:
+110
-99
@@ -3,174 +3,185 @@
|
||||
|
||||
// Validation check functions
|
||||
function hasMinimumLength(password: string): boolean {
|
||||
return password.length >= 8;
|
||||
return password.length >= 8;
|
||||
}
|
||||
|
||||
function hasUppercase(password: string): boolean {
|
||||
return /[A-Z]/.test(password);
|
||||
return /[A-Z]/.test(password);
|
||||
}
|
||||
|
||||
function hasLowercase(password: string): boolean {
|
||||
return /[a-z]/.test(password);
|
||||
return /[a-z]/.test(password);
|
||||
}
|
||||
|
||||
function hasNumber(password: string): boolean {
|
||||
return /[0-9]/.test(password);
|
||||
return /[0-9]/.test(password);
|
||||
}
|
||||
|
||||
function hasSpecialChar(password: string): boolean {
|
||||
return /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password);
|
||||
return /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password);
|
||||
}
|
||||
|
||||
function passwordsMatch(password: string, confirm: string): boolean {
|
||||
if (!password && !confirm) {
|
||||
return false;
|
||||
}
|
||||
return password === confirm;
|
||||
if (!password && !confirm) {
|
||||
return false;
|
||||
}
|
||||
return password === confirm;
|
||||
}
|
||||
|
||||
function hasUsername(username: string): boolean {
|
||||
return username.trim().length > 0;
|
||||
return username.trim().length > 0;
|
||||
}
|
||||
|
||||
function hasEmail(email: string): boolean {
|
||||
return email.trim().length > 0;
|
||||
return email.trim().length > 0;
|
||||
}
|
||||
|
||||
// UI update functions
|
||||
function updateRequirementStatus(elementId: string, passed: boolean): void {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const icon = element.querySelector('.requirement-icon');
|
||||
if (!icon) {
|
||||
return;
|
||||
}
|
||||
const icon = element.querySelector(".requirement-icon");
|
||||
if (!icon) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (passed) {
|
||||
icon.textContent = '✓';
|
||||
icon.className = 'requirement-icon text-green-500';
|
||||
element.style.color = 'var(--text-primary)';
|
||||
} else {
|
||||
icon.textContent = '○';
|
||||
icon.className = 'requirement-icon';
|
||||
element.style.color = 'var(--text-secondary)';
|
||||
}
|
||||
if (passed) {
|
||||
icon.textContent = "✓";
|
||||
icon.className = "requirement-icon text-green-500";
|
||||
element.style.color = "var(--text-primary)";
|
||||
} else {
|
||||
icon.textContent = "○";
|
||||
icon.className = "requirement-icon";
|
||||
element.style.color = "var(--text-secondary)";
|
||||
}
|
||||
}
|
||||
|
||||
function updateSubmitButton(allPassed: boolean): void {
|
||||
const button = document.getElementById('register-btn') as HTMLButtonElement;
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
const button = document.getElementById("register-btn") as HTMLButtonElement;
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (allPassed) {
|
||||
button.disabled = false;
|
||||
button.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||
} else {
|
||||
button.disabled = true;
|
||||
button.classList.add('opacity-50', 'cursor-not-allowed');
|
||||
}
|
||||
if (allPassed) {
|
||||
button.disabled = false;
|
||||
button.classList.remove("opacity-50", "cursor-not-allowed");
|
||||
} else {
|
||||
button.disabled = true;
|
||||
button.classList.add("opacity-50", "cursor-not-allowed");
|
||||
}
|
||||
}
|
||||
|
||||
// Main validation orchestrator
|
||||
function validateAll(): void {
|
||||
const passwordField = document.getElementById('password') as HTMLInputElement;
|
||||
const confirmField = document.getElementById('confirm-password') as HTMLInputElement;
|
||||
const usernameField = document.getElementById('username') as HTMLInputElement;
|
||||
const emailField = document.getElementById('email') as HTMLInputElement;
|
||||
const passwordField = document.getElementById("password") as HTMLInputElement;
|
||||
const confirmField = document.getElementById(
|
||||
"confirm-password",
|
||||
) as HTMLInputElement;
|
||||
const usernameField = document.getElementById("username") as HTMLInputElement;
|
||||
const emailField = document.getElementById("email") as HTMLInputElement;
|
||||
|
||||
if (!passwordField || !confirmField || !usernameField || !emailField) {
|
||||
return;
|
||||
}
|
||||
if (!passwordField || !confirmField || !usernameField || !emailField) {
|
||||
return;
|
||||
}
|
||||
|
||||
const password = passwordField.value;
|
||||
const confirm = confirmField.value;
|
||||
const username = usernameField.value;
|
||||
const email = emailField.value;
|
||||
const password = passwordField.value;
|
||||
const confirm = confirmField.value;
|
||||
const username = usernameField.value;
|
||||
const email = emailField.value;
|
||||
|
||||
// Check password requirements
|
||||
const hasLen = hasMinimumLength(password);
|
||||
const hasUpper = hasUppercase(password);
|
||||
const hasLower = hasLowercase(password);
|
||||
const hasNum = hasNumber(password);
|
||||
const hasSpecial = hasSpecialChar(password);
|
||||
const doMatch = passwordsMatch(password, confirm);
|
||||
const hasUser = hasUsername(username);
|
||||
const hasEmailAddr = hasEmail(email);
|
||||
// Check password requirements
|
||||
const hasLen = hasMinimumLength(password);
|
||||
const hasUpper = hasUppercase(password);
|
||||
const hasLower = hasLowercase(password);
|
||||
const hasNum = hasNumber(password);
|
||||
const hasSpecial = hasSpecialChar(password);
|
||||
const doMatch = passwordsMatch(password, confirm);
|
||||
const hasUser = hasUsername(username);
|
||||
const hasEmailAddr = hasEmail(email);
|
||||
|
||||
// Update requirement indicators
|
||||
updateRequirementStatus('req-length', hasLen);
|
||||
updateRequirementStatus('req-upper', hasUpper);
|
||||
updateRequirementStatus('req-lower', hasLower);
|
||||
updateRequirementStatus('req-number', hasNum);
|
||||
updateRequirementStatus('req-special', hasSpecial);
|
||||
updateRequirementStatus('req-match', doMatch);
|
||||
// Update requirement indicators
|
||||
updateRequirementStatus("req-length", hasLen);
|
||||
updateRequirementStatus("req-upper", hasUpper);
|
||||
updateRequirementStatus("req-lower", hasLower);
|
||||
updateRequirementStatus("req-number", hasNum);
|
||||
updateRequirementStatus("req-special", hasSpecial);
|
||||
updateRequirementStatus("req-match", doMatch);
|
||||
|
||||
// Enable/disable submit button
|
||||
const allPassed = hasLen && hasUpper && hasLower && hasNum &&
|
||||
hasSpecial && doMatch && hasUser && hasEmailAddr;
|
||||
updateSubmitButton(allPassed);
|
||||
// Enable/disable submit button
|
||||
const allPassed =
|
||||
hasLen &&
|
||||
hasUpper &&
|
||||
hasLower &&
|
||||
hasNum &&
|
||||
hasSpecial &&
|
||||
doMatch &&
|
||||
hasUser &&
|
||||
hasEmailAddr;
|
||||
updateSubmitButton(allPassed);
|
||||
}
|
||||
|
||||
// Debounce function to avoid excessive validation calls
|
||||
let debounceTimer: number | null = null;
|
||||
|
||||
function debouncedValidation(): void {
|
||||
if (debounceTimer !== null) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
debounceTimer = window.setTimeout(() => {
|
||||
validateAll();
|
||||
debounceTimer = null;
|
||||
}, 100);
|
||||
if (debounceTimer !== null) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
debounceTimer = window.setTimeout(() => {
|
||||
validateAll();
|
||||
debounceTimer = null;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Event handlers (PASSIVE - no preventDefault, doesn't block password managers)
|
||||
function onPasswordInput(): void {
|
||||
debouncedValidation();
|
||||
debouncedValidation();
|
||||
}
|
||||
|
||||
function onConfirmInput(): void {
|
||||
debouncedValidation();
|
||||
debouncedValidation();
|
||||
}
|
||||
|
||||
function onUsernameInput(): void {
|
||||
debouncedValidation();
|
||||
debouncedValidation();
|
||||
}
|
||||
|
||||
function onEmailInput(): void {
|
||||
debouncedValidation();
|
||||
debouncedValidation();
|
||||
}
|
||||
|
||||
// Initialization
|
||||
function initPasswordValidation(): void {
|
||||
const passwordField = document.getElementById('password') as HTMLInputElement;
|
||||
const confirmField = document.getElementById('confirm-password') as HTMLInputElement;
|
||||
const usernameField = document.getElementById('username') as HTMLInputElement;
|
||||
const emailField = document.getElementById('email') as HTMLInputElement;
|
||||
const passwordField = document.getElementById("password") as HTMLInputElement;
|
||||
const confirmField = document.getElementById(
|
||||
"confirm-password",
|
||||
) as HTMLInputElement;
|
||||
const usernameField = document.getElementById("username") as HTMLInputElement;
|
||||
const emailField = document.getElementById("email") as HTMLInputElement;
|
||||
|
||||
if (!passwordField || !confirmField || !usernameField || !emailField) {
|
||||
return;
|
||||
}
|
||||
if (!passwordField || !confirmField || !usernameField || !emailField) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add passive event listeners - don't prevent default, don't block password managers
|
||||
passwordField.addEventListener('input', onPasswordInput, { passive: true });
|
||||
passwordField.addEventListener('paste', onPasswordInput, { passive: true });
|
||||
// Add passive event listeners - don't prevent default, don't block password managers
|
||||
passwordField.addEventListener("input", onPasswordInput, { passive: true });
|
||||
passwordField.addEventListener("paste", onPasswordInput, { passive: true });
|
||||
|
||||
confirmField.addEventListener('input', onConfirmInput, { passive: true });
|
||||
confirmField.addEventListener('paste', onConfirmInput, { passive: true });
|
||||
confirmField.addEventListener("input", onConfirmInput, { passive: true });
|
||||
confirmField.addEventListener("paste", onConfirmInput, { passive: true });
|
||||
|
||||
usernameField.addEventListener('input', onUsernameInput, { passive: true });
|
||||
usernameField.addEventListener('paste', onUsernameInput, { passive: true });
|
||||
usernameField.addEventListener("input", onUsernameInput, { passive: true });
|
||||
usernameField.addEventListener("paste", onUsernameInput, { passive: true });
|
||||
|
||||
emailField.addEventListener('input', onEmailInput, { passive: true });
|
||||
emailField.addEventListener('paste', onEmailInput, { passive: true });
|
||||
emailField.addEventListener("input", onEmailInput, { passive: true });
|
||||
emailField.addEventListener("paste", onEmailInput, { passive: true });
|
||||
|
||||
// Initial validation
|
||||
validateAll();
|
||||
// Initial validation
|
||||
validateAll();
|
||||
}
|
||||
|
||||
// Export for use in template
|
||||
|
||||
Reference in New Issue
Block a user