Clipboard Save

Save, organize, and quickly access your frequently used text snippets

Add New Snippet

Filter by Tag

All

My Snippets

No snippets found. Add your first snippet using the form!

})(); // Function to initialize the app after Firebase is ready function initializeApp() { // DOM Elements const snippetForm = document.getElementById('snippetForm'); const snippetsContainer = document.getElementById('snippetsContainer'); const searchInput = document.getElementById('searchInput'); const tagFilter = document.getElementById('tagFilter'); const snippetsCount = document.getElementById('snippetsCount'); const toast = document.getElementById('toast'); const toastMessage = document.getElementById('toastMessage'); let toastBootstrap = null; // Initialize Bootstrap toast if available if (toast && typeof bootstrap !== 'undefined') { toastBootstrap = new bootstrap.Toast(toast); } // State let currentUser = null; let currentFilter = 'all'; let snippets = []; let toastTimer = null; try { if (!window.db || !window.auth) { console.error('Firebase not properly initialized'); showToast('Error initializing application. Please refresh the page.', 'error'); return; } console.log("Initializing Clipboard Save app..."); // Initialize Firebase if available if (typeof firebase !== 'undefined') { // Set up auth state listener firebase.auth().onAuthStateChanged((user) => { currentUser = user; if (!user) { // Sign in anonymously if no user is signed in firebase.auth().signInAnonymously().catch(error => { console.error('Error signing in anonymously:', error); showToast('Error initializing application', 'error'); }); } else { // User is signed in, load snippets loadSnippets(); } }); } // Show toast notification function showToast(message, type = 'info') { if (!toast || !toastMessage) return; // Set message and type toastMessage.textContent = message; // Set background color based on type const bgClass = { 'success': 'bg-success', 'error': 'bg-danger', 'warning': 'bg-warning', 'info': 'bg-primary' }[type] || 'bg-primary'; // Update toast classes toast.className = `toast align-items-center text-white ${bgClass} border-0`; // Show the toast if (toastBootstrap) { toastBootstrap.show(); } // Auto-hide after 3 seconds clearTimeout(toastTimer); toastTimer = setTimeout(() => { if (toastBootstrap) { toastBootstrap.hide(); } }, 3000); } // Load snippets from Firestore function loadSnippets() { if (!snippetsContainer) return; snippetsContainer.innerHTML = '
Loading...
'; if (!window.db) { console.error('Firestore not initialized'); snippetsContainer.innerHTML = '
Error: Database not available. Please refresh the page.
'; return; } // Get snippets from Firestore window.db.collection('snippets') .orderBy('createdAt', 'desc') .get() .then((querySnapshot) => { snippets = []; const allTags = new Set(); querySnapshot.forEach((doc) => { const data = doc.data(); snippets.push({ id: doc.id, ...data, // Convert Firestore timestamps to Date objects createdAt: data.createdAt?.toDate() || new Date(), updatedAt: data.updatedAt?.toDate() || new Date() }); // Add tags to the set if (data.tags && Array.isArray(data.tags)) { data.tags.forEach(tag => allTags.add(tag)); } }); // Update the UI renderSnippets(snippets); updateTagFilter(Array.from(allTags)); updateSnippetsCount(snippets.length); // Show/hide no snippets message if (snippets.length === 0) { snippetsContainer.innerHTML = `

No snippets found. Add your first snippet to get started!

`; } }) .catch((error) => { console.error('Error loading snippets:', error); snippetsContainer.innerHTML = `

Error loading snippets. Please try again later.

`; }); } // Initialize auth state listener if (window.auth) { window.auth.onAuthStateChanged(user => { currentUser = user; if (user) { console.log('User is signed in:', user.uid); loadSnippets(); } else { // For demo purposes, we'll use anonymous auth // In production, you'd redirect to login window.auth.signInAnonymously() .then(() => { showToast('Welcome! Start saving your snippets.', 'info'); }) .catch(error => { console.error('Authentication error:', error); showToast('Error initializing app. Please refresh the page.', 'error'); }); } }); } else { console.error('Firebase Auth not initialized'); showToast('Error: Authentication service not available', 'error'); } // Initialize form if (snippetForm) { // Reset form snippetForm.reset(); // Set up form submission snippetForm.addEventListener('submit', async (e) => { e.preventDefault(); if (!currentUser) { showToast('Please sign in to save snippets', 'error'); return; } const title = document.getElementById('snippetTitle')?.value.trim(); const content = document.getElementById('snippetContent')?.value.trim(); const snippetId = document.getElementById('snippetId')?.value; if (!title || !content) { showToast('Please fill in all required fields', 'error'); return; } try { const snippetData = { title, content, tags: currentTags, updatedAt: firebase.firestore.FieldValue.serverTimestamp() }; if (snippetId) { // Update existing snippet await window.db.collection('snippets').doc(snippetId).update(snippetData); showToast('Snippet updated successfully!', 'success'); } else { // Add new snippet snippetData.createdAt = firebase.firestore.FieldValue.serverTimestamp(); snippetData.userId = currentUser.uid; await window.db.collection('snippets').add(snippetData); showToast('Snippet saved successfully!', 'success'); } // Reset form resetForm(); } catch (error) { console.error('Error saving snippet:', error); showToast('Error saving snippet. Please try again.', 'error'); } }); } // Reset form function function resetForm() { if (snippetForm) { snippetForm.reset(); } const snippetIdInput = document.getElementById('snippetId'); if (snippetIdInput) { snippetIdInput.value = ''; } currentTags = []; updateTagsUI(); } // Update tags UI function updateTagsUI() { const tagsContainer = document.getElementById('tagsContainer'); if (tagsContainer) { tagsContainer.innerHTML = currentTags.map(tag => ` ${tag} ` ).join(''); // Add event listeners to remove buttons document.querySelectorAll('[data-tag]').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const tagToRemove = e.currentTarget.getAttribute('data-tag'); currentTags = currentTags.filter(tag => tag !== tagToRemove); updateTagsUI(); }); }); } } // Add tag function function addTag() { const tagInput = document.getElementById('tagInput'); if (!tagInput) return; const tag = tagInput.value.trim(); if (tag && !currentTags.includes(tag)) { currentTags.push(tag); updateTagsUI(); tagInput.value = ''; } tagInput.focus(); } // Initialize add tag button const addTagBtn = document.getElementById('addTagBtn'); if (addTagBtn) { addTagBtn.addEventListener('click', addTag); } // Allow adding tag with Enter key const tagInput = document.getElementById('tagInput'); if (tagInput) { tagInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); addTag(); } }); } // Initial load of snippets loadSnippets(); // Initialize tooltips const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); // Initialize Clipboard.js for copy buttons new ClipboardJS('.copy-btn', { text: function(trigger) { return trigger.getAttribute('data-clipboard-text'); } }).on('success', function(e) { // Show feedback const feedback = document.createElement('div'); feedback.className = 'copy-feedback'; feedback.textContent = 'Copied!'; e.trigger.appendChild(feedback); // Remove feedback after 2 seconds setTimeout(() => { feedback.remove(); }, 2000); e.clearSelection(); }); // Handle tag filter clicks const tagFilterElement = document.getElementById('tagFilter'); if (tagFilterElement) { tagFilterElement.addEventListener('click', (e) => { if (e.target.classList.contains('tag-badge')) { const tag = e.target.textContent; const searchInputElement = document.getElementById('searchInput'); if (searchInputElement) { searchInputElement.value = `#${tag}`; filterSnippets(); } } }); } // Handle search input with debounce const searchInputElement = document.getElementById('searchInput'); if (searchInputElement) { const debouncedFilter = debounce(() => { filterSnippets(); }, 300); searchInputElement.addEventListener('input', debouncedFilter); } // Initialize any other event listeners here console.log('Clipboard Save tool initialized successfully!'); } catch (error) { console.error('Error initializing Clipboard Save tool:', error); const errorContainer = document.createElement('div'); errorContainer.className = 'alert alert-danger'; errorContainer.innerHTML = `
Error Initializing Clipboard Save

${error.message || 'An unknown error occurred'}

`; const container = document.querySelector('.container'); if (container) { container.prepend(errorContainer); } } }); }; // Initialize the app when DOM is loaded document.addEventListener('DOMContentLoaded', () => { try { // Initialize Firebase and app initializeApp(); // Initialize tooltips const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); // Initialize Clipboard.js for copy buttons new ClipboardJS('.copy-btn', { text: function(trigger) { return trigger.getAttribute('data-clipboard-text'); } }).on('success', function(e) { // Show feedback const feedback = document.createElement('div'); feedback.className = 'copy-feedback'; feedback.textContent = 'Copied!'; e.trigger.appendChild(feedback); // Remove feedback after 2 seconds setTimeout(() => { feedback.remove(); }, 2000); e.clearSelection(); }); // Handle tag filter clicks const tagFilter = document.getElementById('tagFilter'); if (tagFilter) { tagFilter.addEventListener('click', (e) => { if (e.target.classList.contains('tag-badge')) { const tag = e.target.textContent; const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.value = `#${tag}`; filterSnippets(); } } }); } // Handle search input with debounce const searchInput = document.getElementById('searchInput'); if (searchInput) { const debouncedFilter = debounce(() => { filterSnippets(); }, 300); searchInput.addEventListener('input', debouncedFilter); } console.log('Clipboard Save tool initialized successfully!'); } catch (error) { console.error('Error initializing Clipboard Save tool:', error); const errorContainer = document.createElement('div'); errorContainer.className = 'alert alert-danger'; errorContainer.innerHTML = `
Error Initializing Clipboard Save

${error.message || 'An unknown error occurred'}

`; const container = document.querySelector('.container'); if (container) { container.prepend(errorContainer); } } }); // Load snippets from Firestore async function loadSnippets() { if (!currentUser) return; try { const querySnapshot = await db.collection('snippets') .where('userId', '==', currentUser.uid) .orderBy('updatedAt', 'desc') .get(); snippets = []; const tags = new Set(); querySnapshot.forEach(doc => { const snippet = { id: doc.id, ...doc.data() }; snippets.push(snippet); // Add tag to set if it exists if (snippet.tag) { tags.add(snippet.tag); } }); // Update tag filter updateTagFilter(Array.from(tags)); // Render snippets filterSnippets(); } catch (error) { console.error('Error loading snippets:', error); showToast('Error loading snippets. Please refresh the page.', 'error'); } } // Update snippets count function updateSnippetsCount(count) { snippetsCount.textContent = `${count} ${count === 1 ? 'snippet' : 'snippets'}`; } // Update tag filter UI function updateTagFilter(tags) { // Clear existing tags except 'All' tagFilter.innerHTML = '
All
'; // Add tags to filter tags.forEach(tag => { if (tag) { // Skip empty tags const tagElement = document.createElement('div'); tagElement.className = 'tag-item'; tagElement.textContent = tag; tagElement.dataset.tag = tag; tagElement.addEventListener('click', () => { // Update active state document.querySelectorAll('.tag-item').forEach(item => { item.classList.remove('active'); }); tagElement.classList.add('active'); // Update filter currentFilter = tagElement.dataset.tag; filterSnippets(); }); tagFilter.appendChild(tagElement); } }); // Update datalist for tag suggestions const datalist = document.getElementById('tagList'); datalist.innerHTML = ''; tags.forEach(tag => { if (tag) { // Skip empty tags const option = document.createElement('option'); option.value = tag; datalist.appendChild(option); } }); } // Render snippets to the DOM function renderSnippets(snippets) { const container = document.getElementById('snippetsContainer'); if (snippets.length === 0) { container.innerHTML = `

No snippets found. Try adjusting your search or add a new snippet.

`; return; } container.innerHTML = snippets.map(snippet => `
${escapeHtml(snippet.title || 'Untitled')}
${escapeHtml(snippet.tag || 'untagged')}

Last updated: ${formatDate(snippet.updatedAt?.toDate() || new Date())}

${formatContent(escapeHtml(snippet.content))}
`).join(''); // Initialize tooltips const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); // Initialize clipboard.js for copy buttons new ClipboardJS('.copy-btn', { text: function(trigger) { return trigger.getAttribute('data-clipboard-text'); } }).on('success', function(e) { // Show feedback const feedback = document.createElement('div'); feedback.className = 'copy-feedback'; feedback.textContent = 'Copied!'; e.trigger.appendChild(feedback); // Remove feedback after animation setTimeout(() => { feedback.remove(); }, 2000); e.clearSelection(); }); // Add event listeners to all action buttons document.querySelectorAll('.edit-snippet').forEach(button => { button.addEventListener('click', async (e) => { const snippetId = e.currentTarget.dataset.id; try { const doc = await db.collection('snippets').doc(snippetId).get(); if (doc.exists) { const snippet = { id: doc.id, ...doc.data() }; document.getElementById('snippetId').value = snippet.id; document.getElementById('snippetTitle').value = snippet.title || ''; document.getElementById('snippetTag').value = snippet.tag || ''; document.getElementById('snippetContent').value = snippet.content || ''; document.getElementById('saveBtn').innerHTML = ' Update'; // Scroll to form document.getElementById('snippetForm').scrollIntoView({ behavior: 'smooth' }); } } catch (error) { console.error('Error loading snippet for edit:', error); showToast('Error loading snippet. Please try again.', 'error'); } }); }); // Handle delete buttons document.querySelectorAll('.delete-snippet').forEach(button => { button.addEventListener('click', async (e) => { const snippetId = e.currentTarget.dataset.id; if (confirm('Are you sure you want to delete this snippet? This action cannot be undone.')) { try { await db.collection('snippets').doc(snippetId).delete(); showToast('Snippet deleted successfully!', 'success'); loadSnippets(); } catch (error) { console.error('Error deleting snippet:', error); showToast('Error deleting snippet. Please try again.', 'error'); } } }); }); } // Format date function formatDate(date) { return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }).format(date); } // Format content with line breaks function formatContent(content) { return content.replace(/\n/g, '
'); } // Escape HTML to prevent XSS function escapeHtml(unsafe) { if (!unsafe) return ''; return unsafe .toString() .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // Show toast notification function showToast(message, type = 'info') { // Clear any existing timer if (toastTimer) { clearTimeout(toastTimer); } // Update toast content and style toastMessage.textContent = message; // Set background color based on type const toastElement = document.getElementById('toast'); toastElement.className = 'toast align-items-center text-white border-0'; if (type === 'success') { toastElement.classList.add('bg-success'); } else if (type === 'error') { toastElement.classList.add('bg-danger'); } else { toastElement.classList.add('bg-info'); } // Show the toast toastBootstrap.show(); // Auto-hide after 3 seconds toastTimer = setTimeout(() => { toastBootstrap.hide(); }, 3000); } // Handle form submission const snippetForm = document.getElementById('snippetForm'); if (snippetForm) { snippetForm.addEventListener('submit', async (e) => { e.preventDefault(); const title = document.getElementById('snippetTitle').value.trim(); const tag = document.getElementById('snippetTag').value.trim().toLowerCase(); const content = document.getElementById('snippetContent').value.trim(); const snippetId = document.getElementById('snippetId').value; if (!title || !content) { showToast('Please fill in all required fields', 'error'); return; } try { const snippetData = { title, tag: tag || 'untagged', content, updatedAt: firebase.firestore.FieldValue.serverTimestamp() }; if (snippetId) { // Update existing snippet await db.collection('snippets').doc(snippetId).update(snippetData); showToast('Snippet updated successfully!', 'success'); } else { // Add new snippet snippetData.userId = currentUser.uid; snippetData.createdAt = firebase.firestore.FieldValue.serverTimestamp(); await db.collection('snippets').add(snippetData); showToast('Snippet saved successfully!', 'success'); } // Reset form snippetForm.reset(); document.getElementById('snippetId').value = ''; document.getElementById('saveBtn').innerHTML = ' Save Snippet'; } catch (error) { console.error('Error saving snippet:', error); showToast('Error saving snippet. Please try again.', 'error'); } }); } // Handle clear form button const clearBtn = document.getElementById('clearBtn'); if (clearBtn) { clearBtn.addEventListener('click', () => { if (confirm('Clear the form? Any unsaved changes will be lost.')) { document.getElementById('snippetForm').reset(); document.getElementById('snippetId').value = ''; document.getElementById('saveBtn').innerHTML = ' Save Snippet'; } }); } // Handle search input if (searchInput) { searchInput.addEventListener('input', debounce(() => { filterSnippets(); }, 300)); } // Handle tag filter clicks if (tagFilter) { tagFilter.addEventListener('click', (e) => { const tagElement = e.target.closest('[data-tag]'); if (tagElement) { // Update active state document.querySelectorAll('#tagFilter .badge').forEach(badge => { badge.classList.remove('bg-primary', 'active'); badge.classList.add('bg-secondary'); }); tagElement.classList.remove('bg-secondary'); tagElement.classList.add('bg-primary', 'active'); // Update filter and re-render currentFilter = tagElement.dataset.tag; filterSnippets(); } }); } // Load snippets if user is logged in if (currentUser) { loadSnippets(); } }); // Debounce function for search input function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }