the best notes app for me
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})();