diff --git a/public/claude-ide/components/file-editor.css b/public/claude-ide/components/file-editor.css new file mode 100644 index 00000000..c846e75b --- /dev/null +++ b/public/claude-ide/components/file-editor.css @@ -0,0 +1,421 @@ +/** + * File Editor Component Styles + * Mobile-first responsive design for CodeMirror 6 editor + */ + +/* === File Editor Container === */ +.file-editor-container { + display: flex; + flex-direction: column; + height: 100%; + background: #0d1117; + color: #c9d1d9; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + overflow: hidden; +} + +/* === Editor Header (Tabs + Actions) === */ +.editor-header { + display: flex; + align-items: center; + justify-content: space-between; + background: #161b22; + border-bottom: 1px solid #30363d; + padding: 0; + min-height: 40px; +} + +.editor-tabs { + display: flex; + align-items: center; + flex: 1; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: thin; + scrollbar-color: #484f58 #161b22; +} + +.editor-tabs::-webkit-scrollbar { + height: 8px; +} + +.editor-tabs::-webkit-scrollbar-track { + background: #161b22; +} + +.editor-tabs::-webkit-scrollbar-thumb { + background: #484f58; + border-radius: 4px; +} + +.editor-tabs::-webkit-scrollbar-thumb:hover { + background: #6e7681; +} + +.editor-actions { + display: flex; + align-items: center; + padding: 0 8px; + gap: 4px; + border-left: 1px solid #30363d; +} + +/* === Editor Tabs === */ +.editor-tab { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + background: transparent; + border: none; + border-right: 1px solid #30363d; + cursor: pointer; + font-size: 13px; + color: #8b949e; + transition: background 0.15s ease, color 0.15s ease; + white-space: nowrap; + user-select: none; + min-width: fit-content; +} + +.editor-tab:hover { + background: #21262d; + color: #c9d1d9; +} + +.editor-tab.active { + background: #0d1117; + color: #c9d1d9; + border-top: 2px solid #58a6ff; +} + +.editor-tab.dirty .tab-name { + color: #e3b341; +} + +.editor-tab.dirty .tab-dirty-indicator { + color: #e3b341; +} + +.tab-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; +} + +.tab-dirty-indicator { + font-size: 10px; + animation: pulse 2s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.tab-close { + display: flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + padding: 0; + background: transparent; + border: none; + color: #8b949e; + cursor: pointer; + border-radius: 3px; + font-size: 16px; + line-height: 1; + transition: all 0.15s ease; +} + +.tab-close:hover { + background: #484f58; + color: #c9d1d9; +} + +/* === Editor Content Area === */ +.editor-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + position: relative; +} + +/* === CodeMirror Editor Instance === */ +.cm-editor-instance { + height: 100%; + width: 100%; + overflow: hidden; +} + +/* === Editor Placeholder === */ +.editor-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + color: #484f58; + text-align: center; + padding: 2rem; +} + +.placeholder-icon { + font-size: 4rem; + margin-bottom: 1rem; + opacity: 0.5; +} + +.editor-placeholder h2 { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: 0.5rem; + color: #8b949e; +} + +.editor-placeholder p { + font-size: 1rem; + color: #484f58; +} + +/* === Fallback Editor (when CodeMirror fails) === */ +.fallback-editor { + width: 100%; + height: 100%; + background: #0d1117; + color: #c9d1d9; + border: none; + outline: none; + resize: none; + font-family: 'Fira Code', 'JetBrains Mono', 'SF Mono', 'Menlo', 'Consolas', monospace; + font-size: 14px; + line-height: 1.6; + padding: 12px; +} + +/* === Action Buttons === */ +.btn-icon { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + background: transparent; + border: none; + color: #8b949e; + cursor: pointer; + border-radius: 4px; + font-size: 16px; + transition: all 0.15s ease; +} + +.btn-icon:hover { + background: #21262d; + color: #c9d1d9; +} + +.btn-icon:active { + transform: scale(0.95); +} + +/* === CodeMirror Customization === */ +.codemirror-editor { + height: 100%; +} + +.codemirror-editor .cm-scroller { + font-family: 'Fira Code', 'JetBrains Mono', 'SF Mono', 'Menlo', 'Consolas', monospace; + font-size: 14px; + line-height: 1.6; +} + +.codemirror-editor .cm-content { + padding: 12px 0; +} + +.codemirror-editor .cm-line { + padding: 0 12px; +} + +/* === Mobile Responsive === */ +@media (max-width: 640px) { + .editor-header { + flex-direction: column; + align-items: stretch; + } + + .editor-tabs { + border-right: none; + border-bottom: 1px solid #30363d; + } + + .editor-actions { + border-left: none; + border-top: 1px solid #30363d; + padding: 4px; + justify-content: center; + } + + .editor-tab { + padding: 10px 8px; + font-size: 12px; + } + + .tab-name { + max-width: 120px; + } + + .tab-close { + width: 28px; + height: 28px; + font-size: 18px; + } + + .btn-icon { + width: 36px; + height: 36px; + font-size: 18px; + } + + .editor-placeholder h2 { + font-size: 1.25rem; + } + + .editor-placeholder p { + font-size: 0.875rem; + } +} + +/* === Tablet Responsive === */ +@media (min-width: 641px) and (max-width: 1024px) { + .tab-name { + max-width: 150px; + } +} + +/* === Touch Targets (Mobile) === */ +@media (hover: none) and (pointer: coarse) { + .editor-tab { + padding: 12px; + min-height: 44px; + } + + .tab-close { + width: 44px; + height: 44px; + } + + .btn-icon { + width: 44px; + height: 44px; + } +} + +/* === Dark Mode Scrollbar for Editor === */ +.cm-editor-instance ::-webkit-scrollbar { + width: 14px; + height: 14px; +} + +.cm-editor-instance ::-webkit-scrollbar-track { + background: #0d1117; +} + +.cm-editor-instance ::-webkit-scrollbar-thumb { + background: #30363d; + border-radius: 7px; + border: 3px solid #0d1117; +} + +.cm-editor-instance ::-webkit-scrollbar-thumb:hover { + background: #484f58; +} + +.cm-editor-instance ::-webkit-scrollbar-corner { + background: #0d1117; +} + +/* === Status Messages === */ +.status-message { + font-size: 12px; + padding: 4px 8px; + border-radius: 4px; + animation: fadeIn 0.2s ease; +} + +.status-success { + color: #3fb950; + background: rgba(63, 185, 80, 0.1); +} + +.status-error { + color: #f85149; + background: rgba(248, 81, 73, 0.1); +} + +.status-info { + color: #58a6ff; + background: rgba(88, 166, 255, 0.1); +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-2px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* === Loading Spinner === */ +.loading-spinner { + width: 40px; + height: 40px; + border: 3px solid #30363d; + border-top-color: #58a6ff; + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin: 2rem auto; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* === No Files State === */ +.no-tabs { + padding: 8px 12px; + color: #484f58; + font-size: 13px; + font-style: italic; +} + +/* === Focus Styles for Accessibility === */ +.editor-tab:focus-visible, +.tab-close:focus-visible, +.btn-icon:focus-visible { + outline: 2px solid #58a6ff; + outline-offset: 2px; +} + +/* === Print Styles === */ +@media print { + .editor-header, + .editor-actions { + display: none; + } + + .editor-content { + height: auto; + overflow: visible; + } +} diff --git a/public/claude-ide/components/file-editor.js b/public/claude-ide/components/file-editor.js new file mode 100644 index 00000000..ab25e06e --- /dev/null +++ b/public/claude-ide/components/file-editor.js @@ -0,0 +1,663 @@ +/** + * File Editor with CodeMirror 6 + * Supports: Multi-file tabs, syntax highlighting, dirty state tracking + */ + +import { EditorState, Compartment } from '@codemirror/state'; +import { EditorView, keymap, highlightSpecialChars, drawSelection, dropCursor, lineNumbers, rectangularSelection, crosshairCursor, highlightActiveLine, highlightSelectionMatches, EditorView as cmEditorView } from '@codemirror/view'; +import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'; +import { searchKeymap, highlightSelectionMatches as searchHighlightMatches } from '@codemirror/search'; +import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete'; +import { bracketMatching, codeFolding, foldGutter, syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language'; +import { tags } from '@lezer/highlight'; + +// Language support +import { javascript } from '@codemirror/lang-javascript'; +import { python } from '@codemirror/lang-python'; +import { html } from '@codemirror/lang-html'; +import { css } from '@codemirror/lang-css'; +import { json } from '@codemirror/lang-json'; +import { markdown } from '@codemirror/lang-markdown'; + +// Custom dark theme matching GitHub's dark theme +const customDarkTheme = cmEditorView.theme({ + '&': { + backgroundColor: '#0d1117', + color: '#c9d1d9', + fontSize: '14px', + fontFamily: "'Fira Code', 'JetBrains Mono', 'SF Mono', 'Menlo', 'Consolas', monospace" + }, + '.cm-scroller': { + fontFamily: 'inherit', + overflow: 'auto' + }, + '.cm-content': { + padding: '12px 0', + minHeight: '100%' + }, + '.cm-line': { + padding: '0 12px' + }, + '.cm-gutters': { + backgroundColor: '#0d1117', + color: '#484f58', + border: 'none' + }, + '.cm-activeLineGutter': { + backgroundColor: 'transparent', + color: '#c9d1d9' + }, + '.cm-activeLine': { + backgroundColor: '#161b22' + }, + '.cm-focused': { + outline: 'none' + }, + '.cm-selectionBackground': { + backgroundColor: '#264f78' + }, + '&.cm-focused .cm-selectionBackground': { + backgroundColor: '#264f78' + }, + '.cm-selectionMatch': { + backgroundColor: '#264f7855' + }, + '.cm-cursor': { + borderLeftColor: '#58a6ff' + }, + '.cm-foldPlaceholder': { + backgroundColor: 'transparent', + border: 'none', + color: '#484f58' + }, + '.cm-tooltip': { + border: '1px solid #30363d', + backgroundColor: '#161b22', + boxShadow: '0 4px 12px rgba(0, 0, 0, 0.5)' + }, + '.cm-tooltip-autocomplete': { + '& > ul': { + maxHeight: '200px', + fontFamily: 'inherit', + '& > li': { + padding: '4px 8px', + '&[aria-selected]': { + backgroundColor: '#1f6feb', + color: '#ffffff' + } + } + } + } +}, { + dark: true +}); + +// State compartments for dynamic reconfiguration +const languageCompartment = new Compartment(); +const tabSizeCompartment = new Compartment(); + +/** + * Get language extension based on file extension + */ +function getLanguageExtension(filePath) { + const ext = filePath.split('.').pop().toLowerCase(); + + const languageMap = { + 'js': javascript(), + 'jsx': javascript({ jsx: true }), + 'ts': javascript({ typescript: true }), + 'tsx': javascript({ typescript: true, jsx: true }), + 'mjs': javascript(), + 'cjs': javascript(), + 'py': python(), + 'html': html(), + 'htm': html(), + 'css': css(), + 'scss': css(), + 'sass': css(), + 'json': json(), + 'md': markdown(), + 'markdown': markdown(), + 'txt': null + }; + + return languageMap[ext] || javascript(); +} + +/** + * Create CodeMirror editor state + */ +function createEditorState(filePath, content, onChange) { + const language = getLanguageExtension(filePath); + + return EditorState.create({ + doc: content || '', + extensions: [ + lineNumbers(), + highlightSpecialChars(), + history(), + foldGutter(), + drawSelection(), + dropCursor(), + codeFolding(), + bracketMatching(), + closeBrackets(), + autocompletion(), + rectangularSelection(), + crosshairCursor(), + highlightActiveLine(), + highlightSelectionMatches(), + syntaxHighlighting(defaultHighlightStyle, { fallback: true }), + keymap.of([ + ...closeBracketsKeymap, + ...defaultKeymap, + ...searchKeymap, + ...historyKeymap, + ...completionKeymap, + { + key: 'Mod-s', + run: () => { + // Trigger save - will be handled by the component + const event = new CustomEvent('editor-save', { bubbles: true }); + document.dispatchEvent(event); + return true; + } + }, + { + key: 'Mod-w', + run: (view) => { + // Trigger close tab - will be handled by the component + const event = new CustomEvent('editor-close-tab', { bubbles: true }); + document.dispatchEvent(event); + return true; + } + } + ]), + customDarkTheme, + languageCompartment.of(language || []), + tabSizeCompartment.of(EditorState.tabSize.of(4)), + cmEditorView.updateListener.of((update) => { + if (update.docChanged) { + // Notify that content changed + if (onChange) onChange(); + } + }) + ] + }); +} + +/** + * FileEditor Class + */ +class FileEditor { + constructor(container) { + this.container = container; + this.editors = new Map(); // tabId -> EditorView + this.activeTab = null; + this.tabs = []; + this.nextTabId = 1; + + this.initialize(); + } + + initialize() { + // Create editor container structure + this.container.innerHTML = ` +
+
+
+ +
+
+ + +
+
+
+
+
📄
+

No file open

+

Select a file from the sidebar to start editing

+
+
+
+ `; + + // Set up event listeners + this.setupEventListeners(); + } + + setupEventListeners() { + // Save all button + const saveAllBtn = this.container.querySelector('#btn-save-all'); + if (saveAllBtn) { + saveAllBtn.addEventListener('click', () => this.saveAllFiles()); + } + + // Close all button + const closeAllBtn = this.container.querySelector('#btn-close-all'); + if (closeAllBtn) { + closeAllBtn.addEventListener('click', () => this.closeAllTabs()); + } + + // Keyboard shortcuts + document.addEventListener('editor-save', () => this.saveCurrentFile()); + document.addEventListener('editor-close-tab', () => this.closeCurrentTab()); + + // Handle window resize + window.addEventListener('resize', () => this.refreshActiveEditor()); + } + + /** + * Open a file in the editor + */ + async openFile(filePath, content) { + // Check if file is already open + const existingTab = this.tabs.find(tab => tab.path === filePath); + if (existingTab) { + this.activateTab(existingTab.id); + return; + } + + // Create new tab + const tabId = `tab-${this.nextTabId++}`; + const tab = { + id: tabId, + path: filePath, + name: filePath.split('/').pop(), + dirty: false, + originalContent: content || '' + }; + + this.tabs.push(tab); + + // Create CodeMirror editor instance + const editorContainer = document.createElement('div'); + editorContainer.className = 'cm-editor-instance'; + editorContainer.style.display = 'none'; + + const contentArea = this.container.querySelector('#editor-content'); + + // Remove placeholder if it exists + const placeholder = contentArea.querySelector('.editor-placeholder'); + if (placeholder) { + placeholder.remove(); + } + + contentArea.appendChild(editorContainer); + + const state = createEditorState(filePath, content || '', () => { + this.markDirty(tabId); + }); + + const editor = new EditorView({ + state: state, + parent: editorContainer + }); + + this.editors.set(tabId, editor); + + // Activate the new tab + this.activateTab(tabId); + + return tabId; + } + + /** + * Activate a tab + */ + activateTab(tabId) { + if (!this.editors.has(tabId)) { + console.error('[FileEditor] Tab not found:', tabId); + return; + } + + // Hide all editors + this.editors.forEach((editor, id) => { + const container = editor.dom.parentElement; + if (container) { + container.style.display = id === tabId ? 'block' : 'none'; + } + }); + + this.activeTab = tabId; + this.renderTabs(); + + // Refresh the active editor to ensure proper rendering + setTimeout(() => this.refreshActiveEditor(), 10); + } + + /** + * Close a tab + */ + async closeTab(tabId) { + const tab = this.tabs.find(t => t.id === tabId); + if (!tab) return; + + // Check for unsaved changes + if (tab.dirty) { + const shouldSave = confirm(`Save changes to ${tab.name} before closing?`); + if (shouldSave) { + await this.saveFile(tabId); + } + } + + // Destroy editor + const editor = this.editors.get(tabId); + if (editor) { + editor.destroy(); + this.editors.delete(tabId); + } + + // Remove tab from list + this.tabs = this.tabs.filter(t => t.id !== tabId); + + // If we closed the active tab, activate another one + if (this.activeTab === tabId) { + if (this.tabs.length > 0) { + this.activateTab(this.tabs[0].id); + } else { + this.activeTab = null; + this.showPlaceholder(); + } + } + + this.renderTabs(); + } + + /** + * Close current tab + */ + closeCurrentTab() { + if (this.activeTab) { + this.closeTab(this.activeTab); + } + } + + /** + * Close all tabs + */ + async closeAllTabs() { + if (this.tabs.length === 0) return; + + const hasUnsaved = this.tabs.some(t => t.dirty); + if (hasUnsaved) { + const shouldSaveAll = confirm('Some files have unsaved changes. Save all before closing?'); + if (shouldSaveAll) { + await this.saveAllFiles(); + } + } + + // Destroy all editors + this.editors.forEach(editor => editor.destroy()); + this.editors.clear(); + + this.tabs = []; + this.activeTab = null; + + this.renderTabs(); + this.showPlaceholder(); + } + + /** + * Save a file + */ + async saveFile(tabId) { + const tab = this.tabs.find(t => t.id === tabId); + if (!tab) return; + + const editor = this.editors.get(tabId); + if (!editor) return; + + const content = editor.state.doc.toString(); + + try { + const response = await fetch(`/claude/api/file/${encodeURIComponent(tab.path)}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content }) + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + if (data.error) { + throw new Error(data.error); + } + + // Update tab state + tab.dirty = false; + tab.originalContent = content; + + this.renderTabs(); + + // Show success toast + if (typeof showToast === 'function') { + showToast(`✅ Saved ${tab.name}`, 'success', 2000); + } + + return true; + } catch (error) { + console.error('[FileEditor] Error saving file:', error); + if (typeof showToast === 'function') { + showToast(`❌ Failed to save ${tab.name}: ${error.message}`, 'error', 3000); + } + return false; + } + } + + /** + * Save current file + */ + async saveCurrentFile() { + if (this.activeTab) { + await this.saveFile(this.activeTab); + } + } + + /** + * Save all files + */ + async saveAllFiles() { + const dirtyTabs = this.tabs.filter(t => t.dirty); + + if (dirtyTabs.length === 0) { + if (typeof showToast === 'function') { + showToast('No unsaved changes', 'info', 2000); + } + return; + } + + let saved = 0; + let failed = 0; + + for (const tab of dirtyTabs) { + const result = await this.saveFile(tab.id); + if (result) { + saved++; + } else { + failed++; + } + } + + if (typeof showToast === 'function') { + if (failed === 0) { + showToast(`✅ Saved ${saved} file${saved > 1 ? 's' : ''}`, 'success', 2000); + } else { + showToast(`⚠️ Saved ${saved} file${saved > 1 ? 's' : ''}, ${failed} failed`, 'warning', 3000); + } + } + } + + /** + * Mark tab as dirty (unsaved changes) + */ + markDirty(tabId) { + const tab = this.tabs.find(t => t.id === tabId); + if (tab && !tab.dirty) { + tab.dirty = true; + this.renderTabs(); + } + } + + /** + * Check if any tab is dirty + */ + hasUnsavedChanges() { + return this.tabs.some(t => t.dirty); + } + + /** + * Get current file content + */ + getCurrentContent() { + if (!this.activeTab) return null; + + const editor = this.editors.get(this.activeTab); + return editor ? editor.state.doc.toString() : null; + } + + /** + * Get current file path + */ + getCurrentFilePath() { + if (!this.activeTab) return null; + + const tab = this.tabs.find(t => t.id === this.activeTab); + return tab ? tab.path : null; + } + + /** + * Refresh active editor + */ + refreshActiveEditor() { + if (!this.activeTab) return; + + const editor = this.editors.get(this.activeTab); + if (editor) { + editor.requestMeasure(); + } + } + + /** + * Show placeholder when no files are open + */ + showPlaceholder() { + const contentArea = this.container.querySelector('#editor-content'); + if (contentArea) { + contentArea.innerHTML = ` +
+
📄
+

No file open

+

Select a file from the sidebar to start editing

+
+ `; + } + } + + /** + * Render tabs + */ + renderTabs() { + const tabsContainer = this.container.querySelector('#editor-tabs'); + if (!tabsContainer) return; + + if (this.tabs.length === 0) { + tabsContainer.innerHTML = ''; + return; + } + + tabsContainer.innerHTML = this.tabs.map(tab => ` +
+ ${this.escapeHtml(tab.name)} + ${tab.dirty ? '' : ''} + +
+ `).join(''); + + // Tab click handlers + tabsContainer.querySelectorAll('.editor-tab').forEach(tabEl => { + tabEl.addEventListener('click', (e) => { + if (!e.target.classList.contains('tab-close')) { + this.activateTab(tabEl.dataset.tabId); + } + }); + + const closeBtn = tabEl.querySelector('.tab-close'); + if (closeBtn) { + closeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this.closeTab(tabEl.dataset.tabId); + }); + } + }); + } + + /** + * Escape HTML to prevent XSS + */ + escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + /** + * Get tab count + */ + getTabCount() { + return this.tabs.length; + } + + /** + * Get dirty tab count + */ + getDirtyTabCount() { + return this.tabs.filter(t => t.dirty).length; + } + + /** + * Destroy editor and cleanup + */ + destroy() { + // Destroy all editors + this.editors.forEach(editor => editor.destroy()); + this.editors.clear(); + + this.tabs = []; + this.activeTab = null; + } +} + +// Export for use in other scripts +if (typeof module !== 'undefined' && module.exports) { + module.exports = { FileEditor }; +} + +// Export to window for use from non-module scripts +if (typeof window !== 'undefined') { + window.FileEditor = FileEditor; + console.log('[FileEditor] Component loaded and available globally'); +} + +// Auto-initialize when DOM is ready +if (typeof document !== 'undefined') { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + // Create global file editor instance + if (!window.fileEditor) { + window.fileEditor = new FileEditor(document.getElementById('file-editor')); + console.log('[FileEditor] Auto-initialized on DOMContentLoaded'); + } + }); + } else { + // DOM is already ready + if (!window.fileEditor) { + window.fileEditor = new FileEditor(document.getElementById('file-editor')); + console.log('[FileEditor] Auto-initialized (DOM already ready)'); + } + } +} diff --git a/public/claude-ide/ide.js b/public/claude-ide/ide.js index 7a009b66..b25121f3 100644 --- a/public/claude-ide/ide.js +++ b/public/claude-ide/ide.js @@ -855,60 +855,74 @@ async function loadFile(filePath) { const res = await fetch(`/claude/api/file/${encodeURIComponent(filePath)}`); const data = await res.json(); - const isHtmlFile = filePath.toLowerCase().endsWith('.html') || filePath.toLowerCase().endsWith('.htm'); - - const editorEl = document.getElementById('file-editor'); - - if (isHtmlFile) { - // HTML file - show with preview option - editorEl.innerHTML = ` -
-

${filePath}

-
- - -
-
-
-
- - -
-
-
${escapeHtml(data.content)}
-
- -
- `; - - // Store file content for preview - window.currentFileContent = data.content; - window.currentFilePath = filePath; - - // Highlight code - if (window.hljs) { - document.querySelectorAll('#file-content-view pre code').forEach((block) => { - hljs.highlightElement(block); - }); - } + // Check if FileEditor component is available + if (window.fileEditor) { + // Use the new CodeMirror-based editor + await window.fileEditor.openFile(filePath, data.content || ''); } else { - // Non-HTML file - show as before - editorEl.innerHTML = ` -
-

${filePath}

-
- + // Fallback to the old view if FileEditor is not loaded yet + console.warn('[loadFile] FileEditor not available, using fallback'); + const editorEl = document.getElementById('file-editor'); + + const isHtmlFile = filePath.toLowerCase().endsWith('.html') || filePath.toLowerCase().endsWith('.htm'); + + if (isHtmlFile) { + // HTML file - show with preview option + editorEl.innerHTML = ` +
+

${filePath}

+
+ + +
-
-
-
${data.html}
-
- `; +
+
+ + +
+
+
${escapeHtml(data.content)}
+
+ +
+ `; + + // Store file content for preview + window.currentFileContent = data.content; + window.currentFilePath = filePath; + + // Highlight code + if (window.hljs) { + document.querySelectorAll('#file-content-view pre code').forEach((block) => { + hljs.highlightElement(block); + }); + } + } else { + // Non-HTML file - show content + editorEl.innerHTML = ` +
+

${filePath}

+
+
+
${escapeHtml(data.content || '')}
+
+ `; + } } } catch (error) { console.error('Error loading file:', error); + const editorEl = document.getElementById('file-editor'); + if (editorEl) { + editorEl.innerHTML = ` +
+

Error loading file

+

${error.message}

+
+ `; + } } } diff --git a/public/claude-ide/index.html b/public/claude-ide/index.html index 6fc7441e..44974ac1 100644 --- a/public/claude-ide/index.html +++ b/public/claude-ide/index.html @@ -10,7 +10,41 @@ + + + +
@@ -344,6 +378,7 @@ +