UNPKG

crevr

Version:

A web-based UI for reviewing and reverting Claude Code changes

901 lines (784 loc) 42.5 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Crevr - Claude Code Changes</title> <!-- Tailwind CSS --> <script src="https://cdn.tailwindcss.com"></script> <!-- Alpine.js --> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script> <!-- Prism.js for syntax highlighting --> <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-jsx.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-tsx.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-css.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-java.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-go.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-rust.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-diff.min.js"></script> <style> [x-cloak] { display: none !important; } /* Custom scrollbar */ ::-webkit-scrollbar { width: 12px; height: 12px; } ::-webkit-scrollbar-track { background: #1e293b; } ::-webkit-scrollbar-thumb { background: #475569; border-radius: 6px; } ::-webkit-scrollbar-thumb:hover { background: #64748b; } /* File tree icons */ .file-icon::before { content: "📄"; margin-right: 0.5rem; } .folder-icon::before { content: "📁"; margin-right: 0.5rem; } .folder-open-icon::before { content: "📂"; margin-right: 0.5rem; } /* Diff styles */ .diff-added { background-color: rgba(34, 197, 94, 0.1); border-left: 3px solid #22c55e; } .diff-removed { background-color: rgba(239, 68, 68, 0.1); border-left: 3px solid #ef4444; } .diff-added::before { content: "+"; color: #22c55e; font-weight: bold; margin-right: 0.5rem; } .diff-removed::before { content: "-"; color: #ef4444; font-weight: bold; margin-right: 0.5rem; } /* Prism overrides for better diff display */ pre[class*="language-"] { background: transparent; margin: 0; padding: 0; } code[class*="language-"] { background: transparent; } .line-highlight { background: rgba(255, 255, 255, 0.1); } /* Inline diff styles */ .inline-file-view { line-height: 1.6; } .line-container { display: flex; position: relative; min-height: 1.6em; } .line-number { user-select: none; color: #64748b; text-align: right; padding-right: 16px; width: 60px; flex-shrink: 0; border-right: 1px solid #374151; } .line-content { flex: 1; padding-left: 16px; padding-right: 16px; white-space: pre-wrap; word-break: break-word; } .line-unchanged { background: transparent; } .line-added { background: rgba(34, 197, 94, 0.1); border-left: 3px solid #22c55e; } .line-removed { background: rgba(239, 68, 68, 0.1); border-left: 3px solid #ef4444; } .line-context { color: #94a3b8; } .revert-button { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: #ef4444; color: white; border: none; padding: 2px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; opacity: 0; transition: opacity 0.2s; } .line-container:hover .revert-button { opacity: 1; } .revert-button:hover { background: #dc2626; } </style> </head> <body class="bg-slate-900 text-slate-100" x-data="claudeRevert()"> <!-- Loading State --> <div x-show="loading" class="flex items-center justify-center h-screen"> <div class="text-xl text-slate-400">Loading changes...</div> </div> <!-- Main App --> <div x-show="!loading" x-cloak class="h-screen flex flex-col"> <!-- Header --> <header class="bg-slate-800 border-b border-slate-700 px-6 py-4"> <h1 class="text-2xl font-semibold">🔄 Crevr</h1> <p class="text-sm text-slate-400 mt-1">Review and revert file changes</p> </header> <!-- Main Content --> <div class="flex-1 flex overflow-hidden"> <!-- File Tree Sidebar --> <aside class="w-96 bg-slate-800 border-r border-slate-700 overflow-y-auto"> <div class="p-4"> <h2 class="text-sm font-semibold text-slate-400 uppercase tracking-wider mb-4">Changed Files</h2> <!-- File Tree --> <div x-show="Object.keys(fileTree).length === 0" class="text-slate-500 text-center py-8"> No changes found </div> <template x-for="(node, path) in fileTree" :key="path"> <div class="mb-2"> <div @click="toggleFolder(path)" class="cursor-pointer hover:bg-slate-700 rounded px-3 py-2 transition-colors" :class="{ 'bg-slate-700': expandedFolders[path] }"> <span :class="expandedFolders[path] ? 'folder-open-icon' : 'folder-icon'" class="text-sm font-medium"> <span x-text="path"></span> <span class="text-slate-500 text-xs ml-2" x-text="`(${Object.keys(node.files).length})`"></span> </span> </div> <!-- Files in folder --> <div x-show="expandedFolders[path]" class="ml-4 mt-1"> <template x-for="(changes, filename) in node.files" :key="filename"> <div @click="selectFile(path + '/' + filename)" class="cursor-pointer hover:bg-slate-700 rounded px-3 py-2 mb-1 transition-colors flex items-center justify-between" :class="{ 'bg-blue-900/30 border-l-2 border-blue-500': selectedFile === path + '/' + filename, 'opacity-60': !changes[0].fileExists }"> <div class="flex items-center"> <span class="file-icon text-sm" x-text="filename"></span> <span x-show="!changes[0].fileExists" class="text-xs text-red-400 ml-2">(deleted)</span> </div> <span class="text-xs px-2 py-1 rounded-full" :class="getChangeTypeClass(changes[0].type)" x-text="changes[0].type"></span> </div> </template> </div> </div> </template> </div> </aside> <!-- Content Area --> <main class="flex-1 bg-slate-900 overflow-hidden flex flex-col"> <!-- File Header --> <div x-show="selectedFile" class="bg-slate-800 border-b border-slate-700 px-6 py-4"> <div class="flex items-center justify-between"> <div> <h2 class="text-lg font-medium" x-text="selectedFile"></h2> <div class="flex items-center gap-4 mt-2 text-sm text-slate-400"> <span x-text="getSelectedFileChanges().length + ' changes'"></span> <span x-text="'Last modified: ' + formatDate(getSelectedFileChanges()[0]?.timestamp)"></span> </div> </div> <div class="flex items-center gap-3"> <span class="text-xs text-slate-500"> <span class="font-semibold" x-text="getTotalChangedLines()"></span> lines affected </span> <button @click="revertAllFileChanges()" class="px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg text-sm font-medium transition-colors"> Revert All Changes </button> </div> </div> </div> <!-- View Mode Tabs --> <div x-show="selectedFile" class="bg-slate-800 border-b border-slate-700 px-6 py-2"> <div class="flex gap-4"> <button @click="viewMode = 'diff'" :class="viewMode === 'diff' ? 'text-blue-400 border-b-2 border-blue-400' : 'text-slate-400'" class="pb-2 px-1 font-medium text-sm hover:text-slate-200 transition-colors"> Diff View </button> <button @click="viewMode = 'inline'" :class="viewMode === 'inline' ? 'text-blue-400 border-b-2 border-blue-400' : 'text-slate-400'" class="pb-2 px-1 font-medium text-sm hover:text-slate-200 transition-colors"> Inline View </button> </div> </div> <!-- File Content --> <div x-show="selectedFile" class="flex-1 overflow-y-auto"> <div class="font-mono text-sm" x-html="fileViewContent"></div> </div> <!-- Empty State --> <div x-show="!selectedFile" class="flex-1 flex items-center justify-center text-slate-500"> <div class="text-center"> <p class="text-xl mb-2">No file selected</p> <p class="text-sm">Select a file from the sidebar to view changes</p> </div> </div> </main> </div> </div> <!-- Status Messages --> <div x-show="statusMessage" x-text="statusMessage" :class="statusType === 'success' ? 'bg-green-600' : 'bg-red-600'" class="fixed bottom-6 right-6 px-6 py-3 rounded-lg text-white font-medium shadow-lg" x-transition> </div> <script> function claudeRevert() { return { ws: null, loading: true, changes: [], fileTree: {}, expandedFolders: {}, selectedFile: null, statusMessage: '', statusType: 'success', fileViewContent: '', viewMode: 'diff', // 'diff' or 'inline' init() { window.claudeRevertInstance = this; this.connectWebSocket(); // Watch for view mode changes this.$watch('viewMode', () => { if (this.selectedFile) { this.loadFileContent(); } }); }, connectWebSocket() { this.ws = new WebSocket('ws://localhost:3456'); this.ws.onopen = () => { console.log('Connected to server'); this.ws.send(JSON.stringify({ type: 'getChanges' })); }; this.ws.onmessage = (event) => { const data = JSON.parse(event.data); switch (data.type) { case 'changes': this.handleChanges(data.changes); break; case 'revertSuccess': this.handleRevertSuccess(data.changeId); break; case 'revertError': this.handleRevertError(data.error); break; } }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); this.showStatus('Connection error', 'error'); }; this.ws.onclose = () => { console.log('Disconnected from server'); setTimeout(() => this.connectWebSocket(), 3000); }; }, async handleChanges(changes) { this.changes = changes; await this.buildFileTree(); this.loading = false; // Auto-expand all folders initially Object.keys(this.fileTree).forEach(path => { this.expandedFolders[path] = true; }); }, async buildFileTree() { this.fileTree = {}; for (const change of this.changes) { const parts = change.filePath.split('/'); const filename = parts.pop(); const folderPath = parts.join('/') || '/'; if (!this.fileTree[folderPath]) { this.fileTree[folderPath] = { files: {} }; } if (!this.fileTree[folderPath].files[filename]) { this.fileTree[folderPath].files[filename] = []; } // Check if file exists change.fileExists = await this.checkFileExists(change.filePath); this.fileTree[folderPath].files[filename].push(change); } }, async checkFileExists(filePath) { try { const response = await fetch(`/api/file-exists?path=${encodeURIComponent(filePath)}`); const data = await response.json(); return data.exists; } catch (error) { console.warn('Error checking file existence:', error); return true; // Assume exists if we can't check } }, toggleFolder(path) { this.expandedFolders[path] = !this.expandedFolders[path]; }, async selectFile(filePath) { this.selectedFile = filePath; await this.loadFileContent(); }, getSelectedFileChanges() { if (!this.selectedFile) return []; return this.changes.filter(c => c.filePath === this.selectedFile) .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); }, getChangeTypeClass(type) { const classes = { 'create': 'bg-green-900/50 text-green-400', 'edit': 'bg-yellow-900/50 text-yellow-400', 'write': 'bg-purple-900/50 text-purple-400', 'delete': 'bg-red-900/50 text-red-400' }; return classes[type] || 'bg-gray-900/50 text-gray-400'; }, formatDate(timestamp) { if (!timestamp) return ''; return new Date(timestamp).toLocaleString(); }, renderChangePreview() { const changes = this.getSelectedFileChanges(); if (changes.length === 0) return '<div class="p-4 text-slate-400">No changes to display</div>'; let html = '<div class="p-4 space-y-6">'; // Show each change with before/after view changes.forEach((change, index) => { html += this.renderSingleChange(change, index); }); html += '</div>'; return html; }, renderSingleChange(change, index) { let html = ` <div class="bg-slate-800 rounded-lg overflow-hidden"> <div class="px-4 py-3 bg-slate-700 flex items-center justify-between"> <div> <span class="text-sm font-medium text-slate-200"> <span class="mr-1">${this.getChangeIcon(change.type)}</span> Change #${index + 1}: ${this.formatChangeType(change.type)} </span> <span class="text-xs text-slate-400 ml-3"> ${this.formatDate(change.timestamp)} </span> </div> <button onclick="window.claudeRevertInstance.revertChange('${change.id}')" class="px-3 py-1 bg-red-600 hover:bg-red-700 rounded text-xs font-medium transition-colors"> Revert This Change </button> </div> `; if (change.type === 'edit' && change.oldString && change.newString) { // Show before/after for edits with ccundo-style display html += ` <div class="p-4"> <div class="text-xs font-semibold text-slate-400 mb-3"> <span class="text-yellow-400">⚠️</span> String replacement that will be reversed: </div> <div class="bg-slate-900 rounded-lg p-4 font-mono text-sm"> <div class="mb-3"> <span class="text-red-400">-</span> <span class="text-red-300">"${this.escapeHtml(change.newString)}"</span> </div> <div> <span class="text-green-400">+</span> <span class="text-green-300">"${this.escapeHtml(change.oldString)}"</span> </div> </div> ${change.diff ? ` <div class="mt-4 text-xs text-slate-400"> <details class="cursor-pointer"> <summary class="hover:text-slate-300">View full diff context</summary> <div class="mt-2">${this.renderDiffContent(change.diff)}</div> </details> </div> ` : ''} </div> `; } else if (change.diff) { // Show unified diff html += '<div class="p-4">' + this.renderDiffContent(change.diff) + '</div>'; } else if (change.type === 'create') { html += '<div class="p-4 text-green-400">➕ New file was created</div>'; } else if (change.type === 'delete') { html += '<div class="p-4 text-red-400">🗑️ File was deleted</div>'; } html += '</div>'; return html; }, renderUnifiedDiff() { const changes = this.getSelectedFileChanges(); if (changes.length === 0) return '<div class="p-4 text-slate-400">No changes to display</div>'; let html = '<div class="p-4">'; // Process each change changes.forEach((change, index) => { // Change header html += ` <div class="mb-6 bg-slate-800 rounded-lg overflow-hidden"> <div class="px-4 py-3 bg-slate-700 flex items-center justify-between"> <div> <span class="text-sm font-medium text-slate-200"> ${this.formatChangeType(change.type)} </span> <span class="text-xs text-slate-400 ml-3"> ${this.formatDate(change.timestamp)} </span> </div> <button onclick="window.claudeRevertInstance.revertChange('${change.id}')" class="px-3 py-1 bg-red-600 hover:bg-red-700 rounded text-xs font-medium transition-colors"> Revert </button> </div> <div class="p-4 overflow-x-auto"> `; if (change.diff) { html += this.renderDiffContent(change.diff); } else if (change.type === 'create') { html += '<div class="text-green-400">+ File created</div>'; } else if (change.type === 'delete') { html += '<div class="text-red-400">- File deleted</div>'; } html += ` </div> </div> `; }); html += '</div>'; return html; }, renderDiffContent(diff) { const lines = diff.split('\n'); let html = '<div class="font-mono text-sm">'; let lineNumber = { old: 0, new: 0 }; let inHunk = false; lines.forEach((line, index) => { if (line.startsWith('@@')) { // Parse hunk header const match = line.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/); if (match) { lineNumber.old = parseInt(match[1]) - 1; lineNumber.new = parseInt(match[2]) - 1; inHunk = true; } html += `<div class="text-blue-400 text-xs mt-4 mb-2 opacity-70">${this.escapeHtml(line)}</div>`; } else if (line.startsWith('+++') || line.startsWith('---')) { // Skip file headers in unified view } else if (inHunk) { if (line.startsWith('+')) { lineNumber.new++; const content = line.substring(1); html += ` <div class="flex bg-green-900/20 border-l-2 border-green-500"> <div class="w-16 text-right pr-2 text-slate-600 select-none">&nbsp;</div> <div class="w-16 text-right pr-2 text-slate-500 select-none">${lineNumber.new}</div> <div class="pl-2 text-green-400">+</div> <div class="pl-2 flex-1 text-green-300">${this.escapeHtml(content)}</div> </div> `; } else if (line.startsWith('-')) { lineNumber.old++; const content = line.substring(1); html += ` <div class="flex bg-red-900/20 border-l-2 border-red-500"> <div class="w-16 text-right pr-2 text-slate-500 select-none">${lineNumber.old}</div> <div class="w-16 text-right pr-2 text-slate-600 select-none">&nbsp;</div> <div class="pl-2 text-red-400">-</div> <div class="pl-2 flex-1 text-red-300">${this.escapeHtml(content)}</div> </div> `; } else { lineNumber.old++; lineNumber.new++; html += ` <div class="flex text-slate-400"> <div class="w-16 text-right pr-2 text-slate-600 select-none">${lineNumber.old}</div> <div class="w-16 text-right pr-2 text-slate-600 select-none">${lineNumber.new}</div> <div class="pl-2">&nbsp;</div> <div class="pl-2 flex-1">${this.escapeHtml(line)}</div> </div> `; } } }); html += '</div>'; return html; }, renderDiff(diff) { if (!diff) return '<span class="text-slate-500">No diff available</span>'; const lines = diff.split('\n'); let html = ''; let inCodeBlock = false; let language = 'javascript'; lines.forEach(line => { if (line.startsWith('+++') || line.startsWith('---')) { // File headers html += `<div class="text-slate-500 text-xs mb-2">${this.escapeHtml(line)}</div>`; } else if (line.startsWith('@@')) { // Hunk headers html += `<div class="text-blue-400 text-xs my-2">${this.escapeHtml(line)}</div>`; } else if (line.startsWith('+')) { // Added lines const content = line.substring(1); html += `<div class="diff-added pl-8 pr-4 py-1">${this.highlightCode(content, language)}</div>`; } else if (line.startsWith('-')) { // Removed lines const content = line.substring(1); html += `<div class="diff-removed pl-8 pr-4 py-1">${this.highlightCode(content, language)}</div>`; } else { // Context lines html += `<div class="text-slate-400 pl-8 pr-4 py-1">${this.highlightCode(line, language)}</div>`; } }); return html; }, highlightCode(code, language) { // Simple syntax highlighting - in production, use Prism.js return this.escapeHtml(code); }, formatChangeType(type) { const types = { 'create': 'File Created', 'edit': 'File Edited', 'write': 'File Written', 'delete': 'File Deleted' }; return types[type] || type; }, getChangeIcon(type) { const icons = { 'create': '➕', 'edit': '✏️', 'write': '📝', 'delete': '🗑️' }; return icons[type] || '📄'; }, getTotalChangedLines() { const changes = this.getSelectedFileChanges(); let total = 0; changes.forEach(change => { if (change.diff) { const lines = change.diff.split('\n'); lines.forEach(line => { if (line.startsWith('+') && !line.startsWith('+++')) total++; if (line.startsWith('-') && !line.startsWith('---')) total++; }); } }); return total; }, async renderInlineFileView() { if (!this.selectedFile) return ''; const fileChanges = this.getSelectedFileChanges(); if (fileChanges.length === 0) return ''; // Debug: log the changes console.log('File changes for', this.selectedFile, fileChanges); // Get current file content let currentContent = ''; try { const response = await fetch(`/api/file-content?path=${encodeURIComponent(this.selectedFile)}`); if (response.ok) { currentContent = await response.text(); } } catch (error) { console.error('Error fetching file content:', error); return '<div class="p-4 text-red-400">Error loading file content</div>'; } const lines = currentContent.split('\n'); let html = '<div class="inline-file-view">'; // Process each line lines.forEach((line, index) => { const lineNumber = index + 1; const isChanged = this.isLineChanged(line, fileChanges); let lineClass = 'line-unchanged'; let revertButton = ''; if (isChanged) { const changeType = this.getLineChangeType(line, fileChanges); console.log(`Line ${lineNumber}: "${line}" -> ${changeType}`); lineClass = changeType === 'added' ? 'line-added' : 'line-removed'; const changeId = this.getLineChangeId(line, fileChanges); revertButton = `<button class="revert-button" onclick="window.claudeRevertInstance.revertChange('${changeId}')">Revert</button>`; } html += ` <div class="line-container ${lineClass}"> <div class="line-number">${lineNumber}</div> <div class="line-content">${this.escapeHtml(line)}</div> ${revertButton} </div> `; }); html += '</div>'; return html; }, isLineChanged(line, changes) { // Check if this line was part of any change return changes.some(change => { if (change.diff) { const diffLines = change.diff.split('\n'); return diffLines.some(diffLine => { // Check for added lines (starts with +) if (diffLine.startsWith('+') && !diffLine.startsWith('+++')) { const diffContent = diffLine.substring(1); return diffContent === line || (diffContent.trim() === '' && line.trim() === ''); } // Check for removed lines (starts with -) if (diffLine.startsWith('-') && !diffLine.startsWith('---')) { const diffContent = diffLine.substring(1); return diffContent === line || (diffContent.trim() === '' && line.trim() === ''); } return false; }); } return false; }); }, getLineChangeType(line, changes) { // For Edit changes, we need to determine if this line is part of newString or oldString // Since we're showing the current file content, all visible lines should be "added" if they're part of changes for (const change of changes) { if (change.type === 'edit' && change.diff) { const diffLines = change.diff.split('\n'); for (const diffLine of diffLines) { if (diffLine.startsWith('+') && !diffLine.startsWith('+++')) { const diffContent = diffLine.substring(1); if (diffContent === line || (diffContent.trim() === '' && line.trim() === '')) { return 'added'; } } // Don't show removed lines since they're not in the current file } } } return 'unchanged'; }, getLineChangeId(line, changes) { // Find the change ID that affected this line for (const change of changes) { if (change.diff) { const diffLines = change.diff.split('\n'); for (const diffLine of diffLines) { if ((diffLine.startsWith('+') || diffLine.startsWith('-')) && !diffLine.startsWith('+++') && !diffLine.startsWith('---')) { const diffContent = diffLine.substring(1); if (diffContent === line || (diffContent.trim() === '' && line.trim() === '')) { return change.id; } } } } } return null; }, escapeHtml(text) { const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' }; return text.replace(/[&<>"']/g, m => map[m]); }, revertChange(changeId) { console.log('Reverting change:', changeId); if (!confirm('Are you sure you want to revert this change?')) { return; } this.ws.send(JSON.stringify({ type: 'revert', changeId: changeId })); }, revertAllFileChanges() { const changes = this.getSelectedFileChanges(); const message = `⚠️ Cascading Revert Warning\n\nThis will revert all ${changes.length} changes to this file.\n\nChanges are reverted in reverse chronological order (newest first).\n\nAre you sure you want to continue?`; if (!confirm(message)) { return; } changes.forEach(change => { this.ws.send(JSON.stringify({ type: 'revert', changeId: change.id })); }); }, handleRevertSuccess(changeId) { this.showStatus('Change reverted successfully!', 'success'); // Remove the reverted change this.changes = this.changes.filter(c => c.id !== changeId); this.buildFileTree(); // Refresh the file view to show the reverted content if (this.selectedFile) { this.refreshFileView(); } // If no more changes for selected file, clear selection if (this.selectedFile && this.getSelectedFileChanges().length === 0) { this.selectedFile = null; } }, handleRevertError(error) { this.showStatus(`Error: ${error}`, 'error'); }, showStatus(message, type) { this.statusMessage = message; this.statusType = type; setTimeout(() => { this.statusMessage = ''; }, 3000); }, async loadFileContent() { if (!this.selectedFile) { this.fileViewContent = ''; return; } try { if (this.viewMode === 'diff') { this.fileViewContent = this.renderChangePreview(); } else { this.fileViewContent = await this.renderInlineFileView(); } } catch (error) { console.error('Error loading file content:', error); this.fileViewContent = '<div class="p-4 text-red-400">Error loading file content</div>'; } }, async refreshFileView() { await this.loadFileContent(); } }; } </script> </body> </html>