mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-09 02:24:11 +08:00
- Introduced auto and manual triggering mechanisms for Exa. - Added quick reference guides for sed and awk text processing. - Established a fallback strategy for handling edit failures.
154 lines
4.6 KiB
JavaScript
154 lines
4.6 KiB
JavaScript
// ========================================
|
|
// Utility Functions
|
|
// ========================================
|
|
// General-purpose helper functions used across the application
|
|
|
|
// ========== HTML/Text Processing ==========
|
|
|
|
/**
|
|
* Escape HTML special characters to prevent XSS attacks
|
|
* @param {string} str - String to escape
|
|
* @returns {string} Escaped string safe for HTML insertion
|
|
*/
|
|
function escapeHtml(str) {
|
|
if (typeof str !== 'string') return str;
|
|
return str
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
/**
|
|
* Truncate text to specified maximum length
|
|
* @param {string} text - Text to truncate
|
|
* @param {number} maxLen - Maximum length (including ellipsis)
|
|
* @returns {string} Truncated text with '...' if needed
|
|
*/
|
|
function truncateText(text, maxLen) {
|
|
if (!text) return '';
|
|
return text.length > maxLen ? text.substring(0, maxLen - 3) + '...' : text;
|
|
}
|
|
|
|
/**
|
|
* Normalize line endings in content
|
|
* Handles both literal \r\n escape sequences and actual newlines
|
|
* @param {string} content - Content to normalize
|
|
* @returns {string} Content with normalized line endings (LF only)
|
|
*/
|
|
function normalizeLineEndings(content) {
|
|
if (!content) return '';
|
|
let normalized = content;
|
|
// If content has literal \r\n or \n as text (escaped), convert to actual newlines
|
|
if (normalized.includes('\\r\\n')) {
|
|
normalized = normalized.replace(/\\r\\n/g, '\n');
|
|
} else if (normalized.includes('\\n')) {
|
|
normalized = normalized.replace(/\\n/g, '\n');
|
|
}
|
|
// Normalize CRLF to LF for consistent rendering
|
|
normalized = normalized.replace(/\r\n/g, '\n');
|
|
return normalized;
|
|
}
|
|
|
|
// ========== Date/Time Formatting ==========
|
|
|
|
/**
|
|
* Format ISO date string to human-readable format
|
|
* @param {string} dateStr - ISO date string
|
|
* @returns {string} Formatted date string (YYYY/MM/DD HH:mm) or '-' if invalid
|
|
*/
|
|
function formatDate(dateStr) {
|
|
if (!dateStr) return '-';
|
|
try {
|
|
const date = new Date(dateStr);
|
|
// Check if date is valid
|
|
if (isNaN(date.getTime())) return '-';
|
|
// Format: YYYY/MM/DD HH:mm
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
return `${year}/${month}/${day} ${hours}:${minutes}`;
|
|
} catch (e) {
|
|
return '-';
|
|
}
|
|
}
|
|
|
|
// ========== UI Helpers ==========
|
|
|
|
/**
|
|
* Get color for relevance score visualization
|
|
* @param {number} score - Relevance score (0-1)
|
|
* @returns {string} CSS color value
|
|
*/
|
|
function getRelevanceColor(score) {
|
|
if (score >= 0.95) return '#10b981';
|
|
if (score >= 0.90) return '#3b82f6';
|
|
if (score >= 0.80) return '#f59e0b';
|
|
return '#6b7280';
|
|
}
|
|
|
|
/**
|
|
* Get CSS class for role badge styling
|
|
* @param {string} role - Role identifier
|
|
* @returns {string} CSS class name
|
|
*/
|
|
function getRoleBadgeClass(role) {
|
|
const roleMap = {
|
|
'core-hook': 'primary',
|
|
'api-client': 'success',
|
|
'api-router': 'info',
|
|
'service-layer': 'warning',
|
|
'pydantic-schemas': 'secondary',
|
|
'orm-model': 'secondary',
|
|
'typescript-types': 'info'
|
|
};
|
|
return roleMap[role] || 'secondary';
|
|
}
|
|
|
|
/**
|
|
* Toggle collapsible section visibility
|
|
* @param {HTMLElement} header - Section header element
|
|
*/
|
|
function toggleSection(header) {
|
|
const content = header.nextElementSibling;
|
|
const icon = header.querySelector('.collapse-icon');
|
|
const isCollapsed = content.classList.contains('collapsed');
|
|
|
|
content.classList.toggle('collapsed');
|
|
header.classList.toggle('expanded');
|
|
icon.textContent = isCollapsed ? '▼' : '▶';
|
|
|
|
// Render flowchart if expanding flow_control section
|
|
if (isCollapsed && header.querySelector('.section-label')?.textContent === 'flow_control') {
|
|
const taskId = content.closest('[data-task-id]')?.dataset.taskId;
|
|
if (taskId) {
|
|
const task = taskJsonStore[taskId];
|
|
if (task?.flow_control) {
|
|
setTimeout(() => renderFullFlowchart(task.flow_control), 100);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize collapsible sections within a container
|
|
* @param {HTMLElement} container - Container element to search within
|
|
*/
|
|
function initCollapsibleSections(container) {
|
|
setTimeout(() => {
|
|
const headers = container.querySelectorAll('.collapsible-header');
|
|
headers.forEach(header => {
|
|
if (!header._clickBound) {
|
|
header._clickBound = true;
|
|
header.addEventListener('click', function(e) {
|
|
e.stopPropagation();
|
|
toggleSection(this);
|
|
});
|
|
}
|
|
});
|
|
}, 100);
|
|
}
|