mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-09 02:24:11 +08:00
Add initial implementation of the Docsify shell template for interactive software manuals
This commit is contained in:
@@ -91,6 +91,71 @@
|
||||
color: hsl(var(--warning-foreground, white));
|
||||
}
|
||||
|
||||
/* ===== Search Box ===== */
|
||||
.cli-stream-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
background: hsl(var(--background));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.cli-stream-search:focus-within {
|
||||
border-color: hsl(var(--primary));
|
||||
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.1);
|
||||
}
|
||||
|
||||
.cli-stream-search-input {
|
||||
width: 140px;
|
||||
padding: 2px 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.cli-stream-search-input::placeholder {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.cli-stream-search-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cli-stream-search-clear {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.cli-stream-search:focus-within .cli-stream-search-clear,
|
||||
.cli-stream-search-input:not(:placeholder-shown) + .cli-stream-search-clear {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.cli-stream-search-clear:hover {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.cli-stream-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -334,6 +399,26 @@
|
||||
color: hsl(200 80% 70%);
|
||||
}
|
||||
|
||||
/* Search highlight */
|
||||
.cli-stream-highlight {
|
||||
background: hsl(50 100% 50% / 0.4);
|
||||
color: inherit;
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* Filter result info */
|
||||
.cli-stream-filter-info {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
margin-bottom: 8px;
|
||||
background: hsl(var(--primary) / 0.15);
|
||||
color: hsl(var(--primary));
|
||||
border-radius: 4px;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Auto-scroll indicator */
|
||||
.cli-stream-scroll-btn {
|
||||
position: sticky;
|
||||
|
||||
@@ -8,6 +8,7 @@ let cliStreamExecutions = {}; // { executionId: { tool, mode, output, status, s
|
||||
let activeStreamTab = null;
|
||||
let autoScrollEnabled = true;
|
||||
let isCliStreamViewerOpen = false;
|
||||
let searchFilter = ''; // Search filter for output content
|
||||
|
||||
const MAX_OUTPUT_LINES = 5000; // Prevent memory issues
|
||||
|
||||
@@ -230,9 +231,9 @@ function renderStreamTabs() {
|
||||
function renderStreamContent(executionId) {
|
||||
const contentContainer = document.getElementById('cliStreamContent');
|
||||
if (!contentContainer) return;
|
||||
|
||||
|
||||
const exec = executionId ? cliStreamExecutions[executionId] : null;
|
||||
|
||||
|
||||
if (!exec) {
|
||||
// Show empty state
|
||||
contentContainer.innerHTML = `
|
||||
@@ -245,20 +246,43 @@ function renderStreamContent(executionId) {
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check if should auto-scroll
|
||||
const wasAtBottom = contentContainer.scrollHeight - contentContainer.scrollTop <= contentContainer.clientHeight + 50;
|
||||
|
||||
// Render output lines
|
||||
contentContainer.innerHTML = exec.output.map(line =>
|
||||
`<div class="cli-stream-line ${line.type}">${escapeHtml(line.content)}</div>`
|
||||
).join('');
|
||||
|
||||
|
||||
// Filter output lines based on search
|
||||
let filteredOutput = exec.output;
|
||||
if (searchFilter.trim()) {
|
||||
const searchLower = searchFilter.toLowerCase();
|
||||
filteredOutput = exec.output.filter(line =>
|
||||
line.content.toLowerCase().includes(searchLower)
|
||||
);
|
||||
}
|
||||
|
||||
// Render output lines with search highlighting
|
||||
contentContainer.innerHTML = filteredOutput.map(line => {
|
||||
let content = escapeHtml(line.content);
|
||||
// Highlight search matches
|
||||
if (searchFilter.trim()) {
|
||||
const searchRegex = new RegExp(`(${escapeRegex(searchFilter)})`, 'gi');
|
||||
content = content.replace(searchRegex, '<mark class="cli-stream-highlight">$1</mark>');
|
||||
}
|
||||
return `<div class="cli-stream-line ${line.type}">${content}</div>`;
|
||||
}).join('');
|
||||
|
||||
// Show filter result count if filtering
|
||||
if (searchFilter.trim() && filteredOutput.length !== exec.output.length) {
|
||||
const filterInfo = document.createElement('div');
|
||||
filterInfo.className = 'cli-stream-filter-info';
|
||||
filterInfo.textContent = `${filteredOutput.length} / ${exec.output.length} lines`;
|
||||
contentContainer.insertBefore(filterInfo, contentContainer.firstChild);
|
||||
}
|
||||
|
||||
// Auto-scroll if enabled and was at bottom
|
||||
if (autoScrollEnabled && wasAtBottom) {
|
||||
contentContainer.scrollTop = contentContainer.scrollHeight;
|
||||
}
|
||||
|
||||
|
||||
// Update status bar
|
||||
renderStreamStatus(executionId);
|
||||
}
|
||||
@@ -408,14 +432,14 @@ function handleStreamContentScroll() {
|
||||
// ===== Helper Functions =====
|
||||
function formatDuration(ms) {
|
||||
if (ms < 1000) return `${ms}ms`;
|
||||
|
||||
|
||||
const seconds = Math.floor(ms / 1000);
|
||||
if (seconds < 60) return `${seconds}s`;
|
||||
|
||||
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
|
||||
|
||||
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const remainingMinutes = minutes % 60;
|
||||
return `${hours}h ${remainingMinutes}m`;
|
||||
@@ -428,6 +452,25 @@ function escapeHtml(text) {
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function escapeRegex(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
// ===== Search Functions =====
|
||||
function handleSearchInput(event) {
|
||||
searchFilter = event.target.value;
|
||||
renderStreamContent(activeStreamTab);
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
searchFilter = '';
|
||||
const searchInput = document.getElementById('cliStreamSearchInput');
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
renderStreamContent(activeStreamTab);
|
||||
}
|
||||
|
||||
// Translation helper with fallback (uses global t from i18n.js)
|
||||
function _streamT(key) {
|
||||
// First try global t() from i18n.js
|
||||
@@ -459,3 +502,16 @@ if (document.readyState === 'loading') {
|
||||
} else {
|
||||
initCliStreamViewer();
|
||||
}
|
||||
|
||||
// ===== Global Exposure =====
|
||||
window.toggleCliStreamViewer = toggleCliStreamViewer;
|
||||
window.handleCliStreamStarted = handleCliStreamStarted;
|
||||
window.handleCliStreamOutput = handleCliStreamOutput;
|
||||
window.handleCliStreamCompleted = handleCliStreamCompleted;
|
||||
window.handleCliStreamError = handleCliStreamError;
|
||||
window.switchStreamTab = switchStreamTab;
|
||||
window.closeStream = closeStream;
|
||||
window.clearCompletedStreams = clearCompletedStreams;
|
||||
window.toggleAutoScroll = toggleAutoScroll;
|
||||
window.handleSearchInput = handleSearchInput;
|
||||
window.clearSearch = clearSearch;
|
||||
|
||||
Reference in New Issue
Block a user