feat: Implement CodeMirror 6 file editor with tab support

Implement Phase 1 of the file editor & chat UI redesign:
- CodeMirror 6 integration with syntax highlighting
- Multi-file tab support with dirty state tracking
- Custom dark theme matching GitHub's color scheme
- Keyboard shortcuts (Ctrl+S to save, Ctrl+W to close tab)
- Mobile-responsive design with proper touch targets
- Fallback to basic textarea if CodeMirror fails to load

Technical details:
- Import map for ESM modules from node_modules
- Language support for JS, Python, HTML, CSS, JSON, Markdown
- Auto-initialization on DOM ready
- Global window.fileEditor instance for integration
- Serve node_modules at /claude/node_modules for import map

Files added:
- public/claude-ide/components/file-editor.js (main component)
- public/claude-ide/components/file-editor.css (responsive styles)

Files modified:
- public/claude-ide/index.html (import map, script tags)
- public/claude-ide/ide.js (updated loadFile function)
- server.js (serve node_modules for CodeMirror)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
uroma
2026-01-21 08:49:01 +00:00
Unverified
parent 9e445bf653
commit b765c537fc
5 changed files with 1185 additions and 49 deletions

View File

@@ -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 = `
<div class="file-header">
<h2>${filePath}</h2>
<div class="file-actions">
<button class="btn-secondary btn-sm" onclick="editFile('${filePath}')">Edit</button>
<button class="btn-primary btn-sm" onclick="showHtmlPreview('${filePath}')">👁️ Preview</button>
</div>
</div>
<div class="file-content" id="file-content-view">
<div class="view-toggle">
<button class="toggle-btn active" data-view="code" onclick="switchFileView('code')">Code</button>
<button class="toggle-btn" data-view="preview" onclick="switchFileView('preview')">Preview</button>
</div>
<div class="code-view">
<pre><code class="language-html">${escapeHtml(data.content)}</code></pre>
</div>
<div class="preview-view" style="display: none;">
<iframe id="html-preview-frame" sandbox="allow-scripts allow-same-origin allow-forms"></iframe>
</div>
</div>
`;
// 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 = `
<div class="file-header">
<h2>${filePath}</h2>
<div class="file-actions">
<button class="btn-secondary btn-sm" onclick="editFile('${filePath}')">Edit</button>
// 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 = `
<div class="file-header">
<h2>${filePath}</h2>
<div class="file-actions">
<button class="btn-secondary btn-sm" onclick="editFile('${filePath}')">Edit</button>
<button class="btn-primary btn-sm" onclick="showHtmlPreview('${filePath}')">👁️ Preview</button>
</div>
</div>
</div>
<div class="file-content">
<div class="markdown-body">${data.html}</div>
</div>
`;
<div class="file-content" id="file-content-view">
<div class="view-toggle">
<button class="toggle-btn active" data-view="code" onclick="switchFileView('code')">Code</button>
<button class="toggle-btn" data-view="preview" onclick="switchFileView('preview')">Preview</button>
</div>
<div class="code-view">
<pre><code class="language-html">${escapeHtml(data.content)}</code></pre>
</div>
<div class="preview-view" style="display: none;">
<iframe id="html-preview-frame" sandbox="allow-scripts allow-same-origin allow-forms"></iframe>
</div>
</div>
`;
// 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 = `
<div class="file-header">
<h2>${filePath}</h2>
</div>
<div class="file-content">
<pre><code>${escapeHtml(data.content || '')}</code></pre>
</div>
`;
}
}
} catch (error) {
console.error('Error loading file:', error);
const editorEl = document.getElementById('file-editor');
if (editorEl) {
editorEl.innerHTML = `
<div class="file-error">
<h2>Error loading file</h2>
<p>${error.message}</p>
</div>
`;
}
}
}