notes.js (8117B)
1 // notes.js - Notes editor JavaScript 2 3 const editor = document.getElementById('editor'); 4 const preview = document.getElementById('preview'); 5 const searchInput = document.getElementById('search-input'); 6 7 let originalContent = editor.value; 8 let currentContent = editor.value; 9 10 // Initialize preview with clickable links 11 document.addEventListener('DOMContentLoaded', () => { 12 updatePreview(currentContent); 13 }); 14 15 // Auto-update preview on typing (debounced) 16 let typingTimer; 17 editor.addEventListener('input', () => { 18 clearTimeout(typingTimer); 19 typingTimer = setTimeout(() => { 20 currentContent = editor.value; 21 updatePreview(currentContent); 22 }, 500); 23 }); 24 25 // Search functionality 26 searchInput.addEventListener('input', () => { 27 const searchTerm = searchInput.value.toLowerCase(); 28 if (searchTerm === '') { 29 updatePreview(currentContent); 30 return; 31 } 32 33 const lines = currentContent.split('\n'); 34 const filtered = lines.filter(line => 35 line.toLowerCase().includes(searchTerm) 36 ); 37 updatePreviewWithLinks(filtered.join('\n')); 38 }); 39 40 // Convert URLs to clickable links and update preview 41 function updatePreview(content) { 42 updatePreviewWithLinks(content); 43 } 44 45 function updatePreviewWithLinks(content) { 46 // Clear preview 47 preview.innerHTML = ''; 48 49 if (!content) return; 50 51 const lines = content.split('\n'); 52 const urlRegex = /(https?:\/\/[^\s]+)/g; 53 54 lines.forEach((line, index) => { 55 const lineDiv = document.createElement('div'); 56 lineDiv.style.marginBottom = '0'; 57 58 // Check if line contains URLs 59 if (urlRegex.test(line)) { 60 // Reset regex lastIndex 61 urlRegex.lastIndex = 0; 62 63 let lastIndex = 0; 64 let match; 65 66 while ((match = urlRegex.exec(line)) !== null) { 67 // Add text before URL 68 if (match.index > lastIndex) { 69 const textNode = document.createTextNode(line.substring(lastIndex, match.index)); 70 lineDiv.appendChild(textNode); 71 } 72 73 // Add clickable link 74 const link = document.createElement('a'); 75 link.href = match[0]; 76 link.textContent = match[0]; 77 link.target = '_blank'; 78 link.rel = 'noopener noreferrer'; 79 // link.style.color = '#2563eb'; 80 link.style.textDecoration = 'underline'; 81 lineDiv.appendChild(link); 82 83 lastIndex = match.index + match[0].length; 84 } 85 86 // Add remaining text after last URL 87 if (lastIndex < line.length) { 88 const textNode = document.createTextNode(line.substring(lastIndex)); 89 lineDiv.appendChild(textNode); 90 } 91 } else { 92 // No URLs, just add plain text 93 lineDiv.textContent = line; 94 } 95 96 preview.appendChild(lineDiv); 97 }); 98 } 99 100 function updateStats(stats) { 101 document.getElementById('total-lines').textContent = stats.total_lines || 0; 102 document.getElementById('categorized-lines').textContent = stats.categorized_lines || 0; 103 document.getElementById('uncategorized-lines').textContent = stats.uncategorized || 0; 104 document.getElementById('unique-categories').textContent = stats.unique_categories || 0; 105 } 106 107 function showMessage(text, type = 'success') { 108 const msg = document.getElementById('message'); 109 msg.textContent = text; 110 msg.className = `message ${type} show`; 111 setTimeout(() => { 112 msg.classList.remove('show'); 113 }, 3000); 114 } 115 116 async function saveNotes() { 117 const content = editor.value; 118 119 try { 120 const response = await fetch('/notes/save', { 121 method: 'POST', 122 headers: {'Content-Type': 'application/x-www-form-urlencoded'}, 123 body: `content=${encodeURIComponent(content)}` 124 }); 125 126 const result = await response.json(); 127 128 if (result.success) { 129 showMessage('Notes saved successfully!', 'success'); 130 originalContent = content; 131 // Reload to show sorted/deduped version 132 setTimeout(() => location.reload(), 1000); 133 } else { 134 showMessage('Failed to save notes', 'error'); 135 } 136 } catch (error) { 137 showMessage('Error: ' + error.message, 'error'); 138 } 139 } 140 141 async function previewProcessing() { 142 const content = editor.value; 143 144 try { 145 const response = await fetch('/notes/preview', { 146 method: 'POST', 147 headers: {'Content-Type': 'application/x-www-form-urlencoded'}, 148 body: `content=${encodeURIComponent(content)}` 149 }); 150 151 const result = await response.json(); 152 153 if (result.success) { 154 editor.value = result.content; 155 currentContent = result.content; 156 updatePreview(result.content); 157 updateStats(result.stats); 158 showMessage(`Processed: ${result.lineCount} lines after sort & dedupe`, 'success'); 159 } 160 } catch (error) { 161 showMessage('Error: ' + error.message, 'error'); 162 } 163 } 164 165 async function applySedRule(ruleIndex) { 166 const content = editor.value; 167 168 try { 169 const response = await fetch('/notes/apply-sed', { 170 method: 'POST', 171 headers: {'Content-Type': 'application/x-www-form-urlencoded'}, 172 body: `content=${encodeURIComponent(content)}&rule_index=${ruleIndex}` 173 }); 174 175 // Get the response text first to see what we're actually receiving 176 const responseText = await response.text(); 177 console.log('Raw response:', responseText); 178 console.log('Response status:', response.status); 179 console.log('Content-Type:', response.headers.get('content-type')); 180 181 // Try to parse as JSON 182 let result; 183 try { 184 result = JSON.parse(responseText); 185 } catch (parseError) { 186 console.error('JSON parse error:', parseError); 187 console.error('Response was:', responseText.substring(0, 200)); 188 showMessage('Server returned invalid response. Check console for details.', 'error'); 189 return; 190 } 191 192 if (result.success) { 193 editor.value = result.content; 194 currentContent = result.content; 195 updatePreview(result.content); 196 updateStats(result.stats); 197 showMessage('Sed rule applied successfully!', 'success'); 198 } else { 199 showMessage(result.error || 'Sed rule failed', 'error'); 200 } 201 } catch (error) { 202 console.error('Fetch error:', error); 203 showMessage('Error: ' + error.message, 'error'); 204 } 205 } 206 207 function filterByCategory() { 208 const category = document.getElementById('category-filter').value; 209 if (!category) { 210 updatePreview(currentContent); 211 return; 212 } 213 214 const lines = currentContent.split('\n'); 215 216 // Handle uncategorized filter 217 if (category === '__uncategorized__') { 218 const filtered = lines.filter(line => { 219 const trimmed = line.trim(); 220 if (!trimmed) return false; 221 // Line is uncategorized if it doesn't contain '>' or '>' is not a separator 222 return !trimmed.includes('>') || trimmed.indexOf('>') === trimmed.length - 1; 223 }); 224 updatePreviewWithLinks(filtered.join('\n')); 225 return; 226 } 227 228 // Handle normal category filter 229 const filtered = lines.filter(line => { 230 if (line.includes('>')) { 231 const cat = line.split('>')[0].trim(); 232 return cat === category; 233 } 234 return false; 235 }); 236 237 updatePreviewWithLinks(filtered.join('\n')); 238 } 239 240 function clearFilters() { 241 searchInput.value = ''; 242 document.getElementById('category-filter').value = ''; 243 updatePreview(currentContent); 244 } 245 246 function exportNotes() { 247 window.location.href = '/notes/export'; 248 } 249 250 // Warn on unsaved changes 251 window.addEventListener('beforeunload', (e) => { 252 if (editor.value !== originalContent) { 253 e.preventDefault(); 254 e.returnValue = ''; 255 } 256 });