// 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 = '';
// 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 = '' +
'';
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 += '' +
'';
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 += '' +
'';
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 = '' +
'' +
'
' +
(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 = ''; // 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 = '' +
'
' +
'
' + t('claude.createDialogTitle') + ' ' +
'
' +
'' + t('claude.selectLevel') + ' ' +
'' +
'' + t('claude.levelUser') + ' ' +
'' + t('claude.levelProject') + ' ' +
'' + t('claude.levelModule') + ' ' +
' ' +
'' + t('claude.modulePath') + ' ' +
' ' +
'' + t('claude.selectTemplate') + ' ' +
'' +
'' + t('claude.templateDefault') + ' ' +
'' + t('claude.templateMinimal') + ' ' +
'' + t('claude.templateComprehensive') + ' ' +
' ' +
'
' +
'
' +
'' + t('common.cancel') + ' ' +
'' + t('claude.createFile') + ' ' +
'
' +
'
' +
'
';
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;
}
}