Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Text Parser & Clipboard Utility</title> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 20px; | |
| } | |
| .container { | |
| background: white; | |
| border-radius: 16px; | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15); | |
| padding: 40px; | |
| width: 100%; | |
| max-width: 800px; | |
| backdrop-filter: blur(10px); | |
| } | |
| h1 { | |
| text-align: center; | |
| color: #333; | |
| margin-bottom: 30px; | |
| font-size: 2.2rem; | |
| font-weight: 600; | |
| } | |
| .input-section { | |
| margin-bottom: 30px; | |
| } | |
| .input-label { | |
| display: block; | |
| margin-bottom: 10px; | |
| color: #555; | |
| font-weight: 500; | |
| font-size: 1.1rem; | |
| } | |
| #textInput { | |
| width: 100%; | |
| height: 200px; | |
| padding: 15px; | |
| border: 2px solid #e1e5e9; | |
| border-radius: 10px; | |
| font-family: 'Courier New', monospace; | |
| font-size: 14px; | |
| line-height: 1.5; | |
| resize: vertical; | |
| transition: border-color 0.3s ease; | |
| } | |
| #textInput:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
| } | |
| .controls { | |
| display: flex; | |
| gap: 15px; | |
| align-items: center; | |
| margin-bottom: 20px; | |
| } | |
| #nextButton { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| padding: 12px 30px; | |
| border-radius: 8px; | |
| font-size: 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); | |
| } | |
| #nextButton:hover:not(:disabled) { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); | |
| } | |
| #nextButton:active:not(:disabled) { | |
| transform: translateY(0); | |
| } | |
| #nextButton:disabled { | |
| background: #ccc; | |
| cursor: not-allowed; | |
| box-shadow: none; | |
| } | |
| #resetButton { | |
| background: #f8f9fa; | |
| color: #666; | |
| border: 2px solid #e1e5e9; | |
| padding: 12px 20px; | |
| border-radius: 8px; | |
| font-size: 16px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| #resetButton:hover { | |
| background: #e9ecef; | |
| border-color: #adb5bd; | |
| } | |
| .status { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin-bottom: 20px; | |
| } | |
| .status-text { | |
| font-size: 16px; | |
| font-weight: 500; | |
| padding: 8px 16px; | |
| border-radius: 6px; | |
| transition: all 0.3s ease; | |
| } | |
| .status-ready { | |
| background: #e3f2fd; | |
| color: #1976d2; | |
| border: 1px solid #bbdefb; | |
| } | |
| .status-copying { | |
| background: #e8f5e8; | |
| color: #2e7d32; | |
| border: 1px solid #c8e6c9; | |
| } | |
| .status-complete { | |
| background: #fff3e0; | |
| color: #f57c00; | |
| border: 1px solid #ffcc02; | |
| } | |
| .status-error { | |
| background: #ffebee; | |
| color: #d32f2f; | |
| border: 1px solid #ffcdd2; | |
| } | |
| .preview-section { | |
| margin-top: 20px; | |
| } | |
| .preview-label { | |
| display: block; | |
| margin-bottom: 10px; | |
| color: #555; | |
| font-weight: 500; | |
| } | |
| #textPreview { | |
| background: #f8f9fa; | |
| border: 1px solid #e1e5e9; | |
| border-radius: 8px; | |
| padding: 15px; | |
| max-height: 100px; | |
| overflow-y: auto; | |
| font-family: 'Courier New', monospace; | |
| font-size: 14px; | |
| line-height: 1.5; | |
| white-space: pre-wrap; | |
| } | |
| .line-number { | |
| color: #666; | |
| margin-right: 10px; | |
| user-select: none; | |
| } | |
| .current-line { | |
| background: #fff3cd; | |
| border-left: 4px solid #ffc107; | |
| margin: -2px -15px; | |
| padding: 2px 15px; | |
| } | |
| .copied-line { | |
| background: #d4edda; | |
| border-left: 4px solid #28a745; | |
| margin: -2px -15px; | |
| padding: 2px 15px; | |
| opacity: 0.7; | |
| } | |
| .info-section { | |
| margin-top: 20px; | |
| padding: 15px; | |
| background: #f8f9fa; | |
| border-radius: 8px; | |
| border-left: 4px solid #17a2b8; | |
| } | |
| .info-section h3 { | |
| color: #17a2b8; | |
| margin-bottom: 10px; | |
| } | |
| .info-section p { | |
| color: #666; | |
| line-height: 1.5; | |
| } | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 20px; | |
| margin: 10px; | |
| } | |
| h1 { | |
| font-size: 1.8rem; | |
| } | |
| .controls { | |
| flex-direction: column; | |
| align-items: stretch; | |
| } | |
| #nextButton, #resetButton { | |
| width: 100%; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>📋 Text Parser & Clipboard Utility</h1> | |
| <div class="input-section"> | |
| <label for="textInput" class="input-label">Paste your text here:</label> | |
| <textarea id="textInput" placeholder="Paste your multi-line text here. Each non-empty line will be copied individually when you click 'Next'."></textarea> | |
| </div> | |
| <div class="controls"> | |
| <button id="nextButton" disabled>Next</button> | |
| <button id="resetButton">Reset</button> | |
| <div class="status"> | |
| <span id="statusText" class="status-text status-ready">Ready - Paste text to begin</span> | |
| </div> | |
| </div> | |
| <div class="preview-section"> | |
| <label class="preview-label">Text Preview:</label> | |
| <div id="textPreview">No text loaded</div> | |
| </div> | |
| <div class="info-section"> | |
| <h3>📝 Instructions</h3> | |
| <p> | |
| 1. Paste your text in the textarea above<br> | |
| 2. Click "Next" to copy each line to your clipboard sequentially<br> | |
| 3. Empty lines are automatically skipped<br> | |
| 4. Use "Reset" to start over with the same text<br> | |
| <strong>Note:</strong> This tool requires HTTPS or localhost for clipboard access. | |
| </p> | |
| </div> | |
| </div> | |
| <script> | |
| class TextParserClipboard { | |
| constructor() { | |
| this.lines = []; | |
| this.currentIndex = 0; | |
| this.isProcessing = false; | |
| // Get DOM elements | |
| this.textInput = document.getElementById('textInput'); | |
| this.nextButton = document.getElementById('nextButton'); | |
| this.resetButton = document.getElementById('resetButton'); | |
| this.statusText = document.getElementById('statusText'); | |
| this.textPreview = document.getElementById('textPreview'); | |
| this.initializeEventListeners(); | |
| this.checkClipboardSupport(); | |
| } | |
| initializeEventListeners() { | |
| this.textInput.addEventListener('input', () => this.processText()); | |
| this.textInput.addEventListener('paste', () => { | |
| // Small delay to allow paste content to be processed | |
| setTimeout(() => this.processText(), 10); | |
| }); | |
| this.nextButton.addEventListener('click', () => this.copyNextLine()); | |
| this.resetButton.addEventListener('click', () => this.reset()); | |
| // Handle keyboard shortcuts | |
| document.addEventListener('keydown', (e) => { | |
| if (e.ctrlKey || e.metaKey) { | |
| if (e.key === 'Enter') { | |
| e.preventDefault(); | |
| if (!this.nextButton.disabled) { | |
| this.copyNextLine(); | |
| } | |
| } else if (e.key === 'r') { | |
| e.preventDefault(); | |
| this.reset(); | |
| } | |
| } | |
| }); | |
| } | |
| checkClipboardSupport() { | |
| if (!navigator.clipboard || !navigator.clipboard.writeText) { | |
| this.updateStatus('Clipboard API not supported. Use HTTPS or localhost.', 'error'); | |
| this.nextButton.disabled = true; | |
| } | |
| } | |
| processText() { | |
| const text = this.textInput.value.trim(); | |
| if (!text) { | |
| this.lines = []; | |
| this.currentIndex = 0; | |
| this.nextButton.disabled = true; | |
| this.updateStatus('Ready - Paste text to begin', 'ready'); | |
| this.updatePreview(); | |
| return; | |
| } | |
| // Split text into lines and filter out empty lines | |
| this.lines = text | |
| .split('\n') | |
| .map(line => line.trim()) | |
| .filter(line => line.length > 0); | |
| this.currentIndex = 0; | |
| this.nextButton.disabled = this.lines.length === 0; | |
| if (this.lines.length === 0) { | |
| this.updateStatus('No valid lines found', 'error'); | |
| } else { | |
| this.updateStatus(`Ready - ${this.lines.length} lines loaded`, 'ready'); | |
| } | |
| this.updatePreview(); | |
| } | |
| async copyNextLine() { | |
| if (this.currentIndex >= this.lines.length || this.isProcessing) { | |
| return; | |
| } | |
| this.isProcessing = true; | |
| const lineText = this.lines[this.currentIndex]; | |
| try { | |
| await navigator.clipboard.writeText(lineText); | |
| this.currentIndex++; | |
| this.updateStatus(`Copied line ${this.currentIndex}/${this.lines.length}`, 'copying'); | |
| if (this.currentIndex >= this.lines.length) { | |
| this.nextButton.disabled = true; | |
| this.updateStatus('All lines copied! Click Reset to start over.', 'complete'); | |
| } | |
| this.updatePreview(); | |
| } catch (error) { | |
| console.error('Failed to copy text:', error); | |
| this.updateStatus('Failed to copy to clipboard', 'error'); | |
| } finally { | |
| this.isProcessing = false; | |
| } | |
| } | |
| reset() { | |
| // Clear the textarea | |
| this.textInput.value = ''; | |
| // Reset all variables | |
| this.lines = []; | |
| this.currentIndex = 0; | |
| this.isProcessing = false; | |
| // Reset UI state | |
| this.nextButton.disabled = true; | |
| this.updateStatus('Ready - Paste text to begin', 'ready'); | |
| this.updatePreview(); | |
| } | |
| updateStatus(message, type) { | |
| this.statusText.textContent = message; | |
| this.statusText.className = `status-text status-${type}`; | |
| } | |
| updatePreview() { | |
| if (this.lines.length === 0) { | |
| this.textPreview.textContent = 'No text loaded'; | |
| return; | |
| } | |
| const previewLines = this.lines.map((line, index) => { | |
| const lineNumber = (index + 1).toString().padStart(2, '0'); | |
| let cssClass = ''; | |
| if (index < this.currentIndex) { | |
| cssClass = 'copied-line'; | |
| } else if (index === this.currentIndex) { | |
| cssClass = 'current-line'; | |
| } | |
| return `<div class="${cssClass}"><span class="line-number">${lineNumber}:</span>${this.escapeHtml(line)}</div>`; | |
| }); | |
| this.textPreview.innerHTML = previewLines.join(''); | |
| // Scroll current line into view | |
| const currentElement = this.textPreview.querySelector('.current-line'); | |
| if (currentElement) { | |
| currentElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| } | |
| } | |
| escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| } | |
| // Initialize the application when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', () => { | |
| new TextParserClipboard(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |