feat: implement elegant slide-in floating control panel for edit mode
Replaced the intrusive blue status bar with a sleek slide-in control panel: UI/UX Improvements: - Minimized ribbon (📝 icon) on top-right that slides out to full panel - Beautiful gradient design with backdrop blur effects - Smooth CSS transitions with cubic-bezier easing - Auto-expand on load, then minimize after 2 seconds - Click outside to close, click ribbon to toggle Features Combined: - Status indicators with dynamic icons (⏳ loading, ✅ success, ❌ error) - Save & Download and Preview buttons in clean grid layout - Version information in panel header - Error reporting with expandable details - Responsive design for mobile devices Technical Changes: - Replaced old floating-header with integrated control panel - Enhanced status update function with visual state management - Added toggle functionality with click-outside-to-close - Improved typography and spacing throughout - Updated test to match new element ID structure This provides a much cleaner editing experience with better space utilization while maintaining all previous functionality and adding visual polish. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -396,35 +396,245 @@ class DocumentManager:
|
|||||||
body_classes = ' class="markitect-edit-mode"'
|
body_classes = ' class="markitect-edit-mode"'
|
||||||
editor_css = """
|
editor_css = """
|
||||||
<style>
|
<style>
|
||||||
.markitect-floating-header {
|
/* Floating Control Panel - Slide-in from right */
|
||||||
|
.markitect-control-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 20px;
|
||||||
left: 0;
|
right: -320px;
|
||||||
right: 0;
|
width: 320px;
|
||||||
background: rgba(255, 255, 255, 0.95);
|
background: rgba(255, 255, 255, 0.98);
|
||||||
border-bottom: 1px solid #ddd;
|
border: 1px solid #e0e0e0;
|
||||||
padding: 10px;
|
border-radius: 12px 0 0 12px;
|
||||||
|
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.15);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(10px);
|
||||||
|
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markitect-control-panel.expanded {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Control ribbon - always visible */
|
||||||
|
.markitect-control-ribbon {
|
||||||
|
position: absolute;
|
||||||
|
left: -40px;
|
||||||
|
top: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(135deg, #2196f3, #1976d2);
|
||||||
|
border-radius: 8px 0 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-control-ribbon:hover {
|
||||||
|
background: linear-gradient(135deg, #1976d2, #1565c0);
|
||||||
|
transform: translateX(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panel content */
|
||||||
|
.markitect-panel-header {
|
||||||
|
background: linear-gradient(135deg, #2196f3, #1976d2);
|
||||||
|
color: white;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-radius: 12px 0 0 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-panel-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-panel-version {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-panel-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 16px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-panel-close:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-panel-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-status-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-status-indicator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-left: 4px solid #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-status-indicator.loading {
|
||||||
|
border-left-color: #ff9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-status-indicator.error {
|
||||||
|
border-left-color: #f44336;
|
||||||
|
background: #ffebee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-status-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-status-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-controls-section {
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-controls-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-control-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #2196f3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-control-btn:hover {
|
||||||
|
background: #1976d2;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-control-btn.secondary {
|
||||||
|
background: #757575;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-control-btn.secondary:hover {
|
||||||
|
background: #616161;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-control-btn .icon {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-save-info {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-error-details {
|
||||||
|
display: none;
|
||||||
|
background: #ffebee;
|
||||||
|
border: 1px solid #f44336;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-error-title {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #c62828;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-error-text {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markitect-error-help {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content editing styles */
|
||||||
.markitect-section-editable {
|
.markitect-section-editable {
|
||||||
border: 1px dashed transparent;
|
border: 1px dashed transparent;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markitect-section-editable:hover {
|
.markitect-section-editable:hover {
|
||||||
border-color: #007acc;
|
border-color: #007acc;
|
||||||
background: rgba(0, 122, 204, 0.05);
|
background: rgba(0, 122, 204, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-mode textarea {
|
.edit-mode textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
font-family: monospace;
|
font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace;
|
||||||
border: 2px solid #007acc;
|
border: 2px solid #007acc;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
padding: 8px;
|
padding: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-mode textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1976d2;
|
||||||
|
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.markitect-control-panel {
|
||||||
|
width: 280px;
|
||||||
|
right: -280px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>"""
|
</style>"""
|
||||||
|
|
||||||
@@ -445,19 +655,19 @@ class DocumentManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
initializeEditor() {
|
initializeEditor() {
|
||||||
const header = document.createElement('div');
|
// Control panel is already in HTML, just make content editable
|
||||||
header.className = 'markitect-floating-header';
|
|
||||||
header.innerHTML = `
|
|
||||||
<button onclick="markitectEditor.save()" title="Download edited file with timestamp">💾 Save & Download</button>
|
|
||||||
<button onclick="markitectEditor.togglePreview()" title="Toggle preview mode">👁️ Preview</button>
|
|
||||||
<span id="save-status" style="margin-left: 15px; font-size: 0.9em;">Ready</span>
|
|
||||||
<span style="margin-left: 15px; font-size: 0.8em; color: #666;">
|
|
||||||
Saves as: filename-edited-YYYY-MM-DD-HH-MM-SS.md
|
|
||||||
</span>
|
|
||||||
`;
|
|
||||||
document.body.insertBefore(header, document.body.firstChild);
|
|
||||||
|
|
||||||
this.makeContentEditable();
|
this.makeContentEditable();
|
||||||
|
|
||||||
|
// Auto-expand control panel briefly to show it's available
|
||||||
|
setTimeout(() => {
|
||||||
|
const panel = document.getElementById('markitect-control-panel');
|
||||||
|
if (panel) {
|
||||||
|
panel.classList.add('expanded');
|
||||||
|
setTimeout(() => {
|
||||||
|
panel.classList.remove('expanded');
|
||||||
|
}, 2000); // Show for 2 seconds then minimize
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeContentEditable() {
|
makeContentEditable() {
|
||||||
@@ -613,7 +823,25 @@ class DocumentManager:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let markitectEditor;"""
|
let markitectEditor;
|
||||||
|
|
||||||
|
// Control panel toggle functionality
|
||||||
|
function toggleControlPanel() {
|
||||||
|
const panel = document.getElementById('markitect-control-panel');
|
||||||
|
if (panel) {
|
||||||
|
panel.classList.toggle('expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-close panel when clicking outside
|
||||||
|
document.addEventListener('click', function(event) {
|
||||||
|
const panel = document.getElementById('markitect-control-panel');
|
||||||
|
if (panel && panel.classList.contains('expanded')) {
|
||||||
|
if (!panel.contains(event.target)) {
|
||||||
|
panel.classList.remove('expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});"""
|
||||||
|
|
||||||
# Edit mode status and error reporting section
|
# Edit mode status and error reporting section
|
||||||
edit_mode_html = ""
|
edit_mode_html = ""
|
||||||
@@ -692,20 +920,58 @@ class DocumentManager:
|
|||||||
version_info = "0.3.0"
|
version_info = "0.3.0"
|
||||||
|
|
||||||
edit_mode_html = f"""
|
edit_mode_html = f"""
|
||||||
<div id="markitect-status" style="background: #e3f2fd; border-left: 4px solid #2196f3; padding: 12px; margin-bottom: 20px; font-family: monospace; font-size: 14px;">
|
<!-- Floating Control Panel -->
|
||||||
<div style="font-weight: bold; color: #1976d2;">📝 Markitect Edit Mode <span style="font-weight: normal; color: #666;">v{version_info}</span></div>
|
<div id="markitect-control-panel" class="markitect-control-panel">
|
||||||
<div id="status-message" style="margin-top: 8px;">Loading edit capabilities...</div>
|
<!-- Control Ribbon - Always Visible -->
|
||||||
<div id="error-details" style="display: none; background: #ffebee; border: 1px solid #f44336; padding: 8px; margin-top: 8px; border-radius: 4px;">
|
<div class="markitect-control-ribbon" onclick="toggleControlPanel()" title="MarkiTect Editor Controls">
|
||||||
<div style="font-weight: bold; color: #c62828;">❌ Edit Mode Failed</div>
|
📝
|
||||||
<div id="error-text" style="margin-top: 4px; color: #666;"></div>
|
</div>
|
||||||
<details style="margin-top: 8px;">
|
|
||||||
<summary style="cursor: pointer; color: #1976d2;">🐛 Help us fix this issue</summary>
|
<!-- Panel Header -->
|
||||||
<div style="margin-top: 8px; font-size: 12px; color: #666;">
|
<div class="markitect-panel-header">
|
||||||
Please report this error with your browser info:
|
<h3 class="markitect-panel-title">📝 MarkiTect Editor</h3>
|
||||||
<br>📋 Browser: <span id="browser-info"></span>
|
<p class="markitect-panel-version">v{version_info}</p>
|
||||||
<br>🔗 Create issue: <a href="https://github.com/anthropics/markitect/issues/new" target="_blank" style="color: #1976d2;">GitHub Issues</a>
|
<button class="markitect-panel-close" onclick="toggleControlPanel()" title="Close panel">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Panel Body -->
|
||||||
|
<div class="markitect-panel-body">
|
||||||
|
<!-- Status Section -->
|
||||||
|
<div class="markitect-status-section">
|
||||||
|
<div id="status-indicator" class="markitect-status-indicator loading">
|
||||||
|
<span class="markitect-status-icon">⏳</span>
|
||||||
|
<div class="markitect-status-text" id="status-message">Loading edit capabilities...</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
|
||||||
|
<!-- Error Details (hidden by default) -->
|
||||||
|
<div id="error-details" class="markitect-error-details">
|
||||||
|
<div class="markitect-error-title">❌ Edit Mode Failed</div>
|
||||||
|
<div class="markitect-error-text" id="error-text"></div>
|
||||||
|
<div class="markitect-error-help">
|
||||||
|
📋 Browser: <span id="browser-info"></span><br>
|
||||||
|
🔗 <a href="https://github.com/anthropics/markitect/issues/new" target="_blank" style="color: #1976d2;">Report Issue</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Controls Section -->
|
||||||
|
<div class="markitect-controls-section">
|
||||||
|
<div class="markitect-controls-grid">
|
||||||
|
<button class="markitect-control-btn" onclick="markitectEditor.save()" title="Download edited content">
|
||||||
|
<span class="icon">💾</span>
|
||||||
|
Save & Download
|
||||||
|
</button>
|
||||||
|
<button class="markitect-control-btn secondary" onclick="markitectEditor.togglePreview()" title="Toggle preview mode">
|
||||||
|
<span class="icon">👁️</span>
|
||||||
|
Preview
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="markitect-save-info">
|
||||||
|
<div id="save-status">Ready to save</div>
|
||||||
|
<div style="margin-top: 4px; opacity: 0.8;">Saves as: filename-edited-YYYY-MM-DD-HH-MM-SS.md</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>"""
|
</div>"""
|
||||||
|
|
||||||
@@ -786,9 +1052,27 @@ class DocumentManager:
|
|||||||
// Status update utility
|
// Status update utility
|
||||||
function updateStatus(message, isError = false) {{
|
function updateStatus(message, isError = false) {{
|
||||||
const statusMsg = document.getElementById('status-message');
|
const statusMsg = document.getElementById('status-message');
|
||||||
|
const statusIndicator = document.getElementById('status-indicator');
|
||||||
|
const statusIcon = document.querySelector('.markitect-status-icon');
|
||||||
|
|
||||||
if (statusMsg) {{
|
if (statusMsg) {{
|
||||||
statusMsg.textContent = message;
|
statusMsg.textContent = message;
|
||||||
statusMsg.style.color = isError ? '#c62828' : '#1976d2';
|
}}
|
||||||
|
|
||||||
|
if (statusIndicator) {{
|
||||||
|
// Remove all status classes
|
||||||
|
statusIndicator.classList.remove('loading', 'error');
|
||||||
|
|
||||||
|
if (isError) {{
|
||||||
|
statusIndicator.classList.add('error');
|
||||||
|
if (statusIcon) statusIcon.textContent = '❌';
|
||||||
|
}} else if (message.includes('Loading') || message.includes('Initializing')) {{
|
||||||
|
statusIndicator.classList.add('loading');
|
||||||
|
if (statusIcon) statusIcon.textContent = '⏳';
|
||||||
|
}} else {{
|
||||||
|
// Success state
|
||||||
|
if (statusIcon) statusIcon.textContent = '✅';
|
||||||
|
}}
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ class TestEditModeRegression:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Should contain error handling elements
|
# Should contain error handling elements
|
||||||
assert 'id="markitect-status"' in html_content
|
assert 'id="markitect-control-panel"' in html_content
|
||||||
assert 'id="status-message"' in html_content
|
assert 'id="status-message"' in html_content
|
||||||
assert 'id="error-details"' in html_content
|
assert 'id="error-details"' in html_content
|
||||||
assert 'reportEditModeError' in html_content
|
assert 'reportEditModeError' in html_content
|
||||||
|
|||||||
Reference in New Issue
Block a user