the best notes app for me
at main 209 lines 6.5 kB view raw
1// enɳoté Web App 2// Desktop note preparation for iOS app 3 4(function() { 5 'use strict'; 6 7 // DOM Elements 8 const notesInput = document.getElementById('notes-input'); 9 const noteCount = document.getElementById('count'); 10 const createStackBtn = document.getElementById('create-stack'); 11 const inputSection = document.getElementById('input-section'); 12 const qrSection = document.getElementById('qr-section'); 13 const qrCanvas = document.getElementById('qr-canvas'); 14 const countdown = document.getElementById('countdown'); 15 const timerProgress = document.getElementById('timer-progress'); 16 const newStackBtn = document.getElementById('new-stack'); 17 18 // Constants 19 const EXPIRY_MINUTES = 5; 20 const STACK_ID_LENGTH = 12; 21 22 // State 23 let countdownInterval = null; 24 let expiryTime = null; 25 26 // Generate random alphanumeric ID 27 function generateStackId() { 28 const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 29 let result = ''; 30 for (let i = 0; i < STACK_ID_LENGTH; i++) { 31 result += chars.charAt(Math.floor(Math.random() * chars.length)); 32 } 33 return result; 34 } 35 36 // Parse notes from textarea 37 function parseNotes() { 38 const text = notesInput.value.trim(); 39 if (!text) return []; 40 41 return text 42 .split('\n') 43 .map(line => line.trim()) 44 .filter(line => line.length > 0); 45 } 46 47 // Update note count display 48 function updateNoteCount() { 49 const notes = parseNotes(); 50 noteCount.textContent = notes.length; 51 createStackBtn.disabled = notes.length === 0; 52 } 53 54 // Format time as M:SS 55 function formatTime(seconds) { 56 const mins = Math.floor(seconds / 60); 57 const secs = seconds % 60; 58 return `${mins}:${secs.toString().padStart(2, '0')}`; 59 } 60 61 // Update countdown timer 62 function updateCountdown() { 63 const now = Date.now(); 64 const remaining = Math.max(0, Math.ceil((expiryTime - now) / 1000)); 65 const totalSeconds = EXPIRY_MINUTES * 60; 66 const progress = (remaining / totalSeconds) * 100; 67 68 countdown.textContent = formatTime(remaining); 69 timerProgress.style.width = `${progress}%`; 70 71 // Update color based on time remaining 72 timerProgress.classList.remove('warning', 'critical'); 73 if (remaining <= 60) { 74 timerProgress.classList.add('critical'); 75 } else if (remaining <= 120) { 76 timerProgress.classList.add('warning'); 77 } 78 79 // Expired 80 if (remaining <= 0) { 81 clearInterval(countdownInterval); 82 resetToInput(); 83 } 84 } 85 86 // Create stack and show QR 87 async function createStack() { 88 const notes = parseNotes(); 89 if (notes.length === 0) return; 90 91 const stackId = generateStackId(); 92 const now = new Date(); 93 const expiresAt = new Date(now.getTime() + EXPIRY_MINUTES * 60 * 1000); 94 95 // Stack object (for CloudKit integration) 96 const stack = { 97 id: stackId, 98 notes: notes, 99 createdAt: now.toISOString(), 100 expiresAt: expiresAt.toISOString(), 101 fetched: false 102 }; 103 104 // TODO: CloudKit Integration 105 // When CloudKit is configured, uncomment and implement: 106 // await saveToCloudKit(stack); 107 108 // For now, log the stack data 109 console.log('Stack created:', stack); 110 111 // Generate QR code with deep link 112 const deepLink = `ennote://stack/${stackId}`; 113 114 try { 115 await QRCode.toCanvas(qrCanvas, deepLink, { 116 width: 200, 117 margin: 0, 118 color: { 119 dark: '#171717', 120 light: '#FFFFFF' 121 }, 122 errorCorrectionLevel: 'M' 123 }); 124 125 // Show QR section 126 inputSection.classList.add('hidden'); 127 qrSection.classList.remove('hidden'); 128 129 // Start countdown 130 expiryTime = expiresAt.getTime(); 131 updateCountdown(); 132 countdownInterval = setInterval(updateCountdown, 1000); 133 134 } catch (err) { 135 console.error('QR generation failed:', err); 136 alert('Failed to generate QR code. Please try again.'); 137 } 138 } 139 140 // Reset to input view 141 function resetToInput() { 142 if (countdownInterval) { 143 clearInterval(countdownInterval); 144 countdownInterval = null; 145 } 146 147 qrSection.classList.add('hidden'); 148 inputSection.classList.remove('hidden'); 149 timerProgress.style.width = '100%'; 150 timerProgress.classList.remove('warning', 'critical'); 151 } 152 153 // CloudKit Integration (placeholder) 154 // To implement: 155 // 1. Include CloudKit JS: <script src="https://cdn.apple-cloudkit.com/ck/2/cloudkit.js"></script> 156 // 2. Configure with your container ID 157 // 3. Implement saveToCloudKit function 158 159 /* 160 async function initCloudKit() { 161 CloudKit.configure({ 162 containers: [{ 163 containerIdentifier: 'iCloud.com.yourname.ennote', 164 apiTokenAuth: { 165 apiToken: 'YOUR_API_TOKEN', 166 persist: false 167 }, 168 environment: 'production' 169 }] 170 }); 171 } 172 173 async function saveToCloudKit(stack) { 174 const container = CloudKit.getDefaultContainer(); 175 const publicDB = container.publicCloudDatabase; 176 177 const record = { 178 recordType: 'Stack', 179 recordName: stack.id, 180 fields: { 181 notes: { value: stack.notes }, 182 expiresAt: { value: stack.expiresAt }, 183 fetched: { value: 0 } 184 } 185 }; 186 187 await publicDB.saveRecords([record]); 188 } 189 */ 190 191 // Event Listeners 192 notesInput.addEventListener('input', updateNoteCount); 193 createStackBtn.addEventListener('click', createStack); 194 newStackBtn.addEventListener('click', resetToInput); 195 196 // Keyboard shortcut: Cmd/Ctrl + Enter to create stack 197 notesInput.addEventListener('keydown', (e) => { 198 if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { 199 e.preventDefault(); 200 if (!createStackBtn.disabled) { 201 createStack(); 202 } 203 } 204 }); 205 206 // Initialize 207 updateNoteCount(); 208 209})();