Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Enhanced Magic Text HTML Editor</title> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| margin: 0; | |
| padding: 20px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| color: #333; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| background: rgba(255, 255, 255, 0.95); | |
| border-radius: 20px; | |
| padding: 30px; | |
| backdrop-filter: blur(10px); | |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); | |
| transition: all 0.3s ease; | |
| } | |
| .container.ai-active { | |
| margin-left: 320px; | |
| } | |
| h1 { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| background: linear-gradient(135deg, #667eea, #764ba2); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| font-size: 2.5em; | |
| font-weight: 700; | |
| } | |
| .editor-layout { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 20px; | |
| margin-top: 20px; | |
| } | |
| .editor-layout.collapsed { | |
| grid-template-columns: 60px 1fr; | |
| } | |
| .input-section, .output-section { | |
| background: #fff; | |
| border-radius: 15px; | |
| padding: 20px; | |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| } | |
| .input-section.collapsed { | |
| width: 60px; | |
| padding: 20px 10px; | |
| overflow: hidden; | |
| } | |
| .input-section.collapsed .section-title, | |
| .input-section.collapsed textarea, | |
| .input-section.collapsed .status-bar { | |
| display: none; | |
| } | |
| .section-title { | |
| font-size: 1.2em; | |
| font-weight: 600; | |
| margin-bottom: 15px; | |
| color: #4a5568; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .section-title::before { | |
| content: ''; | |
| width: 4px; | |
| height: 20px; | |
| background: linear-gradient(135deg, #667eea, #764ba2); | |
| border-radius: 2px; | |
| } | |
| textarea { | |
| width: 100%; | |
| height: 300px; | |
| font-family: 'Fira Code', 'Consolas', monospace; | |
| font-size: 14px; | |
| border: 2px solid #e2e8f0; | |
| border-radius: 10px; | |
| padding: 15px; | |
| resize: vertical; | |
| transition: all 0.3s ease; | |
| line-height: 1.5; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
| } | |
| .toolbar { | |
| display: flex; | |
| gap: 10px; | |
| margin-bottom: 15px; | |
| flex-wrap: wrap; | |
| } | |
| .btn { | |
| background: linear-gradient(135deg, #667eea, #764ba2); | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 25px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| font-weight: 500; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); | |
| } | |
| .btn:active { | |
| transform: translateY(0); | |
| } | |
| .btn.secondary { | |
| background: #e2e8f0; | |
| color: #4a5568; | |
| } | |
| .btn.secondary:hover { | |
| background: #cbd5e0; | |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); | |
| } | |
| .btn.danger { | |
| background: linear-gradient(135deg, #fc8181, #e53e3e); | |
| } | |
| .btn.danger:hover { | |
| box-shadow: 0 5px 15px rgba(229, 62, 62, 0.4); | |
| } | |
| #renderedArea { | |
| border: 2px dashed #e2e8f0; | |
| border-radius: 10px; | |
| padding: 20px; | |
| min-height: 300px; | |
| max-height: 500px; | |
| overflow-y: auto; | |
| transition: all 0.3s ease; | |
| } | |
| #renderedArea:empty::before { | |
| content: 'Your HTML will render here...'; | |
| color: #a0aec0; | |
| font-style: italic; | |
| text-align: center; | |
| display: block; | |
| padding: 50px; | |
| } | |
| .editable { | |
| transition: all 0.3s ease; | |
| border-radius: 4px; | |
| position: relative; | |
| padding: 5px; | |
| margin: 2px; | |
| } | |
| .editable:hover { | |
| outline: 2px dashed #667eea; | |
| background-color: rgba(102, 126, 234, 0.05); | |
| cursor: text; | |
| } | |
| .editable:hover::after { | |
| content: '✏️ Click to edit'; | |
| position: absolute; | |
| top: -25px; | |
| left: 0; | |
| background: #667eea; | |
| color: white; | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| font-size: 12px; | |
| white-space: nowrap; | |
| z-index: 10; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| .editing { | |
| outline: 2px solid #48bb78 ; | |
| background-color: rgba(72, 187, 120, 0.1) ; | |
| box-shadow: 0 0 10px rgba(72, 187, 120, 0.2); | |
| } | |
| .editing:hover::after { | |
| content: '✅ Press Enter to save'; | |
| background: #48bb78; | |
| } | |
| .status-bar { | |
| background: #f7fafc; | |
| padding: 10px 15px; | |
| border-radius: 8px; | |
| margin-top: 15px; | |
| font-size: 14px; | |
| color: #4a5568; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .live-preview { | |
| background: linear-gradient(45deg, #48bb78, #38a169); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .live-preview::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: -100%; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); | |
| animation: shimmer 2s infinite; | |
| } | |
| @keyframes shimmer { | |
| 0% { left: -100%; } | |
| 100% { left: 100%; } | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(-5px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .templates-dropdown { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .dropdown-content { | |
| display: none; | |
| position: absolute; | |
| background-color: white; | |
| min-width: 200px; | |
| box-shadow: 0 8px 16px rgba(0,0,0,0.1); | |
| border-radius: 8px; | |
| z-index: 1000; | |
| top: 100%; | |
| left: 0; | |
| margin-top: 5px; | |
| } | |
| .dropdown-content a { | |
| color: #4a5568; | |
| padding: 12px 16px; | |
| text-decoration: none; | |
| display: block; | |
| border-radius: 8px; | |
| margin: 4px; | |
| transition: all 0.2s ease; | |
| } | |
| .dropdown-content a:hover { | |
| background-color: #f7fafc; | |
| color: #667eea; | |
| } | |
| .templates-dropdown:hover .dropdown-content { | |
| display: block; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| @media (max-width: 768px) { | |
| .editor-layout { | |
| grid-template-columns: 1fr; | |
| } | |
| .toolbar { | |
| justify-content: center; | |
| } | |
| .btn { | |
| font-size: 12px; | |
| padding: 8px 16px; | |
| } | |
| h1 { | |
| font-size: 2em; | |
| } | |
| } | |
| .word-count { | |
| font-size: 12px; | |
| color: #718096; | |
| } | |
| .element-count { | |
| font-size: 12px; | |
| color: #718096; | |
| } | |
| .save-indicator { | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| color: #48bb78; | |
| font-weight: 500; | |
| } | |
| .save-indicator.show { | |
| opacity: 1; | |
| } | |
| /* AI Assistant Styles */ | |
| .ai-assistant { | |
| position: fixed; | |
| left: 20px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| width: 300px; | |
| background: white; | |
| border-radius: 15px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); | |
| z-index: 1000; | |
| display: none; | |
| flex-direction: column; | |
| max-height: 80vh; | |
| } | |
| .ai-assistant.active { | |
| display: flex; | |
| animation: slideIn 0.3s ease; | |
| } | |
| @keyframes slideIn { | |
| from { transform: translateY(-50%) translateX(-20px); opacity: 0; } | |
| to { transform: translateY(-50%) translateX(0); opacity: 1; } | |
| } | |
| .ai-header { | |
| background: linear-gradient(135deg, #667eea, #764ba2); | |
| color: white; | |
| padding: 15px; | |
| border-radius: 15px 15px 0 0; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .ai-header h3 { | |
| margin: 0; | |
| font-size: 1.2em; | |
| } | |
| .ai-close { | |
| background: none; | |
| border: none; | |
| color: white; | |
| font-size: 1.2em; | |
| cursor: pointer; | |
| } | |
| .ai-chat { | |
| flex: 1; | |
| padding: 15px; | |
| overflow-y: auto; | |
| max-height: 400px; | |
| } | |
| .ai-message { | |
| margin-bottom: 15px; | |
| padding: 10px 15px; | |
| border-radius: 10px; | |
| max-width: 85%; | |
| } | |
| .ai-message.user { | |
| background: #e3f2fd; | |
| margin-left: auto; | |
| border-bottom-right-radius: 2px; | |
| } | |
| .ai-message.assistant { | |
| background: #f5f5f5; | |
| margin-right: auto; | |
| border-bottom-left-radius: 2px; | |
| } | |
| .ai-input { | |
| display: flex; | |
| padding: 15px; | |
| border-top: 1px solid #e2e8f0; | |
| } | |
| .ai-input input { | |
| flex: 1; | |
| padding: 10px 15px; | |
| border: 1px solid #e2e8f0; | |
| border-radius: 25px; | |
| outline: none; | |
| } | |
| .ai-input button { | |
| background: #667eea; | |
| color: white; | |
| border: none; | |
| border-radius: 50%; | |
| width: 40px; | |
| height: 40px; | |
| margin-left: 10px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .ai-icon { | |
| position: absolute; | |
| top: 5px; | |
| right: 5px; | |
| background: rgba(102, 126, 234, 0.1); | |
| border-radius: 50%; | |
| width: 24px; | |
| height: 24px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 12px; | |
| cursor: pointer; | |
| opacity: 0; | |
| transition: all 0.3s ease; | |
| z-index: 5; | |
| } | |
| .editable:hover .ai-icon { | |
| opacity: 1; | |
| } | |
| .ai-icon:hover { | |
| background: rgba(102, 126, 234, 0.2); | |
| transform: scale(1.1); | |
| } | |
| .selected { | |
| background-color: rgba(102, 126, 234, 0.1) ; | |
| outline: 2px solid #667eea ; | |
| } | |
| .multi-select-toolbar { | |
| position: fixed; | |
| bottom: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: white; | |
| padding: 10px 20px; | |
| border-radius: 25px; | |
| box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15); | |
| display: flex; | |
| gap: 10px; | |
| z-index: 999; | |
| display: none; | |
| } | |
| .multi-select-toolbar.active { | |
| display: flex; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| .multi-select-toolbar .btn { | |
| font-size: 12px; | |
| padding: 8px 15px; | |
| } | |
| /* Collapse Toggle Styles */ | |
| .collapse-toggle { | |
| position: absolute; | |
| top: 10px; | |
| right: 10px; | |
| background: #667eea; | |
| color: white; | |
| border: none; | |
| border-radius: 50%; | |
| width: 30px; | |
| height: 30px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| z-index: 10; | |
| } | |
| .collapse-toggle:hover { | |
| background: #5a6fd8; | |
| transform: scale(1.1); | |
| } | |
| .input-section.collapsed .collapse-toggle { | |
| position: static; | |
| margin: 0 auto; | |
| } | |
| .input-section.collapsed .collapse-toggle .icon { | |
| transform: rotate(180deg); | |
| } | |
| /* Undo Button Styles */ | |
| .undo-btn { | |
| background: linear-gradient(135deg, #48bb78, #38a169); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .undo-btn:disabled { | |
| background: #cbd5e0; | |
| cursor: not-allowed; | |
| transform: none ; | |
| box-shadow: none ; | |
| } | |
| .undo-btn:disabled:hover { | |
| transform: none ; | |
| box-shadow: none ; | |
| } | |
| .undo-btn .icon { | |
| transition: transform 0.3s ease; | |
| } | |
| .undo-btn:active .icon { | |
| transform: rotate(-360deg); | |
| } | |
| /* History Panel */ | |
| .history-panel { | |
| position: absolute; | |
| top: 100%; | |
| right: 0; | |
| background: white; | |
| border-radius: 10px; | |
| box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15); | |
| padding: 10px; | |
| z-index: 100; | |
| min-width: 200px; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| display: none; | |
| } | |
| .history-panel.active { | |
| display: block; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| .history-item { | |
| padding: 8px 12px; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| font-size: 14px; | |
| margin-bottom: 5px; | |
| } | |
| .history-item:hover { | |
| background: #f7fafc; | |
| } | |
| .history-item.active { | |
| background: #e3f2fd; | |
| color: #667eea; | |
| } | |
| .history-dropdown { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| /* PDF Export Styles */ | |
| .pdf-export-btn { | |
| background: linear-gradient(135deg, #e53e3e, #c53030); | |
| } | |
| .pdf-export-btn:hover { | |
| box-shadow: 0 5px 15px rgba(229, 62, 62, 0.4); | |
| } | |
| .pdf-modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.5); | |
| z-index: 2000; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .pdf-modal.active { | |
| display: flex; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| .pdf-modal-content { | |
| background: white; | |
| padding: 30px; | |
| border-radius: 15px; | |
| max-width: 500px; | |
| width: 90%; | |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2); | |
| } | |
| .pdf-options { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 15px; | |
| margin: 20px 0; | |
| } | |
| .pdf-option { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| padding: 10px; | |
| border: 2px solid #e2e8f0; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .pdf-option:hover { | |
| border-color: #667eea; | |
| background: #f7fafc; | |
| } | |
| .pdf-option.selected { | |
| border-color: #667eea; | |
| background: rgba(102, 126, 234, 0.05); | |
| } | |
| .pdf-option input { | |
| display: none; | |
| } | |
| .pdf-preview { | |
| max-height: 200px; | |
| overflow-y: auto; | |
| border: 1px solid #e2e8f0; | |
| border-radius: 8px; | |
| padding: 15px; | |
| margin: 15px 0; | |
| background: #f8f9fa; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container" id="mainContainer"> | |
| <h1>🎨 Enhanced Magic HTML Editor</h1> | |
| <div class="toolbar"> | |
| <button class="btn live-preview" onclick="toggleLivePreview()"> | |
| <span id="livePreviewText">🔴 Enable Live Preview</span> | |
| </button> | |
| <button class="btn" onclick="loadHTML()">🚀 Render HTML</button> | |
| <button class="btn secondary" onclick="clearEditor()">🗑️ Clear</button> | |
| <button class="btn secondary" onclick="copyRenderedHTML()">📋 Copy HTML</button> | |
| <button class="btn secondary" onclick="exportHTML()">💾 Export</button> | |
| <button class="btn pdf-export-btn" onclick="showPDFOptions()">📄 Save as PDF</button> | |
| <div class="history-dropdown"> | |
| <button class="btn undo-btn" id="undoBtn" onclick="undoChange()" disabled> | |
| <span class="icon">↶</span> Undo | |
| </button> | |
| <div class="history-panel" id="historyPanel"> | |
| <!-- History items will be added here --> | |
| </div> | |
| </div> | |
| <div class="templates-dropdown"> | |
| <button class="btn secondary">📋 Templates ▼</button> | |
| <div class="dropdown-content"> | |
| <a href="#" onclick="loadTemplate('basic')">Basic Page</a> | |
| <a href="#" onclick="loadTemplate('card')">Card Layout</a> | |
| <a href="#" onclick="loadTemplate('blog')">Blog Post</a> | |
| <a href="#" onclick="loadTemplate('landing')">Landing Page</a> | |
| <a href="#" onclick="loadTemplate('table')">Data Table</a> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="editor-layout" id="editorLayout"> | |
| <div class="input-section" id="inputSection"> | |
| <button class="collapse-toggle" onclick="toggleEditorCollapse()"> | |
| <span class="icon">◀</span> | |
| </button> | |
| <div class="section-title">HTML Editor</div> | |
| <textarea id="htmlInput" placeholder="Write your HTML here..."> | |
| <h1 class="title">Welcome to My Enhanced Page</h1> | |
| <div class="content-card"> | |
| <h2>Editable Content</h2> | |
| <p>Click any element to edit it directly! This text is fully editable.</p> | |
| <div class="highlight-box"> | |
| <strong>Pro tip:</strong> Hover over elements to see edit hints! | |
| </div> | |
| </div> | |
| <style> | |
| .title { color: #667eea; text-align: center; } | |
| .content-card { background: #f8f9fa; padding: 20px; border-radius: 10px; margin: 20px 0; } | |
| .highlight-box { background: #e3f2fd; padding: 15px; border-left: 4px solid #2196f3; margin-top: 15px; } | |
| </style> | |
| </textarea> | |
| <div class="status-bar"> | |
| <span class="word-count" id="wordCount">Words: 0</span> | |
| <span class="save-indicator" id="saveIndicator">✅ Saved</span> | |
| </div> | |
| </div> | |
| <div class="output-section"> | |
| <div class="section-title">Live Preview</div> | |
| <div id="renderedArea"></div> | |
| <div class="status-bar"> | |
| <span class="element-count" id="elementCount">Elements: 0</span> | |
| <span>🎯 Click elements to edit | Ctrl+Click to select multiple</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- AI Assistant Panel --> | |
| <div class="ai-assistant" id="aiAssistant"> | |
| <div class="ai-header"> | |
| <h3>🤖 AI Assistant</h3> | |
| <button class="ai-close" onclick="closeAIAssistant()">✕</button> | |
| </div> | |
| <div class="ai-chat" id="aiChat"> | |
| <div class="ai-message assistant"> | |
| Hello! I'm your AI assistant. How can I help you with your HTML today? | |
| </div> | |
| </div> | |
| <div class="ai-input"> | |
| <input type="text" id="aiInput" placeholder="Ask me anything about HTML..."> | |
| <button onclick="sendAIMessage()">➤</button> | |
| </div> | |
| </div> | |
| <!-- Multi-select toolbar --> | |
| <div class="multi-select-toolbar" id="multiSelectToolbar"> | |
| <button class="btn" onclick="applyToSelected('style', 'color: #667eea;')">Style Selected</button> | |
| <button class="btn secondary" onclick="clearSelection()">Clear Selection</button> | |
| <button class="btn danger" onclick="deleteSelected()">Delete Selected</button> | |
| </div> | |
| <!-- PDF Export Modal --> | |
| <div class="pdf-modal" id="pdfModal"> | |
| <div class="pdf-modal-content"> | |
| <h2 style="margin-top: 0; color: #4a5568;">Export as PDF</h2> | |
| <p>Choose your preferred export format:</p> | |
| <div class="pdf-options"> | |
| <div class="pdf-option" onclick="selectPDFOption('text')"> | |
| <input type="radio" name="pdf-option" id="text-option" value="text" checked> | |
| <div style="flex: 1;"> | |
| <strong>Text Format</strong> | |
| <p style="margin: 5px 0 0; font-size: 14px; color: #718096;"> | |
| Best for text content, smaller file size | |
| </p> | |
| </div> | |
| </div> | |
| <div class="pdf-option" onclick="selectPDFOption('visual')"> | |
| <input type="radio" name="pdf-option" id="visual-option" value="visual"> | |
| <div style="flex: 1;"> | |
| <strong>Visual Format</strong> | |
| <p style="margin: 5px 0 0; font-size: 14px; color: #718096;"> | |
| Exact visual representation, larger file size | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="pdf-preview" id="pdfPreview"> | |
| <strong>Preview:</strong> | |
| <div id="pdfPreviewContent" style="margin-top: 10px; font-size: 14px;"> | |
| <!-- Preview content will be added here --> | |
| </div> | |
| </div> | |
| <div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px;"> | |
| <button class="btn secondary" onclick="closePDFModal()">Cancel</button> | |
| <button class="btn pdf-export-btn" onclick="generatePDF()">Generate PDF</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- External Libraries --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> | |
| <script> | |
| let livePreviewEnabled = false; | |
| let editingElement = null; | |
| let selectedElements = new Set(); | |
| let isMultiSelectMode = false; | |
| let isEditorCollapsed = false; | |
| let history = []; | |
| let historyIndex = -1; | |
| let selectedPDFOption = 'text'; | |
| // Initialize with default content | |
| window.onload = function() { | |
| loadHTML(); | |
| updateWordCount(); | |
| saveToHistory(); | |
| updatePDFPreview(); | |
| }; | |
| function makeElementsEditable(container) { | |
| const elements = container.querySelectorAll("*"); | |
| elements.forEach(el => { | |
| // Skip script and style elements | |
| if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE') return; | |
| el.classList.add('editable'); | |
| // Add AI icon to each element | |
| const aiIcon = document.createElement('div'); | |
| aiIcon.className = 'ai-icon'; | |
| aiIcon.innerHTML = '🤖'; | |
| aiIcon.title = 'Get AI help for this element'; | |
| aiIcon.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| openAIAssistant(el); | |
| }); | |
| el.appendChild(aiIcon); | |
| el.addEventListener('click', function handleClick(e) { | |
| e.stopPropagation(); | |
| // Check if Ctrl key is pressed for multi-select | |
| if (e.ctrlKey || e.metaKey) { | |
| toggleElementSelection(el); | |
| return; | |
| } | |
| if (editingElement && editingElement !== el) { | |
| finishEditing(editingElement); | |
| } | |
| if (!isMultiSelectMode) { | |
| startEditing(el); | |
| } | |
| }); | |
| el.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| finishEditing(el); | |
| } else if (e.key === 'Escape') { | |
| finishEditing(el); | |
| } | |
| }); | |
| el.addEventListener('blur', () => { | |
| setTimeout(() => finishEditing(el), 100); | |
| }); | |
| el.addEventListener('input', () => { | |
| showSaveIndicator(); | |
| }); | |
| }); | |
| updateElementCount(elements.length); | |
| } | |
| function toggleElementSelection(element) { | |
| if (selectedElements.has(element)) { | |
| selectedElements.delete(element); | |
| element.classList.remove('selected'); | |
| } else { | |
| selectedElements.add(element); | |
| element.classList.add('selected'); | |
| } | |
| // Show/hide multi-select toolbar | |
| const toolbar = document.getElementById('multiSelectToolbar'); | |
| if (selectedElements.size > 0) { | |
| toolbar.classList.add('active'); | |
| isMultiSelectMode = true; | |
| } else { | |
| toolbar.classList.remove('active'); | |
| isMultiSelectMode = false; | |
| } | |
| } | |
| function clearSelection() { | |
| selectedElements.forEach(el => { | |
| el.classList.remove('selected'); | |
| }); | |
| selectedElements.clear(); | |
| document.getElementById('multiSelectToolbar').classList.remove('active'); | |
| isMultiSelectMode = false; | |
| } | |
| function deleteSelected() { | |
| if (confirm(`Are you sure you want to delete ${selectedElements.size} selected elements?`)) { | |
| selectedElements.forEach(el => { | |
| el.remove(); | |
| }); | |
| selectedElements.clear(); | |
| document.getElementById('multiSelectToolbar').classList.remove('active'); | |
| isMultiSelectMode = false; | |
| showSaveIndicator(); | |
| } | |
| } | |
| function applyToSelected(property, value) { | |
| selectedElements.forEach(el => { | |
| if (property === 'style') { | |
| el.style.cssText += value; | |
| } | |
| }); | |
| showSaveIndicator(); | |
| } | |
| function startEditing(element) { | |
| if (element.getAttribute('contenteditable') !== 'true') { | |
| element.setAttribute('contenteditable', 'true'); | |
| element.classList.add('editing'); | |
| element.focus(); | |
| editingElement = element; | |
| // Select all text for easy editing | |
| const range = document.createRange(); | |
| range.selectNodeContents(element); | |
| const selection = window.getSelection(); | |
| selection.removeAllRanges(); | |
| selection.addRange(range); | |
| } | |
| } | |
| function finishEditing(element) { | |
| if (element && element.getAttribute('contenteditable') === 'true') { | |
| element.removeAttribute('contenteditable'); | |
| element.classList.remove('editing'); | |
| editingElement = null; | |
| saveToHistory(); | |
| showSaveIndicator(); | |
| } | |
| } | |
| function loadHTML() { | |
| const htmlContent = document.getElementById('htmlInput').value; | |
| const renderedArea = document.getElementById('renderedArea'); | |
| // Clear and insert new content | |
| renderedArea.innerHTML = htmlContent; | |
| // Make all elements editable | |
| makeElementsEditable(renderedArea); | |
| showSaveIndicator(); | |
| updatePDFPreview(); | |
| } | |
| function toggleLivePreview() { | |
| livePreviewEnabled = !livePreviewEnabled; | |
| const button = document.getElementById('livePreviewText'); | |
| if (livePreviewEnabled) { | |
| button.textContent = '🟢 Live Preview ON'; | |
| startLivePreview(); | |
| } else { | |
| button.textContent = '🔴 Enable Live Preview'; | |
| stopLivePreview(); | |
| } | |
| } | |
| function startLivePreview() { | |
| const textarea = document.getElementById('htmlInput'); | |
| textarea.addEventListener('input', loadHTML); | |
| loadHTML(); | |
| } | |
| function stopLivePreview() { | |
| const textarea = document.getElementById('htmlInput'); | |
| textarea.removeEventListener('input', loadHTML); | |
| } | |
| function clearEditor() { | |
| if (confirm('Are you sure you want to clear the editor?')) { | |
| document.getElementById('htmlInput').value = ''; | |
| document.getElementById('renderedArea').innerHTML = ''; | |
| updateWordCount(); | |
| updateElementCount(0); | |
| clearSelection(); | |
| saveToHistory(); | |
| updatePDFPreview(); | |
| } | |
| } | |
| function copyRenderedHTML() { | |
| const renderedArea = document.getElementById('renderedArea'); | |
| const htmlContent = renderedArea.innerHTML; | |
| navigator.clipboard.writeText(htmlContent).then(() => { | |
| showTemporaryMessage('HTML copied to clipboard!'); | |
| }).catch(err => { | |
| console.error('Failed to copy: ', err); | |
| }); | |
| } | |
| function exportHTML() { | |
| const htmlContent = document.getElementById('htmlInput').value; | |
| const blob = new Blob([`<!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Exported HTML</title> | |
| </head> | |
| <body> | |
| ${htmlContent} | |
| </body> | |
| </html>`], { type: 'text/html' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = 'magic-editor-export.html'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } | |
| function loadTemplate(templateName) { | |
| const templates = { | |
| basic: `<h1>My Basic Page</h1> | |
| <p>This is a simple paragraph.</p> | |
| <ul> | |
| <li>List item 1</li> | |
| <li>List item 2</li> | |
| </ul>`, | |
| card: `<div style="max-width: 400px; margin: 20px auto; padding: 20px; border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); background: white;"> | |
| <h2 style="color: #333; margin-top: 0;">Card Title</h2> | |
| <p style="color: #666; line-height: 1.6;">This is a beautiful card component with shadow and rounded corners.</p> | |
| <button style="background: #667eea; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer;">Action Button</button> | |
| </div>`, | |
| blog: `<article style="max-width: 700px; margin: 0 auto; font-family: Georgia, serif;"> | |
| <header> | |
| <h1 style="font-size: 2.5em; margin-bottom: 10px;">Blog Post Title</h1> | |
| <p style="color: #666; font-style: italic;">Published on March 15, 2024</p> | |
| </header> | |
| <p style="font-size: 1.1em; line-height: 1.8;">This is the opening paragraph of your blog post. Make it engaging and compelling to draw readers in.</p> | |
| <h2>Section Heading</h2> | |
| <p style="line-height: 1.8;">Content for this section goes here. You can add multiple paragraphs, images, and other elements.</p> | |
| </article>`, | |
| landing: `<div style="text-align: center; padding: 50px 20px; background: linear-gradient(135deg, #667eea, #764ba2); color: white; border-radius: 15px;"> | |
| <h1 style="font-size: 3em; margin-bottom: 20px;">Welcome to Our Product</h1> | |
| <p style="font-size: 1.2em; margin-bottom: 30px; opacity: 0.9;">The best solution for your needs. Join thousands of satisfied customers.</p> | |
| <button style="background: white; color: #667eea; border: none; padding: 15px 30px; font-size: 1.1em; border-radius: 25px; cursor: pointer; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">Get Started Now</button> | |
| </div>`, | |
| table: `<table style="width: 100%; border-collapse: collapse; margin: 20px 0; box-shadow: 0 4px 15px rgba(0,0,0,0.1);"> | |
| <thead> | |
| <tr style="background: #667eea; color: white;"> | |
| <th style="padding: 15px; text-align: left;">Name</th> | |
| <th style="padding: 15px; text-align: left;">Email</th> | |
| <th style="padding: 15px; text-align: left;">Role</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr style="border-bottom: 1px solid #eee;"> | |
| <td style="padding: 15px;">John Doe</td> | |
| <td style="padding: 15px;">[email protected]</td> | |
| <td style="padding: 15px;">Developer</td> | |
| </tr> | |
| <tr style="background: #f8f9fa; border-bottom: 1px solid #eee;"> | |
| <td style="padding: 15px;">Jane Smith</td> | |
| <td style="padding: 15px;">[email protected]</td> | |
| <td style="padding: 15px;">Designer</td> | |
| </tr> | |
| </tbody> | |
| </table>` | |
| }; | |
| document.getElementById('htmlInput').value = templates[templateName] || ''; | |
| loadHTML(); | |
| updateWordCount(); | |
| saveToHistory(); | |
| updatePDFPreview(); | |
| } | |
| function updateWordCount() { | |
| const text = document.getElementById('htmlInput').value; | |
| const words = text.trim().split(/\s+/).filter(word => word.length > 0).length; | |
| document.getElementById('wordCount').textContent = `Words: ${words}`; | |
| } | |
| function updateElementCount(count) { | |
| document.getElementById('elementCount').textContent = `Elements: ${count}`; | |
| } | |
| function showSaveIndicator() { | |
| const indicator = document.getElementById('saveIndicator'); | |
| indicator.classList.add('show'); | |
| setTimeout(() => { | |
| indicator.classList.remove('show'); | |
| }, 2000); | |
| } | |
| function showTemporaryMessage(message) { | |
| const indicator = document.getElementById('saveIndicator'); | |
| const originalText = indicator.textContent; | |
| indicator.textContent = message; | |
| indicator.classList.add('show'); | |
| setTimeout(() => { | |
| indicator.textContent = originalText; | |
| indicator.classList.remove('show'); | |
| }, 2000); | |
| } | |
| // AI Assistant Functions | |
| function openAIAssistant(element) { | |
| const assistant = document.getElementById('aiAssistant'); | |
| const container = document.getElementById('mainContainer'); | |
| assistant.classList.add('active'); | |
| container.classList.add('ai-active'); | |
| // Store the current element for context | |
| assistant.currentElement = element; | |
| // Add initial context message | |
| addAIMessage('assistant', `I see you're working on a <${element.tagName.toLowerCase()}> element. How can I help you with it?`); | |
| } | |
| function closeAIAssistant() { | |
| const assistant = document.getElementById('aiAssistant'); | |
| const container = document.getElementById('mainContainer'); | |
| assistant.classList.remove('active'); | |
| container.classList.remove('ai-active'); | |
| // Clear chat | |
| document.getElementById('aiChat').innerHTML = ` | |
| <div class="ai-message assistant"> | |
| Hello! I'm your AI assistant. How can I help you with your HTML today? | |
| </div> | |
| `; | |
| } | |
| function sendAIMessage() { | |
| const input = document.getElementById('aiInput'); | |
| const message = input.value.trim(); | |
| if (!message) return; | |
| // Add user message to chat | |
| addAIMessage('user', message); | |
| // Clear input | |
| input.value = ''; | |
| // Simulate AI response (in a real implementation, this would call an API) | |
| setTimeout(() => { | |
| const assistant = document.getElementById('aiAssistant'); | |
| const element = assistant.currentElement; | |
| let response = ''; | |
| if (message.toLowerCase().includes('style') || message.toLowerCase().includes('css')) { | |
| response = `For styling your <${element.tagName.toLowerCase()}> element, you can use CSS properties like: | |
| - color: #333; (for text color) | |
| - background-color: #f5f5f5; (for background) | |
| - font-size: 16px; (for text size) | |
| - padding: 10px; (for internal spacing) | |
| - margin: 10px; (for external spacing) | |
| Would you like me to apply any specific styles?`; | |
| } else if (message.toLowerCase().includes('content') || message.toLowerCase().includes('text')) { | |
| response = `For content in your <${element.tagName.toLowerCase()}> element, consider: | |
| - Keeping it concise and relevant | |
| - Using proper heading hierarchy | |
| - Adding semantic meaning with appropriate tags | |
| - Ensuring accessibility with alt text for images | |
| What type of content are you planning to add?`; | |
| } else if (message.toLowerCase().includes('api') || message.toLowerCase().includes('openai')) { | |
| response = `Here's an example of how to make an API request to OpenAI: | |
| \`\`\`javascript | |
| import OpenAI from "openai"; | |
| const client = new OpenAI(); | |
| const response = await client.responses.create({ | |
| model: "gpt-4", | |
| input: "Write a one-sentence bedtime story about a unicorn." | |
| }); | |
| console.log(response.output_text); | |
| \`\`\` | |
| Note: You'll need to install the OpenAI package and set up your API key.`; | |
| } else { | |
| response = `I can help you with various aspects of your HTML element: | |
| - Styling and CSS properties | |
| - Content suggestions | |
| - Accessibility improvements | |
| - Best practices for ${element.tagName.toLowerCase()} elements | |
| - Integration with JavaScript or APIs | |
| What specific aspect would you like to focus on?`; | |
| } | |
| addAIMessage('assistant', response); | |
| }, 1000); | |
| } | |
| function addAIMessage(sender, text) { | |
| const chat = document.getElementById('aiChat'); | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `ai-message ${sender}`; | |
| messageDiv.textContent = text; | |
| chat.appendChild(messageDiv); | |
| chat.scrollTop = chat.scrollHeight; | |
| } | |
| // Editor Collapse Functionality | |
| function toggleEditorCollapse() { | |
| const inputSection = document.getElementById('inputSection'); | |
| const editorLayout = document.getElementById('editorLayout'); | |
| isEditorCollapsed = !isEditorCollapsed; | |
| if (isEditorCollapsed) { | |
| inputSection.classList.add('collapsed'); | |
| editorLayout.classList.add('collapsed'); | |
| } else { | |
| inputSection.classList.remove('collapsed'); | |
| editorLayout.classList.remove('collapsed'); | |
| } | |
| } | |
| // Undo/Redo Functionality | |
| function saveToHistory() { | |
| const content = document.getElementById('htmlInput').value; | |
| // If we're not at the end of history, remove future states | |
| if (historyIndex < history.length - 1) { | |
| history = history.slice(0, historyIndex + 1); | |
| } | |
| history.push(content); | |
| historyIndex = history.length - 1; | |
| updateUndoButton(); | |
| updateHistoryPanel(); | |
| } | |
| function undoChange() { | |
| if (historyIndex > 0) { | |
| historyIndex--; | |
| document.getElementById('htmlInput').value = history[historyIndex]; | |
| loadHTML(); | |
| updateWordCount(); | |
| updateUndoButton(); | |
| updateHistoryPanel(); | |
| } | |
| } | |
| function redoChange() { | |
| if (historyIndex < history.length - 1) { | |
| historyIndex++; | |
| document.getElementById('htmlInput').value = history[historyIndex]; | |
| loadHTML(); | |
| updateWordCount(); | |
| updateUndoButton(); | |
| updateHistoryPanel(); | |
| } | |
| } | |
| function updateUndoButton() { | |
| const undoBtn = document.getElementById('undoBtn'); | |
| undoBtn.disabled = historyIndex <= 0; | |
| } | |
| function updateHistoryPanel() { | |
| const panel = document.getElementById('historyPanel'); | |
| panel.innerHTML = ''; | |
| // Show last 10 history items | |
| const startIndex = Math.max(0, history.length - 10); | |
| for (let i = startIndex; i < history.length; i++) { | |
| const item = document.createElement('div'); | |
| item.className = 'history-item'; | |
| if (i === historyIndex) { | |
| item.classList.add('active'); | |
| } | |
| const preview = history[i].substring(0, 30) + (history[i].length > 30 ? '...' : ''); | |
| item.textContent = `Version ${i + 1}: ${preview}`; | |
| item.onclick = () => { | |
| historyIndex = i; | |
| document.getElementById('htmlInput').value = history[i]; | |
| loadHTML(); | |
| updateWordCount(); | |
| updateUndoButton(); | |
| updateHistoryPanel(); | |
| }; | |
| panel.appendChild(item); | |
| } | |
| } | |
| // PDF Export Functions | |
| function showPDFOptions() { | |
| document.getElementById('pdfModal').classList.add('active'); | |
| updatePDFPreview(); | |
| } | |
| function closePDFModal() { | |
| document.getElementById('pdfModal').classList.remove('active'); | |
| } | |
| function selectPDFOption(option) { | |
| selectedPDFOption = option; | |
| // Update UI | |
| document.querySelectorAll('.pdf-option').forEach(el => { | |
| el.classList.remove('selected'); | |
| }); | |
| if (option === 'text') { | |
| document.getElementById('text-option').parentElement.classList.add('selected'); | |
| } else { | |
| document.getElementById('visual-option').parentElement.classList.add('selected'); | |
| } | |
| updatePDFPreview(); | |
| } | |
| function updatePDFPreview() { | |
| const previewContent = document.getElementById('pdfPreviewContent'); | |
| const renderedArea = document.getElementById('renderedArea'); | |
| if (selectedPDFOption === 'text') { | |
| const textContent = renderedArea.textContent || renderedArea.innerText || ''; | |
| const preview = textContent.substring(0, 150) + (textContent.length > 150 ? '...' : ''); | |
| previewContent.innerHTML = `<div style="white-space: pre-wrap; font-family: monospace;">${preview}</div>`; | |
| } else { | |
| previewContent.innerHTML = '<div style="color: #718096; font-style: italic;">Visual preview not available. PDF will contain exact visual representation of your content.</div>'; | |
| } | |
| } | |
| function generatePDF() { | |
| closePDFModal(); | |
| if (selectedPDFOption === 'text') { | |
| saveAsPDF(); | |
| } else { | |
| saveAsPDFWithScreenshot(); | |
| } | |
| } | |
| function saveAsPDF() { | |
| const { jsPDF } = window.jspdf; | |
| const renderedArea = document.getElementById('renderedArea'); | |
| // Create a new PDF instance | |
| const doc = new jsPDF(); | |
| // Get the HTML content | |
| const content = renderedArea.innerHTML; | |
| // Add title | |
| doc.setFontSize(20); | |
| doc.text('HTML Export', 105, 15, { align: 'center' }); | |
| // Add current date | |
| const now = new Date(); | |
| doc.setFontSize(10); | |
| doc.text(`Generated on: ${now.toLocaleDateString()} ${now.toLocaleTimeString()}`, 105, 22, { align: 'center' }); | |
| // Add separator line | |
| doc.setDrawColor(200, 200, 200); | |
| doc.line(10, 25, 200, 25); | |
| // Convert HTML to PDF | |
| doc.setFontSize(12); | |
| // Create a temporary div to parse HTML content | |
| const tempDiv = document.createElement('div'); | |
| tempDiv.innerHTML = content; | |
| // Extract text content (simplified version) | |
| let textContent = tempDiv.textContent || tempDiv.innerText || ''; | |
| // Clean and format text | |
| textContent = textContent.replace(/\s+/g, ' ').trim(); | |
| // Split text into lines that fit PDF width | |
| const lines = doc.splitTextToSize(textContent, 180); | |
| // Add content to PDF | |
| let yPosition = 35; | |
| const lineHeight = 7; | |
| const pageHeight = doc.internal.pageSize.height; | |
| lines.forEach(line => { | |
| // Check if we need a new page | |
| if (yPosition > pageHeight - 20) { | |
| doc.addPage(); | |
| yPosition = 20; | |
| } | |
| doc.text(line, 15, yPosition); | |
| yPosition += lineHeight; | |
| }); | |
| // Add page numbers | |
| const pageCount = doc.internal.getNumberOfPages(); | |
| for (let i = 1; i <= pageCount; i++) { | |
| doc.setPage(i); | |
| doc.setFontSize(8); | |
| doc.text(`Page ${i} of ${pageCount}`, 105, pageHeight - 10, { align: 'center' }); | |
| } | |
| // Save the PDF | |
| doc.save('html-export.pdf'); | |
| showTemporaryMessage('PDF saved successfully!'); | |
| } | |
| function saveAsPDFWithScreenshot() { | |
| const { jsPDF } = window.jspdf; | |
| const renderedArea = document.getElementById('renderedArea'); | |
| // Use html2canvas to capture the visual representation | |
| html2canvas(renderedArea, { | |
| scale: 2, | |
| useCORS: true, | |
| logging: false, | |
| width: renderedArea.scrollWidth, | |
| height: renderedArea.scrollHeight | |
| }).then(canvas => { | |
| const imgData = canvas.toDataURL('image/png'); | |
| const pdf = new jsPDF('p', 'mm', 'a4'); | |
| const imgWidth = 190; | |
| const pageHeight = 280; | |
| const imgHeight = (canvas.height * imgWidth) / canvas.width; | |
| let heightLeft = imgHeight; | |
| let position = 10; | |
| pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight); | |
| heightLeft -= pageHeight; | |
| // Add multiple pages if content is too long | |
| while (heightLeft >= 0) { | |
| position = heightLeft - imgHeight + 10; | |
| pdf.addPage(); | |
| pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight); | |
| heightLeft -= pageHeight; | |
| } | |
| pdf.save('html-screenshot.pdf'); | |
| showTemporaryMessage('PDF saved successfully!'); | |
| }).catch(error => { | |
| console.error('Error generating PDF:', error); | |
| showTemporaryMessage('Error generating PDF. Using text fallback.'); | |
| saveAsPDF(); // Fallback to text version | |
| }); | |
| } | |
| // Update word count as user types | |
| document.getElementById('htmlInput').addEventListener('input', updateWordCount); | |
| document.getElementById('htmlInput').addEventListener('input', () => { | |
| saveToHistory(); | |
| updatePDFPreview(); | |
| }); | |
| // Handle clicks outside of editable elements | |
| document.addEventListener('click', (e) => { | |
| if (!e.target.classList.contains('editable') && | |
| !e.target.classList.contains('ai-icon') && | |
| editingElement) { | |
| finishEditing(editingElement); | |
| } | |
| // Close history panel when clicking elsewhere | |
| if (!e.target.closest('.history-dropdown')) { | |
| document.getElementById('historyPanel').classList.remove('active'); | |
| } | |
| }); | |
| // Handle Enter key in AI input | |
| document.getElementById('aiInput').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| sendAIMessage(); | |
| } | |
| }); | |
| // Handle Escape key to close AI assistant | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 'Escape') { | |
| closeAIAssistant(); | |
| } | |
| // Undo/Redo with Ctrl+Z and Ctrl+Y | |
| if (e.ctrlKey || e.metaKey) { | |
| if (e.key === 'z') { | |
| e.preventDefault(); | |
| undoChange(); | |
| } else if (e.key === 'y') { | |
| e.preventDefault(); | |
| redoChange(); | |
| } | |
| } | |
| }); | |
| // Toggle history panel | |
| document.getElementById('undoBtn').addEventListener('click', (e) => { | |
| if (history.length > 0) { | |
| const panel = document.getElementById('historyPanel'); | |
| panel.classList.toggle('active'); | |
| e.stopPropagation(); | |
| } | |
| }); | |
| // Initialize PDF options | |
| selectPDFOption('text'); | |
| </script> | |
| </body> | |
| </html> |