pdftools / static /js /app.js
Shivakafle038's picture
PDF Tools Web App - compress, convert, watermark removal
32a841c
/**
* LocalTools - Main Application
* Handles navigation, theme, and global features
*/
document.addEventListener('DOMContentLoaded', () => {
initTheme();
initNavigation();
initMobileMenu();
initSetupNotice();
initPdfEditor();
initImageScraper();
initGlobalShortcuts();
initPdfTools();
initImageTools();
});
/**
* Theme Management
*/
function initTheme() {
const themeToggle = document.getElementById('themeToggle');
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
themeToggle.addEventListener('click', () => {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
showToast(
newTheme === 'dark' ? 'Dark Mode' : 'Light Mode',
'Theme updated',
'success',
2000
);
});
}
/**
* Setup Notice
*/
function initSetupNotice() {
const notice = document.getElementById('setupNotice');
const closeBtn = document.getElementById('closeSetupNotice');
// Check if dismissed
if (localStorage.getItem('setupNoticeDismissed')) {
notice.classList.add('hidden');
}
// Close button
closeBtn.addEventListener('click', () => {
notice.classList.add('hidden');
localStorage.setItem('setupNoticeDismissed', 'true');
});
// Copy buttons
document.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const text = btn.dataset.copy;
try {
await navigator.clipboard.writeText(text);
btn.classList.add('copied');
showToast('Copied!', text, 'success', 2000);
setTimeout(() => {
btn.classList.remove('copied');
}, 2000);
} catch (err) {
showToast('Copy failed', 'Please copy manually', 'error');
}
});
});
}
/**
* Sidebar Navigation
*/
function initNavigation() {
const navItems = document.querySelectorAll('.nav-item[data-page]');
const pages = document.querySelectorAll('.page');
navItems.forEach(item => {
item.addEventListener('click', () => {
const targetPage = item.dataset.page;
switchToPage(targetPage);
// Close mobile menu
closeMobileMenu();
});
});
}
function switchToPage(pageName) {
const navItems = document.querySelectorAll('.nav-item[data-page]');
const pages = document.querySelectorAll('.page');
navItems.forEach(item => {
item.classList.toggle('active', item.dataset.page === pageName);
});
pages.forEach(page => {
page.classList.toggle('active', page.id === `page-${pageName}`);
});
}
/**
* Mobile Menu
*/
function initMobileMenu() {
const menuBtn = document.getElementById('mobileMenuBtn');
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
menuBtn.addEventListener('click', () => {
sidebar.classList.toggle('open');
overlay.classList.toggle('active');
});
overlay.addEventListener('click', closeMobileMenu);
}
function closeMobileMenu() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.remove('open');
overlay.classList.remove('active');
}
/**
* Global Keyboard Shortcuts
*/
function initGlobalShortcuts() {
registerShortcut('1', () => switchToPage('pdf'), 'PDF Editor');
registerShortcut('2', () => switchToPage('watermark'), 'Watermark Remover');
registerShortcut('3', () => switchToPage('img2pdf'), 'Images to PDF');
registerShortcut('4', () => switchToPage('merge'), 'Merge PDFs');
registerShortcut('5', () => switchToPage('split'), 'Split PDF');
registerShortcut('6', () => switchToPage('images'), 'Image Scraper');
registerShortcut('ctrl+k', () => {
document.getElementById('themeToggle').click();
}, 'Toggle Theme');
registerShortcut('?', () => {
showKeyboardShortcutsModal();
}, 'Show Shortcuts');
// Nav button handlers
document.getElementById('navRecentFiles').addEventListener('click', showRecentFilesModal);
document.getElementById('navKeyboardShortcuts').addEventListener('click', showKeyboardShortcutsModal);
// Initialize watermark removal page
initWatermarkRemoval();
}
/**
* Recent Files Modal
*/
function showRecentFilesModal() {
const recentFiles = JSON.parse(localStorage.getItem('recentFiles') || '[]');
let content = '';
if (recentFiles.length === 0) {
content = `
<div class="empty-state" style="padding: 40px 20px;">
<div class="empty-state-illustration" style="width: 80px; height: 80px;">
<svg width="32" height="32" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="empty-state-title" style="font-size: 16px;">No Recent Files</div>
<div class="empty-state-message" style="font-size: 13px;">Files you work with will appear here</div>
</div>
`;
} else {
content = `
<div style="padding: 16px;">
<div style="font-size: 14px; font-weight: 600; margin-bottom: 12px; color: var(--text);">Recent Files</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
${recentFiles.map(file => `
<div style="display: flex; align-items: center; gap: 12px; padding: 12px; background: var(--border-light); border-radius: 8px;">
<svg width="20" height="20" fill="none" stroke="var(--primary)" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<div style="flex: 1; min-width: 0;">
<div style="font-size: 13px; font-weight: 500; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${file.name}</div>
<div style="font-size: 11px; color: var(--text-muted);">${file.date}</div>
</div>
</div>
`).join('')}
</div>
<button class="btn btn-secondary" style="width: 100%; margin-top: 12px;" onclick="clearRecentFiles()">Clear History</button>
</div>
`;
}
showModal('Recent Files', content);
}
function addToRecentFiles(name, type) {
const recentFiles = JSON.parse(localStorage.getItem('recentFiles') || '[]');
const newFile = {
name: name,
type: type,
date: new Date().toLocaleDateString()
};
// Remove duplicate if exists
const filtered = recentFiles.filter(f => f.name !== name);
filtered.unshift(newFile);
// Keep only last 10
localStorage.setItem('recentFiles', JSON.stringify(filtered.slice(0, 10)));
}
function clearRecentFiles() {
localStorage.removeItem('recentFiles');
closeModal();
showToast('Cleared', 'Recent files history cleared', 'success', 2000);
}
/**
* Keyboard Shortcuts Modal
*/
function showKeyboardShortcutsModal() {
const shortcuts = [
{ key: '1', desc: 'PDF Editor' },
{ key: '2', desc: 'Watermark Remover' },
{ key: '3', desc: 'Images to PDF' },
{ key: '4', desc: 'Merge PDFs' },
{ key: '5', desc: 'Split PDF' },
{ key: '6', desc: 'Image Scraper' },
{ key: 'Ctrl + K', desc: 'Toggle theme' },
{ key: '?', desc: 'Show this help' },
];
const content = `
<div style="padding: 16px;">
<div style="display: flex; flex-direction: column; gap: 8px;">
${shortcuts.map(s => `
<div style="display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; background: var(--border-light); border-radius: 8px;">
<span style="font-size: 13px; color: var(--text-secondary);">${s.desc}</span>
<kbd class="kbd" style="margin-left: 0;">${s.key}</kbd>
</div>
`).join('')}
</div>
</div>
`;
showModal('Keyboard Shortcuts', content);
}
/**
* Generic Modal
*/
function showModal(title, content) {
// Remove existing modal if any
closeModal();
const modal = document.createElement('div');
modal.className = 'modal-overlay active';
modal.id = 'genericModal';
modal.innerHTML = `
<div class="modal-content" style="max-width: 400px; width: 100%;">
<button class="modal-close" onclick="closeModal()">
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
<div style="padding: 20px 24px; border-bottom: 1px solid var(--border);">
<h3 style="font-size: 16px; font-weight: 600; color: var(--text);">${title}</h3>
</div>
${content}
</div>
`;
modal.addEventListener('click', (e) => {
if (e.target === modal) closeModal();
});
document.body.appendChild(modal);
}
function closeModal() {
const modal = document.getElementById('genericModal');
if (modal) modal.remove();
}
// Close modal on Escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeModal();
});