Online Secure Private Journal App

Welcome to Your Private Journal

Please set a 4-digit PIN to secure your entries. This PIN is stored only on your device.

Enter PIN

Enter your 4-digit PIN to unlock your journal.

My Entries

Select an entry to read

Or create a new one to get started.

${date}

`; entryList.appendChild(item); }); } // --- DATA & LOGIC FUNCTIONS --- function loadEntries() { const storedEntries = localStorage.getItem(ENTRIES_KEY); entries = storedEntries ? JSON.parse(storedEntries) : []; } function saveEntries() { localStorage.setItem(ENTRIES_KEY, JSON.stringify(entries)); } function unlockApp() { loadEntries(); renderEntryList(); switchView('mainApp'); switchContentPanel('welcome'); pinEntryInput.value = ''; } function lockApp() { currentEntryId = null; switchView('lockScreen'); } function viewEntry(id) { const entry = entries.find(e => e.id === id); if (!entry) return; currentEntryId = id; entryTitleView.textContent = entry.title; const date = new Date(entry.timestamp).toLocaleString('en-US', { dateStyle: 'full', timeStyle: 'short' }); entryDateView.textContent = date; // Sanitize and format body content for display const formattedBody = entry.content.replace(/\n/g, '
'); entryBodyView.innerHTML = formattedBody; switchContentPanel('view'); renderEntryList(); // To update selection style } function editEntry(id) { const entry = entries.find(e => e.id === id); if (!entry) return; currentEntryId = id; entryTitleEdit.value = entry.title; entryBodyEdit.value = entry.content; switchContentPanel('edit'); } function saveEntry() { const title = entryTitleEdit.value.trim() || 'Untitled Entry'; const content = entryBodyEdit.value.trim(); if (currentEntryId === 'new') { // Creating a new entry const newEntry = { id: Date.now(), title, content, timestamp: Date.now() }; entries.push(newEntry); currentEntryId = newEntry.id; } else { // Updating an existing entry const entry = entries.find(e => e.id === currentEntryId); if (entry) { entry.title = title; entry.content = content; entry.timestamp = Date.now(); // Update timestamp on edit } } saveEntries(); renderEntryList(); viewEntry(currentEntryId); } function deleteEntry() { if (!currentEntryId) return; // Simple confirmation, as per spec (no system dialogs) const originalText = deleteEntryBtn.innerText; if (deleteEntryBtn.dataset.confirming !== 'true') { deleteEntryBtn.innerText = 'Are you sure? Click again to delete.'; deleteEntryBtn.dataset.confirming = 'true'; setTimeout(() => { deleteEntryBtn.innerText = originalText; deleteEntryBtn.dataset.confirming = 'false'; }, 3000); return; } entries = entries.filter(e => e.id !== currentEntryId); saveEntries(); currentEntryId = null; renderEntryList(); switchContentPanel('welcome'); } async function downloadPDF() { const entry = entries.find(e => e.id === currentEntryId); if (!entry) return; const { jsPDF } = window.jspdf; const date = new Date(entry.timestamp).toLocaleString('en-US', { dateStyle: 'full', timeStyle: 'short' }); const formattedBody = entry.content.replace(/\n/g, '
'); pdfContent.innerHTML = `

${entry.title}

${date}

${formattedBody}
`; pdfContent.classList.remove('hidden'); try { const canvas = await html2canvas(pdfContent, { scale: 2 }); const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF('p', 'pt', 'a4'); const imgProps = pdf.getImageProperties(imgData); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width; pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); const safeTitle = entry.title.replace(/[^a-z0-9]/gi, '_').toLowerCase(); pdf.save(`${safeTitle}.pdf`); } catch (error) { console.error("PDF Generation Error:", error); } finally { pdfContent.classList.add('hidden'); } } // --- EVENT LISTENERS --- setPinBtn.addEventListener('click', () => { const pin = pinSetupInput.value; if (pin.length === 4 && /^\d{4}$/.test(pin)) { localStorage.setItem(PIN_KEY, pin); unlockApp(); } else { pinSetupError.textContent = 'Please enter a valid 4-digit PIN.'; } }); unlockBtn.addEventListener('click', () => { const storedPin = localStorage.getItem(PIN_KEY); if (pinEntryInput.value === storedPin) { unlockApp(); } else { pinEntryError.textContent = 'Incorrect PIN. Please try again.'; pinEntryInput.value = ''; } }); // Allow Enter key to submit PIN pinSetupInput.addEventListener('keyup', (e) => { if (e.key === 'Enter') setPinBtn.click(); }); pinEntryInput.addEventListener('keyup', (e) => { if (e.key === 'Enter') unlockBtn.click(); }); lockBtn.addEventListener('click', lockApp); newEntryBtn.addEventListener('click', () => { currentEntryId = 'new'; entryTitleEdit.value = ''; entryBodyEdit.value = ''; switchContentPanel('edit'); }); entryList.addEventListener('click', (e) => { const item = e.target.closest('.entry-item'); if (item) { viewEntry(parseInt(item.dataset.id, 10)); } }); saveEntryBtn.addEventListener('click', saveEntry); cancelEditBtn.addEventListener('click', () => { if (currentEntryId === 'new' || !currentEntryId) { switchContentPanel('welcome'); } else { viewEntry(currentEntryId); } }); editEntryBtn.addEventListener('click', () => editEntry(currentEntryId)); deleteEntryBtn.addEventListener('click', deleteEntry); downloadPdfBtn.addEventListener('click', downloadPDF); // --- INITIALIZATION --- function init() { const storedPin = localStorage.getItem(PIN_KEY); if (storedPin) { switchView('lockScreen'); } else { switchView('pinSetup'); } } init(); });
Scroll to Top