Big Note Book – Real-Time Personal Notes
https://cdn.tailwindcss.com
tailwind.config = {
theme: {
extend: {
colors: {
brand: {
50: ‘#f0f4f8’,
100: ‘#d9e2ec’,
200: ‘#bcccdc’,
300: ‘#9fb3c8’,
400: ‘#829ab1’,
500: ‘#627d98’,
600: ‘#486581’,
700: ‘#334e68’,
800: ‘#243b53’,
900: ‘#102a43’,
pastelBg: ‘#f8fafc’,
cardBg: ‘#ffffff’,
accent: ‘#6366f1’,
accentHover: ‘#4f46e5’
}
},
fontFamily: {
sans: [‘Inter’, ‘system-ui’, ‘-apple-system’, ‘BlinkMacSystemFont’, ‘Segoe UI’, ‘Roboto’, ‘sans-serif’]
}
}
}
}
https://www.gstatic.com/firebasejs/9.22.1/firebase-app-compat.js
https://www.gstatic.com/firebasejs/9.22.1/firebase-database-compat.js
/* Custom Styles for Rich Editor & Clean UI */
body {
background-color: #f4f6f9;
color: #334e68;
font-family: ‘Inter’, sans-serif;
}
/* Editor styling */
#editor {
min-height: 380px;
outline: none;
}
#editor:empty:before {
content: attr(data-placeholder);
color: #9fb3c8;
pointer-events: none;
display: block;
}
/* Custom highlight mark */
mark.search-highlight {
background-color: #fef08a;
color: #854d0e;
padding: 0 2px;
border-radius: 2px;
}
/* Custom Box styling inside editor */
.editor-box {
border: 2px dashed #cbd5e1;
padding: 12px;
border-radius: 8px;
background-color: #f8fafc;
margin: 8px 0;
}
.editor-comment {
background-color: #fef3c7;
border-left: 4px solid #f59e0b;
padding: 6px 12px;
margin: 6px 0;
font-size: 0.9em;
border-radius: 0 6px 6px 0;
}
/* Print media rules */
@media print {
body * {
visibility: hidden;
}
#print-area, #print-area * {
visibility: visible;
}
#print-area {
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 20px;
background: white;
color: black;
}
}
/* Hide scrollbars for clean toolbar */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
Big Note Book
Write • Save • Edit • Search • Organize
Saved ✓
📝 Personal
📚 Study
💼 Work
💰 Business
⭐ Important
Created: Just now
Backup & Restore Notes
Safely download all your personal notes into a single backup file or restore them from JSON.
Export All Notes
Download a complete JSON file containing all titles, content, categories, and star states.
Restore from Backup
Upload a JSON backup file to sync all notes back into your Firebase Realtime Database.
Select Backup File
Enter Password
This note is protected with a passcode.
.tb-btn {
padding: 6px 8px;
border-radius: 6px;
font-size: 13px;
transition: all 0.15s;
}
.tb-btn:hover {
background-color: #e2e8f0;
color: #0f172a;
}
/* —————————————————-
* FIREBASE REALTIME DATABASE CONFIGURATION & INIT
* —————————————————- */
const firebaseConfig = {
databaseURL: “https://bignotetulonbookshop-default-rtdb.firebaseio.com/”
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.database();
const notesRef = db.ref(‘notes’);
/* —————————————————-
* GLOBAL APP STATE
* —————————————————- */
let notesData = {}; // Object holding key-value pairs from Firebase
let currentNoteId = null; // ID of actively opened note
let activeTab = ‘all’; // ‘all’, ‘starred’, ‘categories’, ‘hidden’, ‘recycle’, ‘backup’
let activeCategoryFilter = null; // For categories tab filtering
let searchQuery = ”; // Active search term
let isSaving = false;
let autoSaveTimer = null;
let isOnline = true;
/* —————————————————-
* DOM ELEMENTS
* —————————————————- */
const notesList = document.getElementById(‘notes-list’);
const emptyState = document.getElementById(’empty-state’);
const searchInput = document.getElementById(‘search-input’);
const searchResultsCount = document.getElementById(‘search-results-count’);
const clearSearchBtn = document.getElementById(‘clear-search-btn’);
const editorWrapper = document.getElementById(‘editor-wrapper’);
const backupSection = document.getElementById(‘backup-section’);
const editor = document.getElementById(‘editor’);
const noteTitleInput = document.getElementById(‘note-title-input’);
const noteCategorySelect = document.getElementById(‘note-category-select’);
const starBtn = document.getElementById(‘star-btn’);
const statusBadge = document.getElementById(‘status-badge’);
const statusText = document.getElementById(‘status-text’);
const categoryFilterWrapper = document.getElementById(‘category-filter-wrapper’);
const categoryChips = document.getElementById(‘category-chips’);
const emptyBinBtn = document.getElementById(’empty-bin-btn’);
const wordCharCount = document.getElementById(‘word-char-count’);
const noteDates = document.getElementById(‘note-dates’);
const moreMenu = document.getElementById(‘more-menu’);
const passwordModal = document.getElementById(‘password-modal’);
const passwordInput = document.getElementById(‘password-modal-input’);
/* —————————————————-
* FIREBASE REAL-TIME LISTENERS
* —————————————————- */
// Listen for connection state changes
const connectedRef = db.ref(‘.info/connected’);
connectedRef.on(‘value’, (snap) => {
if (snap.val() === true) {
isOnline = true;
updateStatusBadge(‘Saved ✓’, ’emerald’);
} else {
isOnline = false;
updateStatusBadge(‘Offline’, ‘amber’);
}
});
// Sync notes from Firebase in real time
notesRef.on(‘value’, (snapshot) => {
notesData = snapshot.val() || {};
renderNotesList();
// If current note exists in data, update editor view if not currently typing
if (currentNoteId && notesData[currentNoteId]) {
const note = notesData[currentNoteId];
if (document.activeElement !== editor && document.activeElement !== noteTitleInput) {
noteTitleInput.value = note.title || ”;
editor.innerHTML = note.content || ”;
noteCategorySelect.value = note.category || ‘📝 Personal’;
updateStarButtonUI(note.starred);
updateStats();
}
} else if (!currentNoteId || !notesData[currentNoteId]) {
// Select first available note if active note is missing
selectFirstAvailableNote();
}
});
/* —————————————————-
* HELPER UTILITIES
* —————————————————- */
function showToast(message, type = ‘info’) {
const container = document.getElementById(‘toast-container’);
const toast = document.createElement(‘div’);
toast.className = `px-4 py-2.5 rounded-xl shadow-lg text-xs font-semibold text-white transition-all transform translate-y-2 opacity-0 flex items-center gap-2 bg-slate-800`;
toast.innerHTML = ` ${message}`;
container.appendChild(toast);
setTimeout(() => {
toast.classList.remove(‘translate-y-2’, ‘opacity-0’);
}, 10);
setTimeout(() => {
toast.classList.add(‘opacity-0’, ‘translate-y-2′);
setTimeout(() => toast.remove(), 200);
}, 2500);
}
function updateStatusBadge(text, color) {
statusText.innerText = text;
if (color === ’emerald’) {
statusBadge.className = “flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-700 shadow-sm transition-all”;
} else if (color === ‘indigo’) {
statusBadge.className = “flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-indigo-100 text-indigo-700 shadow-sm transition-all”;
} else if (color === ‘amber’) {
statusBadge.className = “flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold bg-amber-100 text-amber-700 shadow-sm transition-all”;
}
}
/* —————————————————-
* RENDERING NOTE LIST & SEARCH
* —————————————————- */
function renderNotesList() {
notesList.innerHTML = ”;
let filteredKeys = Object.keys(notesData);
// 1. Filter by Nav Tab
if (activeTab === ‘all’) {
filteredKeys = filteredKeys.filter(k => !notesData[k].deleted && !notesData[k].hidden);
} else if (activeTab === ‘starred’) {
filteredKeys = filteredKeys.filter(k => !notesData[k].deleted && !notesData[k].hidden && notesData[k].starred);
} else if (activeTab === ‘categories’) {
filteredKeys = filteredKeys.filter(k => !notesData[k].deleted && !notesData[k].hidden);
if (activeCategoryFilter) {
filteredKeys = filteredKeys.filter(k => notesData[k].category === activeCategoryFilter);
}
} else if (activeTab === ‘hidden’) {
filteredKeys = filteredKeys.filter(k => !notesData[k].deleted && notesData[k].hidden);
} else if (activeTab === ‘recycle’) {
filteredKeys = filteredKeys.filter(k => notesData[k].deleted);
}
// 2. Filter by Search Query
if (searchQuery.trim() !== ”) {
const query = searchQuery.toLowerCase();
filteredKeys = filteredKeys.filter(k => {
const note = notesData[k];
const titleMatch = (note.title || ”).toLowerCase().includes(query);
const contentMatch = (note.content || ”).toLowerCase().includes(query);
return titleMatch || contentMatch;
});
searchResultsCount.classList.remove(‘hidden’);
searchResultsCount.innerText = `${filteredKeys.length} result${filteredKeys.length === 1 ? ” : ‘s’} found`;
} else {
searchResultsCount.classList.add(‘hidden’);
}
// Show / Hide empty bin button
if (activeTab === ‘recycle’ && filteredKeys.length > 0) {
emptyBinBtn.classList.remove(‘hidden’);
} else {
emptyBinBtn.classList.add(‘hidden’);
}
// Handle empty UI state
if (filteredKeys.length === 0) {
emptyState.classList.remove(‘hidden’);
emptyState.classList.add(‘flex’);
} else {
emptyState.classList.add(‘hidden’);
emptyState.classList.remove(‘flex’);
}
// Render Notes List Items
filteredKeys.sort((a, b) => (notesData[b].updatedAt || 0) – (notesData[a].updatedAt || 0)).forEach(id => {
const note = notesData[id];
const card = document.createElement(‘div’);
const isActive = id === currentNoteId;
card.className = `p-3 rounded-xl border transition-all cursor-pointer relative group flex flex-col gap-1 ${
isActive ? ‘bg-indigo-50/70 border-indigo-200 shadow-sm’ : ‘bg-white border-slate-100 hover:border-slate-200 hover:bg-slate-50’
}`;
// Clean snippet text
const tempDiv = document.createElement(‘div’);
tempDiv.innerHTML = note.content || ”;
let textSnippet = tempDiv.textContent || tempDiv.innerText || ‘No additional text…’;
// Highlight search keywords if active
let titleHTML = escapeHTML(note.title || ‘Untitled Note’);
if (searchQuery.trim() !== ”) {
const regex = new RegExp(`(${escapeRegExp(searchQuery)})`, ‘gi’);
titleHTML = titleHTML.replace(regex, `$1`);
textSnippet = escapeHTML(textSnippet).replace(regex, `$1`);
} else {
textSnippet = escapeHTML(textSnippet);
}
const updatedDateStr = note.updatedAt ? new Date(note.updatedAt).toLocaleDateString(undefined, { month: ‘short’, day: ‘numeric’ }) : ”;
card.innerHTML = `
${note.starred ? ‘‘ : ”}
${textSnippet}
${updatedDateStr}
`;
card.addEventListener(‘click’, () => openNote(id));
notesList.appendChild(card);
});
}
function escapeHTML(str) {
return str.replace(/[&’”]/g,
tag => ({ ‘&’: ‘&’, ”: ‘>’, “‘”: ‘'’, ‘”‘: ‘"’ }[tag] || tag)
);
}
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, ‘\\$&’);
}
/* —————————————————-
* OPENING & CREATING NOTES
* —————————————————- */
function selectFirstAvailableNote() {
const availableKeys = Object.keys(notesData).filter(k => !notesData[k].deleted && !notesData[k].hidden);
if (availableKeys.length > 0) {
openNote(availableKeys[0]);
} else {
currentNoteId = null;
noteTitleInput.value = ”;
editor.innerHTML = ”;
}
}
function openNote(id) {
const note = notesData[id];
if (!note) return;
// Check if Password Locked
if (note.locked && note.password) {
promptPasswordModal(note.password, () => {
loadNoteIntoEditor(id);
});
} else {
loadNoteIntoEditor(id);
}
}
function loadNoteIntoEditor(id) {
currentNoteId = id;
const note = notesData[id];
noteTitleInput.value = note.title || ”;
editor.innerHTML = note.content || ”;
noteCategorySelect.value = note.category || ‘📝 Personal’;
updateStarButtonUI(note.starred);
// Apply note background color
if (note.color) {
editor.style.backgroundColor = note.color;
} else {
editor.style.backgroundColor = ‘#ffffff’;
}
// Update Header dates
const createdStr = note.createdAt ? new Date(note.createdAt).toLocaleDateString() : ‘Unknown’;
const updatedStr = note.updatedAt ? new Date(note.updatedAt).toLocaleTimeString([], { hour: ‘2-digit’, minute: ‘2-digit’ }) : ”;
noteDates.innerText = `Created: ${createdStr} • Updated: ${updatedStr}`;
// Update UI Menu Lock Text
document.getElementById(‘menu-lock-text’).innerText = note.locked ? ‘Unlock Note’ : ‘Lock Note’;
document.getElementById(‘menu-hide-text’).innerText = note.hidden ? ‘Unhide Note’ : ‘Hide Note’;
updateStats();
renderNotesList();
}
function createNewNote() {
const newId = notesRef.push().key;
const now = Date.now();
const newNote = {
id: newId,
title: ‘Untitled Note’,
content: ”,
category: ‘📝 Personal’,
createdAt: now,
updatedAt: now,
starred: false,
hidden: false,
locked: false,
deleted: false,
color: ‘#ffffff’
};
notesRef.child(newId).set(newNote, (err) => {
if (!err) {
showToast(‘Note Added’, ’emerald’);
openNote(newId);
}
});
}
/* —————————————————-
* AUTO SAVE LOGIC
* —————————————————- */
function triggerAutoSave() {
if (!currentNoteId) return;
updateStatusBadge(‘Saving…’, ‘indigo’);
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(() => {
const title = noteTitleInput.value.trim() || ‘Untitled Note’;
const content = editor.innerHTML;
const category = noteCategorySelect.value;
notesRef.child(currentNoteId).update({
title: title,
content: content,
category: category,
updatedAt: Date.now()
}, (err) => {
if (!err) {
updateStatusBadge(‘Saved ✓’, ’emerald’);
} else {
updateStatusBadge(‘Error’, ‘amber’);
}
});
}, 600); // 600ms debounce
}
noteTitleInput.addEventListener(‘input’, triggerAutoSave);
editor.addEventListener(‘input’, () => {
triggerAutoSave();
updateStats();
});
noteCategorySelect.addEventListener(‘change’, triggerAutoSave);
/* —————————————————-
* WYSIWYG EDITOR TOOLBAR ACTIONS
* —————————————————- */
document.querySelectorAll(‘.tb-btn[data-cmd]’).forEach(btn => {
btn.addEventListener(‘click’, () => {
const cmd = btn.getAttribute(‘data-cmd’);
document.execCommand(cmd, false, null);
triggerAutoSave();
});
});
// Text Size
document.getElementById(‘text-size-select’).addEventListener(‘change’, (e) => {
document.execCommand(‘fontSize’, false, e.target.value);
triggerAutoSave();
});
// Text Color
document.getElementById(‘text-color-picker’).addEventListener(‘input’, (e) => {
document.execCommand(‘foreColor’, false, e.target.value);
triggerAutoSave();
});
// Highlight Color
document.getElementById(‘bg-color-picker’).addEventListener(‘input’, (e) => {
document.execCommand(‘hiliteColor’, false, e.target.value);
triggerAutoSave();
});
// Note Background Color
document.querySelectorAll(‘.note-bg-btn’).forEach(btn => {
btn.addEventListener(‘click’, () => {
const color = btn.getAttribute(‘data-color’);
editor.style.backgroundColor = color;
if (currentNoteId) {
notesRef.child(currentNoteId).update({ color: color });
}
});
});
// Double Underline
document.getElementById(‘double-underline-btn’).addEventListener(‘click’, () => {
const span = document.createElement(‘span’);
span.style.textDecoration = ‘underline double’;
const sel = window.getSelection();
if (sel.rangeCount) {
const range = sel.getRangeAt(0);
range.surroundContents(span);
}
triggerAutoSave();
});
// Add Rounded Box
document.getElementById(‘add-box-btn’).addEventListener(‘click’, () => {
const box = document.createElement(‘div’);
box.className = ‘editor-box’;
box.innerHTML = ‘Type inside this box…’;
editor.appendChild(box);
triggerAutoSave();
});
// Add Comment
document.getElementById(‘add-comment-btn’).addEventListener(‘click’, () => {
const comment = document.createElement(‘div’);
comment.className = ‘editor-comment’;
comment.innerHTML = ‘💡 Note: Add your comment here…’;
editor.appendChild(comment);
triggerAutoSave();
});
// Copy Text
document.getElementById(‘copy-text-btn’).addEventListener(‘click’, () => {
const text = editor.innerText;
navigator.clipboard.writeText(text).then(() => {
showToast(‘Copied to clipboard!’);
});
});
// Print
document.getElementById(‘print-note-btn’).addEventListener(‘click’, printCurrentNote);
function printCurrentNote() {
const printArea = document.getElementById(‘print-area’);
const title = noteTitleInput.value || ‘Untitled Note’;
printArea.innerHTML = `
${title}
`;
window.print();
}
// Downloads (.txt, .html)
document.getElementById(‘download-txt’).addEventListener(‘click’, () => {
const title = noteTitleInput.value || ‘Note’;
downloadFile(`${title}.txt`, editor.innerText, ‘text/plain’);
});
document.getElementById(‘download-html’).addEventListener(‘click’, () => {
const title = noteTitleInput.value || ‘Note’;
const htmlContent = `${title}
${title}
${editor.innerHTML}`;
downloadFile(`${title}.html`, htmlContent, ‘text/html’);
});
function downloadFile(filename, text, type) {
const blob = new Blob([text], { type: type });
const link = document.createElement(‘a’);
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
}
/* —————————————————-
* MORE MENU ACTIONS & STAR
* —————————————————- */
starBtn.addEventListener(‘click’, () => {
if (!currentNoteId) return;
const currentStarState = notesData[currentNoteId]?.starred || false;
notesRef.child(currentNoteId).update({ starred: !currentStarState });
updateStarButtonUI(!currentStarState);
});
function updateStarButtonUI(isStarred) {
if (isStarred) {
starBtn.innerHTML = ‘‘;
} else {
starBtn.innerHTML = ‘‘;
}
}
document.getElementById(‘more-menu-btn’).addEventListener(‘click’, (e) => {
e.stopPropagation();
moreMenu.classList.toggle(‘hidden’);
});
document.addEventListener(‘click’, () => moreMenu.classList.add(‘hidden’));
// Menu: Duplicate
document.getElementById(‘menu-duplicate’).addEventListener(‘click’, () => {
if (!currentNoteId) return;
const original = notesData[currentNoteId];
const newId = notesRef.push().key;
const now = Date.now();
const duplicate = {
…original,
id: newId,
title: `${original.title || ‘Note’} (Copy)`,
createdAt: now,
updatedAt: now
};
notesRef.child(newId).set(duplicate, () => {
showToast(‘Note Duplicated’);
openNote(newId);
});
});
// Menu: Hide / Unhide
document.getElementById(‘menu-hide’).addEventListener(‘click’, () => {
if (!currentNoteId) return;
const isHidden = notesData[currentNoteId]?.hidden || false;
notesRef.child(currentNoteId).update({ hidden: !isHidden }, () => {
showToast(isHidden ? ‘Note Unhidden’ : ‘Note Hidden’);
selectFirstAvailableNote();
});
});
// Menu: Delete (Move to Recycle Bin)
document.getElementById(‘menu-delete’).addEventListener(‘click’, () => {
if (!currentNoteId) return;
if (confirm(‘Are you sure you want to move this note to ♻️ Recycle Bin?’)) {
notesRef.child(currentNoteId).update({ deleted: true }, () => {
showToast(‘Moved to Recycle Bin’);
selectFirstAvailableNote();
});
}
});
// Menu: Lock / Unlock Password
document.getElementById(‘menu-lock’).addEventListener(‘click’, () => {
if (!currentNoteId) return;
const note = notesData[currentNoteId];
if (note.locked) {
// Unlock
notesRef.child(currentNoteId).update({ locked: false, password: null }, () => {
showToast(‘Note Unlocked’);
document.getElementById(‘menu-lock-text’).innerText = ‘Lock Note’;
});
} else {
// Lock prompt
const pass = prompt(‘Set a passcode for this note:’);
if (pass && pass.trim() !== ”) {
notesRef.child(currentNoteId).update({ locked: true, password: pass.trim() }, () => {
showToast(‘Note Locked 🔒’);
document.getElementById(‘menu-lock-text’).innerText = ‘Unlock Note’;
});
}
}
});
/* —————————————————-
* TOP NAVIGATION TABS
* —————————————————- */
document.querySelectorAll(‘.nav-tab’).forEach(tabBtn => {
tabBtn.addEventListener(‘click’, () => {
document.querySelectorAll(‘.nav-tab’).forEach(b => {
b.classList.remove(‘bg-indigo-50’, ‘text-indigo-600’, ‘border-indigo-600’);
b.classList.add(‘text-slate-600’);
});
tabBtn.classList.add(‘bg-indigo-50’, ‘text-indigo-600’, ‘border-indigo-600’);
tabBtn.classList.remove(‘text-slate-600’);
activeTab = tabBtn.getAttribute(‘data-tab’);
// Toggle visibility for Backup section vs Editor
if (activeTab === ‘backup’) {
editorWrapper.classList.add(‘hidden’);
backupSection.classList.remove(‘hidden’);
backupSection.classList.add(‘flex’);
} else {
editorWrapper.classList.remove(‘hidden’);
backupSection.classList.add(‘hidden’);
backupSection.classList.remove(‘flex’);
}
// Toggle category chips bar
if (activeTab === ‘categories’) {
categoryFilterWrapper.classList.remove(‘hidden’);
renderCategoryChips();
} else {
categoryFilterWrapper.classList.add(‘hidden’);
}
renderNotesList();
});
});
function renderCategoryChips() {
const categories = [‘📝 Personal’, ‘📚 Study’, ‘💼 Work’, ‘💰 Business’, ‘⭐ Important’];
categoryChips.innerHTML = ”;
categories.forEach(cat => {
const chip = document.createElement(‘button’);
const isSelected = activeCategoryFilter === cat;
chip.className = `px-2.5 py-1 rounded-lg text-xs font-medium transition-all ${
isSelected ? ‘bg-indigo-600 text-white’ : ‘bg-slate-100 text-slate-600 hover:bg-slate-200’
}`;
chip.innerText = cat;
chip.addEventListener(‘click’, () => {
activeCategoryFilter = isSelected ? null : cat;
renderCategoryChips();
renderNotesList();
});
categoryChips.appendChild(chip);
});
}
// Empty Bin Action
emptyBinBtn.addEventListener(‘click’, () => {
if (confirm(‘This action will permanently delete all notes in the Recycle Bin. Continue?’)) {
Object.keys(notesData).forEach(k => {
if (notesData[k].deleted) {
notesRef.child(k).remove();
}
});
showToast(‘Recycle Bin Emptied’);
}
});
/* —————————————————-
* SEARCH INPUT HANDLING
* —————————————————- */
searchInput.addEventListener(‘input’, (e) => {
searchQuery = e.target.value;
if (searchQuery.trim() !== ”) {
clearSearchBtn.classList.remove(‘hidden’);
} else {
clearSearchBtn.classList.add(‘hidden’);
}
renderNotesList();
});
clearSearchBtn.addEventListener(‘click’, () => {
searchInput.value = ”;
searchQuery = ”;
clearSearchBtn.classList.add(‘hidden’);
renderNotesList();
});
/* —————————————————-
* EXPORT / IMPORT BACKUP
* —————————————————- */
document.getElementById(‘export-json-btn’).addEventListener(‘click’, () => {
const dataStr = “data:text/json;charset=utf-8,” + encodeURIComponent(JSON.stringify(notesData, null, 2));
const downloadAnchor = document.createElement(‘a’);
downloadAnchor.setAttribute(“href”, dataStr);
downloadAnchor.setAttribute(“download”, `bignotebook_backup_${Date.now()}.json`);
document.body.appendChild(downloadAnchor);
downloadAnchor.click();
downloadAnchor.remove();
showToast(‘Backup Created ✓’);
});
document.getElementById(‘import-json-input’).addEventListener(‘change’, (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const importedNotes = JSON.parse(event.target.result);
if (typeof importedNotes === ‘object’) {
notesRef.update(importedNotes, (err) => {
if (!err) {
showToast(‘Restored ✓’);
}
});
}
} catch (err) {
alert(‘Invalid JSON backup file.’);
}
};
reader.readAsText(file);
});
/* —————————————————-
* PASSWORD MODAL PROMPT
* —————————————————- */
function promptPasswordModal(correctPass, onSuccess) {
passwordModal.classList.remove(‘hidden’);
passwordInput.value = ”;
passwordInput.focus();
const submitHandler = () => {
if (passwordInput.value === correctPass) {
passwordModal.classList.add(‘hidden’);
onSuccess();
cleanup();
} else {
alert(‘Incorrect password!’);
}
};
const cancelHandler = () => {
passwordModal.classList.add(‘hidden’);
cleanup();
};
const cleanup = () => {
document.getElementById(‘password-submit-btn’).removeEventListener(‘click’, submitHandler);
document.getElementById(‘password-cancel-btn’).removeEventListener(‘click’, cancelHandler);
};
document.getElementById(‘password-submit-btn’).addEventListener(‘click’, submitHandler);
document.getElementById(‘password-cancel-btn’).addEventListener(‘click’, cancelHandler);
}
/* —————————————————-
* KEYBOARD SHORTCUTS & STATS
* —————————————————- */
document.addEventListener(‘keydown’, (e) => {
if (e.ctrlKey || e.metaKey) {
switch (e.key.toLowerCase()) {
case ‘s’:
e.preventDefault();
triggerAutoSave();
showToast(‘Saved ✓’);
break;
case ‘f’:
e.preventDefault();
searchInput.focus();
break;
case ‘p’:
e.preventDefault();
printCurrentNote();
break;
}
}
});
function updateStats() {
const text = editor.innerText || ”;
const charCount = text.length;
const wordCount = text.trim() === ” ? 0 : text.trim().split(/\s+/).length;
wordCharCount.innerText = `${wordCount} words • ${charCount} characters`;
}
// Add Note Button Listeners
document.getElementById(‘add-note-btn’).addEventListener(‘click’, createNewNote);
document.getElementById(’empty-state-action’).addEventListener(‘click’, createNewNote);
// Mobile drawer toggle
document.getElementById(‘mobile-menu-btn’).addEventListener(‘click’, () => {
const sidebar = document.getElementById(‘sidebar-container’);
sidebar.classList.toggle(‘hidden’);
});