// CLAUDE.md Manager View // Three-column layout: File Tree | Viewer/Editor | Metadata & Actions // ========== State Management ========== var claudeFilesData = { user: { main: null }, project: { main: null }, modules: [], summary: { totalFiles: 0, totalSize: 0 } }; var selectedFile = null; var isEditMode = false; var isDirty = false; var fileTreeExpanded = { user: true, project: true, modules: {} }; var searchQuery = ''; // ========== Main Render Function ========== async function renderClaudeManager() { var container = document.getElementById('mainContent'); if (!container) return; // Hide stats grid and search for claude-manager view var statsGrid = document.getElementById('statsGrid'); var searchInput = document.getElementById('searchInput'); if (statsGrid) statsGrid.style.display = 'none'; if (searchInput) searchInput.parentElement.style.display = 'none'; // Show loading state container.innerHTML = '
' + '
' + '

' + t('common.loading') + '

' + '
'; // Load data await loadClaudeFiles(); // Render layout container.innerHTML = '
' + '
' + '
' + '

' + t('claudeManager.title') + '

' + '' + claudeFilesData.summary.totalFiles + ' ' + t('claudeManager.files') + '' + '
' + '
' + '' + '' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
'; // Render each column renderFileTree(); renderFileViewer(); renderFileMetadata(); // Initialize Lucide icons if (window.lucide) lucide.createIcons(); } // ========== Data Loading ========== async function loadClaudeFiles() { try { var res = await fetch('/api/memory/claude/scan?path=' + encodeURIComponent(projectPath || '')); if (!res.ok) throw new Error('Failed to load CLAUDE.md files'); claudeFilesData = await res.json(); updateClaudeBadge(); // Update navigation badge } catch (error) { console.error('Error loading CLAUDE.md files:', error); addGlobalNotification('error', t('claudeManager.loadError'), null, 'CLAUDE.md'); } } async function refreshClaudeFiles() { await loadClaudeFiles(); await renderClaudeManager(); addGlobalNotification('success', t('claudeManager.refreshed'), null, 'CLAUDE.md'); } // ========== File Tree Rendering ========== function renderFileTree() { var container = document.getElementById('claude-file-tree'); if (!container) return; var html = '
' + // Search Box '' + renderClaudeFilesTree() + '
'; // end file-tree container.innerHTML = html; if (window.lucide) lucide.createIcons(); } function renderClaudeFilesTree() { var html = '
' + '
' + '' + '' + '' + t('claudeManager.userLevel') + '' + '' + (claudeFilesData.user.main ? 1 : 0) + '' + '
'; if (fileTreeExpanded.user) { // User CLAUDE.md (only main file, no rules) if (claudeFilesData.user.main) { html += renderFileTreeItem(claudeFilesData.user.main, 1); } else { html += '
' + '' + '' + t('claudeManager.noFile') + '' + '
'; } } html += '
'; // end user section // Project section html += '
' + '
' + '' + '' + '' + t('claudeManager.projectLevel') + '' + '' + (claudeFilesData.project.main ? 1 : 0) + '' + '
'; if (fileTreeExpanded.project) { // Project CLAUDE.md (only main file, no rules) if (claudeFilesData.project.main) { html += renderFileTreeItem(claudeFilesData.project.main, 1); } else { html += '
' + '' + '' + t('claudeManager.noFile') + '' + '
'; } } html += '
'; // end project section // Modules section html += '
' + '
' + '' + '' + t('claudeManager.moduleLevel') + '' + '' + claudeFilesData.modules.length + '' + '
'; if (claudeFilesData.modules.length > 0) { claudeFilesData.modules.forEach(function (file) { html += renderFileTreeItem(file, 1); }); } else { html += '
' + '' + '' + t('claudeManager.noModules') + '' + '
'; } html += '
'; // end modules section return html; } function renderFileTreeItem(file, indentLevel) { var isSelected = selectedFile && selectedFile.id === file.id; var indentPx = indentLevel * 1.5; var safeId = file.id.replace(/'/g, "'"); return '
' + '' + '' + escapeHtml(file.name) + '' + (file.parentDirectory ? '' + escapeHtml(file.parentDirectory) + '' : '') + '
'; } function toggleTreeSection(section) { fileTreeExpanded[section] = !fileTreeExpanded[section]; renderFileTree(); } async function selectClaudeFile(fileId) { // Find file in data (only main CLAUDE.md files, no rules) var allFiles = [ claudeFilesData.user.main, claudeFilesData.project.main, ...claudeFilesData.modules ].filter(function (f) { return f !== null; }); selectedFile = allFiles.find(function (f) { return f.id === fileId; }) || null; if (selectedFile) { // Load full content if not already loaded if (!selectedFile.content) { try { var res = await fetch('/api/memory/claude/file?path=' + encodeURIComponent(selectedFile.path)); if (res.ok) { var data = await res.json(); selectedFile.content = data.content; selectedFile.stats = data.stats; } } catch (error) { console.error('Error loading file content:', error); } } } renderFileTree(); renderFileViewer(); renderFileMetadata(); } // ========== File Viewer Rendering ========== function renderFileViewer() { var container = document.getElementById('claude-file-viewer'); if (!container) return; if (!selectedFile) { container.innerHTML = '
' + '' + '

' + t('claudeManager.selectFile') + '

' + '
'; if (window.lucide) lucide.createIcons(); return; } container.innerHTML = '
' + '
' + '

' + escapeHtml(selectedFile.name) + '

' + '
' + '' + '' + '
' + '
' + '
' + (isEditMode ? renderEditor() : renderMarkdownContent(selectedFile.content || '')) + '
' + '
'; if (window.lucide) lucide.createIcons(); } function renderMarkdownContent(content) { // Check if marked.js is available for enhanced rendering if (typeof marked !== 'undefined') { try { marked.setOptions({ gfm: true, breaks: true, tables: true, smartLists: true, highlight: function(code, lang) { // Check if highlight.js or Prism is available if (typeof hljs !== 'undefined' && lang) { try { return hljs.highlight(code, { language: lang }).value; } catch (e) { return escapeHtml(code); } } else if (typeof Prism !== 'undefined' && lang && Prism.languages[lang]) { return Prism.highlight(code, Prism.languages[lang], lang); } return escapeHtml(code); } }); return '
' + marked.parse(content) + '
'; } catch (e) { console.error('Error rendering markdown with marked.js:', e); } } // Fallback: Enhanced basic rendering var html = escapeHtml(content); // Headers html = html .replace(/^# (.*$)/gim, '

$1

') .replace(/^## (.*$)/gim, '

$1

') .replace(/^### (.*$)/gim, '

$1

') .replace(/^#### (.*$)/gim, '

$1

'); // Inline formatting html = html .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/`([^`]+)`/g, '$1'); // Links html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); // Task lists html = html .replace(/- \[ \] (.+)$/gim, '
  • $1
  • ') .replace(/- \[x\] (.+)$/gim, '
  • $1
  • '); // Lists html = html.replace(/^- (.+)$/gim, '
  • $1
  • '); html = html.replace(/(
  • .*<\/li>)/s, ''); // Code blocks html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, function(match, lang, code) { return '
    ' + code + '
    '; }); // Line breaks html = html.replace(/\n/g, '
    '); return '
    ' + html + '
    '; } function renderEditor() { return ''; } function toggleEditMode() { if (isEditMode && isDirty) { if (!confirm(t('claudeManager.unsavedChanges'))) { return; } } isEditMode = !isEditMode; isDirty = false; renderFileViewer(); } function markDirty() { isDirty = true; } async function saveClaudeFile() { if (!selectedFile || !isEditMode) return; var editor = document.getElementById('claudeFileEditor'); if (!editor) return; var newContent = editor.value; try { var res = await fetch('/api/memory/claude/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: selectedFile.path, content: newContent, createBackup: true }) }); if (!res.ok) throw new Error('Failed to save file'); selectedFile.content = newContent; selectedFile.stats = calculateFileStats(newContent); isDirty = false; addGlobalNotification('success', t('claudeManager.saved'), null, 'CLAUDE.md'); renderFileMetadata(); } catch (error) { console.error('Error saving file:', error); addGlobalNotification('error', t('claudeManager.saveError'), null, 'CLAUDE.md'); } } function calculateFileStats(content) { var lines = content.split('\n').length; var words = content.split(/\s+/).filter(function (w) { return w.length > 0; }).length; var characters = content.length; return { lines: lines, words: words, characters: characters }; } // ========== File Metadata Rendering ========== function renderFileMetadata() { var container = document.getElementById('claude-file-metadata'); if (!container) return; if (!selectedFile) { container.innerHTML = '
    ' + '' + '

    ' + t('claudeManager.noMetadata') + '

    ' + '
    '; if (window.lucide) lucide.createIcons(); return; } var html = '
    ' + ''; if (selectedFile.stats) { html += ''; } html += ''; // end actions section // CLI Sync Panel html += ''; // end cli-sync-panel html += '
    '; // end file-metadata container.innerHTML = html; if (window.lucide) lucide.createIcons(); } // ========== CLI Sync Functions ========== async function syncFileWithCLI() { if (!selectedFile) return; var tool = document.getElementById('cliToolSelect').value; var mode = document.getElementById('cliModeSelect').value; // Show progress showSyncProgress(true, tool); // Disable sync button var syncButton = document.getElementById('cliSyncButton'); if (syncButton) syncButton.disabled = true; try { var response = await fetch('/api/memory/claude/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ level: selectedFile.level, path: selectedFile.level === 'module' ? selectedFile.path.replace(/CLAUDE\.md$/, '').replace(/\/$/, '') : undefined, tool: tool, mode: mode }) }); var result = await response.json(); if (result.success) { // Reload file content var fileData = await loadFileContent(selectedFile.path); if (fileData) { selectedFile = fileData; renderFileViewer(); renderFileMetadata(); } showClaudeNotification('success', (t('claude.syncSuccess') || 'Synced successfully').replace('{file}', selectedFile.name)); } else { showClaudeNotification('error', (t('claude.syncError') || 'Sync failed').replace('{error}', result.error || 'Unknown error')); } } catch (error) { console.error('CLI sync error:', error); showClaudeNotification('error', (t('claude.syncError') || 'Sync failed').replace('{error}', error.message)); } finally { showSyncProgress(false); if (syncButton) syncButton.disabled = false; } } function showSyncProgress(show, tool) { var progressEl = document.getElementById('syncProgress'); var progressText = document.getElementById('syncProgressText'); if (!progressEl) return; if (show) { progressEl.style.display = 'flex'; if (progressText) { var text = (t('claude.syncing') || 'Analyzing with {tool}...').replace('{tool}', tool || 'CLI'); progressText.textContent = text; } if (window.lucide) lucide.createIcons(); } else { progressEl.style.display = 'none'; } } async function loadFileContent(filePath) { try { var res = await fetch('/api/memory/claude/file?path=' + encodeURIComponent(filePath)); if (!res.ok) return null; return await res.json(); } catch (error) { console.error('Error loading file content:', error); return null; } } function showClaudeNotification(type, message) { // Use global notification system if available if (typeof addGlobalNotification === 'function') { addGlobalNotification(type, message, null, 'CLAUDE.md'); } else { // Fallback to simple alert alert(message); } } // ========== Search Functions ========== function filterFileTree(query) { searchQuery = query.toLowerCase(); renderFileTree(); // Add keyboard shortcut handler if (query && !window.claudeSearchKeyboardHandlerAdded) { document.addEventListener('keydown', handleSearchKeyboard); window.claudeSearchKeyboardHandlerAdded = true; } } function handleSearchKeyboard(e) { // Ctrl+F or Cmd+F if ((e.ctrlKey || e.metaKey) && e.key === 'f') { e.preventDefault(); var searchInput = document.getElementById('fileSearchInput'); if (searchInput) { searchInput.focus(); searchInput.select(); } } } // ========== File Creation Functions ========== function showCreateFileDialog() { var dialog = ''; document.body.insertAdjacentHTML('beforeend', dialog); if (window.lucide) lucide.createIcons(); } function closeCreateDialog() { var overlay = document.querySelector('.modal-overlay'); if (overlay) overlay.remove(); } function toggleModulePathInput(level) { var pathLabel = document.getElementById('modulePathLabel'); var pathInput = document.getElementById('modulePath'); if (level === 'module') { pathLabel.style.display = 'block'; pathInput.style.display = 'block'; } else { pathLabel.style.display = 'none'; pathInput.style.display = 'none'; } } async function createNewFile() { var level = document.getElementById('createLevel').value; var template = document.getElementById('createTemplate').value; var modulePath = document.getElementById('modulePath').value; if (level === 'module' && !modulePath) { addGlobalNotification('error', t('claude.modulePathRequired') || 'Module path is required', null, 'CLAUDE.md'); return; } try { var res = await fetch('/api/memory/claude/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ level: level, path: modulePath || undefined, template: template }) }); if (!res.ok) throw new Error('Failed to create file'); var result = await res.json(); closeCreateDialog(); addGlobalNotification('success', t('claude.fileCreated') || 'File created successfully', null, 'CLAUDE.md'); // Refresh file tree await refreshClaudeFiles(); } catch (error) { console.error('Error creating file:', error); addGlobalNotification('error', t('claude.createFileError') || 'Failed to create file', null, 'CLAUDE.md'); } } // ========== File Deletion Functions ========== async function confirmDeleteFile() { if (!selectedFile) return; var confirmed = confirm( (t('claude.deleteConfirm') || 'Are you sure you want to delete {file}?').replace('{file}', selectedFile.name) + '\n\n' + 'Path: ' + selectedFile.path + '\n\n' + (t('claude.deleteWarning') || 'This action cannot be undone.') ); if (!confirmed) return; try { var res = await fetch('/api/memory/claude/file?path=' + encodeURIComponent(selectedFile.path) + '&confirm=true', { method: 'DELETE' }); if (!res.ok) throw new Error('Failed to delete file'); addGlobalNotification('success', t('claude.fileDeleted') || 'File deleted successfully', null, 'CLAUDE.md'); selectedFile = null; // Refresh file tree await refreshClaudeFiles(); } catch (error) { console.error('Error deleting file:', error); addGlobalNotification('error', t('claude.deleteFileError') || 'Failed to delete file', null, 'CLAUDE.md'); } } // ========== Copy Content Function ========== function copyFileContent() { if (!selectedFile || !selectedFile.content) return; navigator.clipboard.writeText(selectedFile.content).then(function() { addGlobalNotification('success', t('claude.contentCopied') || 'Content copied to clipboard', null, 'CLAUDE.md'); }).catch(function(error) { console.error('Error copying content:', error); addGlobalNotification('error', t('claude.copyError') || 'Failed to copy content', null, 'CLAUDE.md'); }); } // ========== Utility Functions ========== // Note: escapeHtml and formatDate are imported from utils.js function formatFileSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; } // Update navigation badge with total file count function updateClaudeBadge() { var badge = document.getElementById('badgeClaude'); if (badge && claudeFilesData && claudeFilesData.summary) { var total = claudeFilesData.summary.totalFiles; badge.textContent = total; } }