Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff6b807f3b | |||
| 6447c617fd | |||
| 5337b26d5e | |||
| 87e970bbee | |||
| eced5cbae4 | |||
| 6e60e5f13d | |||
| 93655512d0 | |||
| 74ee2760e2 | |||
| aefece1fe7 | |||
| ce7ce0470f | |||
| 6df6430b72 | |||
| ed27dee5a0 |
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,4 +1,36 @@
|
||||
# Changelog
|
||||
## [0.5.0] - 2025-10-26
|
||||
|
||||
### Added
|
||||
- **Clean TDD-Driven Editor Architecture**: Complete rewrite with object-oriented JavaScript architecture featuring Section, SectionManager, and DOMRenderer classes
|
||||
- **Enhanced Test Framework**: Comprehensive testing framework with clean separation of concerns for robust development
|
||||
- **Multiple Concurrent Section Editing**: Support for editing multiple sections simultaneously with intelligent management
|
||||
- **Intelligent Section Splitting**: Advanced heading detection and section management capabilities
|
||||
- **Four-Layer Content Management**: Sophisticated content state management (original, current, pending, editing layers)
|
||||
- **Enhanced Status Dialog**: Repository info display showing version, git commit status, and actual save filename
|
||||
- **Elegant Slide-in Control Panel**: Floating control panel for edit mode with improved UX
|
||||
- **Intelligent Auto-sizing Textarea**: Optimal editing experience with smart textarea resizing
|
||||
- **Enhanced Empty Line Preservation**: Better markdown structure preservation with automatic paragraph separation
|
||||
|
||||
### Fixed
|
||||
- **Textarea Sizing and Font Preservation**: Resolved sizing issues and maintained consistent font rendering
|
||||
- **Markdown Structure Preservation**: Fixed roundtrip formatting issues in save functionality
|
||||
- **Section Duplication Prevention**: Eliminated duplicate sections when saving edited content
|
||||
- **Section Position Preservation**: Prevented unwanted section jumping during editing
|
||||
- **CSS Embedding Issues**: Resolved import errors in HTML template generation
|
||||
- **Control Panel UX**: Hidden control ribbon when panel is expanded for cleaner interface
|
||||
|
||||
### Changed
|
||||
- **Action Semantics**: Proper implementation of Accept, Cancel, and Reset operations
|
||||
- **Global Reset Functionality**: Enhanced reset capabilities across the editor
|
||||
- **Makefile Organization**: Reorganized installation targets for better user experience
|
||||
|
||||
### Technical Improvements
|
||||
- Complete legacy editor system replacement
|
||||
- Test-driven development approach implementation
|
||||
- Enhanced UI/UX with better section positioning
|
||||
- Improved content management workflow
|
||||
|
||||
## [0.4.0] - 2025-10-25
|
||||
|
||||
### Added
|
||||
|
||||
69
Makefile
69
Makefile
@@ -1,7 +1,7 @@
|
||||
# MarkiTect - Advanced Markdown Engine
|
||||
# Makefile for common development tasks
|
||||
|
||||
.PHONY: help setup install-dev install-home install-home-venv install-deps install-deps-force install-deps-venv install-system list-deps setup-dev test build clean update status lint format check-deps venv-status update-digest add-diary-entry test-status test-new test-coverage test-arch test-foundation test-infrastructure test-integration test-domain test-service test-application test-presentation test-quick test-layers test-random test-random-seed test-random-repeat test-install-randomly test-clean test-tdd test-changed test-module test-cache-clean test-efficient cli-help release-status release-validate release-prepare release-build release-publish release-dry-run chaos-validate chaos-matrix chaos-inject chaos-report cost-help
|
||||
.PHONY: help setup install install-dev uninstall install-home install-home-venv install-user-deps install-force-deps install-deps-venv install-system-deps list-deps setup-dev test build clean update status lint format check-deps venv-status update-digest add-diary-entry test-status test-new test-coverage test-arch test-foundation test-infrastructure test-integration test-domain test-service test-application test-presentation test-quick test-layers test-random test-random-seed test-random-repeat test-install-randomly test-clean test-tdd test-changed test-module test-cache-clean test-efficient cli-help release-status release-validate release-prepare release-build release-publish release-dry-run chaos-validate chaos-matrix chaos-inject chaos-report cost-help
|
||||
|
||||
# Default target
|
||||
help:
|
||||
@@ -13,17 +13,17 @@ help:
|
||||
@echo ""
|
||||
@echo "Setup & Installation:"
|
||||
@echo " setup - Initial project setup (venv + install-dev)"
|
||||
@echo " install - Install markitect globally (recommended)"
|
||||
@echo " install-dev - Install package in development mode"
|
||||
@echo " install-home - Install markitect binary to ~/bin/"
|
||||
@echo " install-deps - Install dependencies (tries user-local first)"
|
||||
@echo " install-deps-force - Force install with --break-system-packages"
|
||||
@echo " install-deps-venv - Install to user virtual environment"
|
||||
@echo " install-home-venv - Install binary using user virtual environment"
|
||||
@echo " install-system - Install system dependencies via apt (requires sudo)"
|
||||
@echo " uninstall - Remove global markitect installation"
|
||||
@echo " list-deps - List required dependencies for markitect"
|
||||
@echo " setup-dev - Install with development dependencies"
|
||||
@echo " venv-status - Check if venv is active"
|
||||
@echo ""
|
||||
@echo "Advanced Installation:"
|
||||
@echo " install-user-deps - Install dependencies to user location only"
|
||||
@echo " install-system-deps - Install dependencies via system packages (sudo)"
|
||||
@echo " install-force-deps - Force install with --break-system-packages"
|
||||
@echo ""
|
||||
@echo "Development:"
|
||||
@echo " test - Run core tests (excluding capability-specific tests)"
|
||||
@echo " test-capabilities - Run all capability-specific tests"
|
||||
@@ -141,6 +141,40 @@ $(VENV)/bin/activate:
|
||||
$(PYTHON) -m venv $(VENV)
|
||||
$(VENV_PIP) install --upgrade pip setuptools wheel
|
||||
|
||||
# Install markitect globally (recommended approach)
|
||||
install:
|
||||
@echo "🚀 Installing MarkiTect globally..."
|
||||
@echo "📦 Creating user virtual environment with dependencies..."
|
||||
@$(MAKE) --no-print-directory install-deps-venv
|
||||
@echo "🏠 Installing markitect binary to ~/bin/..."
|
||||
@$(MAKE) --no-print-directory install-home-venv
|
||||
@echo ""
|
||||
@echo "✅ MarkiTect installed successfully!"
|
||||
@echo "💡 Make sure ~/bin is in your PATH:"
|
||||
@echo " export PATH=\"$$HOME/bin:$$PATH\""
|
||||
@echo ""
|
||||
@echo "🧪 Test installation:"
|
||||
@echo " markitect version"
|
||||
|
||||
# Remove global markitect installation
|
||||
uninstall:
|
||||
@echo "🗑️ Removing MarkiTect global installation..."
|
||||
@if [ -f "$$HOME/bin/markitect" ]; then \
|
||||
echo " Removing binary: $$HOME/bin/markitect"; \
|
||||
rm "$$HOME/bin/markitect"; \
|
||||
echo " ✅ Binary removed"; \
|
||||
else \
|
||||
echo " ℹ️ Binary not found at $$HOME/bin/markitect"; \
|
||||
fi
|
||||
@if [ -d "$$HOME/.local/markitect-venv" ]; then \
|
||||
echo " Removing virtual environment: $$HOME/.local/markitect-venv"; \
|
||||
rm -rf "$$HOME/.local/markitect-venv"; \
|
||||
echo " ✅ Virtual environment removed"; \
|
||||
else \
|
||||
echo " ℹ️ Virtual environment not found"; \
|
||||
fi
|
||||
@echo "✅ MarkiTect uninstalled successfully!"
|
||||
|
||||
# Install package in development mode
|
||||
install-dev: $(VENV)/bin/activate
|
||||
@echo "📦 Installing MarkiTect in development mode..."
|
||||
@@ -215,13 +249,14 @@ list-deps:
|
||||
@echo " toml - TOML configuration parsing"
|
||||
@echo ""
|
||||
@echo "🔧 Installation options:"
|
||||
@echo " make install-deps - Install user-local (recommended)"
|
||||
@echo " make install-system - Install via apt + pip --user (requires sudo)"
|
||||
@echo " make install - Install globally (recommended)"
|
||||
@echo " make install-user-deps - Install user-local dependencies only"
|
||||
@echo " make install-system-deps - Install via apt + pip --user (requires sudo)"
|
||||
@echo " pip3 install --user [packages] - Manual user-local installation"
|
||||
@echo " pip install -e . - Install from project directory (dev mode)"
|
||||
|
||||
# Install user-local dependencies for markitect (no sudo needed)
|
||||
install-deps:
|
||||
install-user-deps:
|
||||
@echo "📦 Installing MarkiTect dependencies (user-local)..."
|
||||
@echo "🐍 Target Python: $$(which python3) (version: $$(python3 --version))"
|
||||
@echo "📍 pip3 location: $$(which pip3)"
|
||||
@@ -233,12 +268,12 @@ install-deps:
|
||||
echo "❌ User-local installation failed (externally-managed-environment)"; \
|
||||
echo ""; \
|
||||
echo "🔧 Alternative solutions:"; \
|
||||
echo " 1. Use system packages: make install-system"; \
|
||||
echo " 2. Override restriction: make install-deps-force"; \
|
||||
echo " 1. Use system packages: make install-system-deps"; \
|
||||
echo " 2. Override restriction: make install-force-deps"; \
|
||||
echo " 3. Create user venv: make install-deps-venv"; \
|
||||
echo " 4. Use development setup: make setup"; \
|
||||
echo ""; \
|
||||
echo "💡 Recommended: Try 'make install-system' first"; \
|
||||
echo "💡 Recommended: Try 'make install' for complete setup"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "🧪 Testing import..."
|
||||
@@ -246,7 +281,7 @@ install-deps:
|
||||
@echo "💡 You can now use 'markitect' command if it's in your PATH"
|
||||
|
||||
# Force install user-local dependencies (overrides externally-managed restriction)
|
||||
install-deps-force:
|
||||
install-force-deps:
|
||||
@echo "📦 Force installing MarkiTect dependencies (overriding restrictions)..."
|
||||
@echo "⚠️ This uses --break-system-packages flag"
|
||||
@echo " Only use if you understand the implications"
|
||||
@@ -287,7 +322,7 @@ install-deps-venv:
|
||||
@echo "💡 To use this, run 'make install-home-venv' instead of 'make install-home'"
|
||||
|
||||
# Install system dependencies via apt (requires sudo)
|
||||
install-system:
|
||||
install-system-deps:
|
||||
@echo "📦 Installing MarkiTect dependencies via apt..."
|
||||
@echo "⚠️ This requires sudo and installs system packages"
|
||||
@echo ""
|
||||
@@ -313,7 +348,7 @@ install-system:
|
||||
echo "💡 You can now use 'markitect' command if it's in your PATH"; \
|
||||
else \
|
||||
echo "❌ Installation cancelled"; \
|
||||
echo "💡 Alternative: Use 'make install-deps' for user-local installation"; \
|
||||
echo "💡 Alternative: Use 'make install' for automated setup"; \
|
||||
fi
|
||||
|
||||
# Install with development dependencies
|
||||
|
||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Base version from pyproject.toml
|
||||
__version__ = "0.4.0"
|
||||
__version__ = "0.5.0"
|
||||
|
||||
def get_git_commit_hash() -> Optional[str]:
|
||||
"""Get the current git commit hash if available."""
|
||||
|
||||
1430
markitect/clean_document_manager.py
Normal file
1430
markitect/clean_document_manager.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -101,7 +101,7 @@ def detect_execution_mode():
|
||||
def should_use_associated_files():
|
||||
"""Determine if commands should use associated files behavior."""
|
||||
return detect_execution_mode() == 'interactive'
|
||||
from .document_manager import DocumentManager
|
||||
# DocumentManager removed - using CleanDocumentManager directly in commands
|
||||
from .serializer import ASTSerializer
|
||||
from .cache_service import CacheDirectoryService
|
||||
from .ast_service import ASTService
|
||||
|
||||
@@ -355,6 +355,7 @@ class DocumentManager:
|
||||
edit_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True) -> str:
|
||||
"""Generate HTML template with embedded markdown and client-side rendering."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Escape the markdown content for JavaScript
|
||||
js_markdown_content = json.dumps(markdown_content)
|
||||
@@ -395,35 +396,267 @@ class DocumentManager:
|
||||
body_classes = ' class="markitect-edit-mode"'
|
||||
editor_css = """
|
||||
<style>
|
||||
.markitect-floating-header {
|
||||
/* Floating Control Panel - Slide-in from right */
|
||||
.markitect-control-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
top: 20px;
|
||||
right: -320px;
|
||||
width: 320px;
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 12px 0 0 12px;
|
||||
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.15);
|
||||
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;
|
||||
}
|
||||
|
||||
.markitect-control-panel.expanded .markitect-control-ribbon {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 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, opacity 0.3s ease;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.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 {
|
||||
border: 1px dashed transparent;
|
||||
padding: 8px;
|
||||
margin: 4px 0;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.markitect-section-editable:hover {
|
||||
border-color: #007acc;
|
||||
background: rgba(0, 122, 204, 0.05);
|
||||
}
|
||||
|
||||
.markitect-section-editable[data-edited] {
|
||||
border-color: rgba(76, 175, 80, 0.3);
|
||||
background: rgba(76, 175, 80, 0.02);
|
||||
}
|
||||
|
||||
.markitect-section-editable[data-edited]:hover {
|
||||
border-color: #4caf50;
|
||||
background: rgba(76, 175, 80, 0.08);
|
||||
}
|
||||
|
||||
.edit-mode textarea {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
font-family: monospace;
|
||||
min-height: 60px;
|
||||
max-height: 360px;
|
||||
font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace;
|
||||
border: 2px solid #007acc;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
font-size: inherit; /* Will be overridden by JavaScript */
|
||||
line-height: inherit; /* Will be overridden by JavaScript */
|
||||
resize: both; /* Allow both horizontal and vertical resize */
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
transition: height 0.15s ease;
|
||||
min-width: 200px; /* Ensure minimum width */
|
||||
}
|
||||
|
||||
.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>"""
|
||||
|
||||
@@ -439,24 +672,25 @@ class DocumentManager:
|
||||
editor_scripts = """
|
||||
class MarkitectEditor {
|
||||
constructor() {
|
||||
this.hasEdits = false; // Track if any edits have been made
|
||||
this.initializeEditor();
|
||||
this.setupKeyboardShortcuts();
|
||||
}
|
||||
|
||||
initializeEditor() {
|
||||
const header = document.createElement('div');
|
||||
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);
|
||||
|
||||
// Control panel is already in HTML, just make content editable
|
||||
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() {
|
||||
@@ -468,10 +702,30 @@ class DocumentManager:
|
||||
}
|
||||
|
||||
markSections(element) {
|
||||
// Clear existing section markers (except edited ones)
|
||||
const existingSections = element.querySelectorAll('.markitect-section-editable:not([data-edited])');
|
||||
existingSections.forEach(section => {
|
||||
section.classList.remove('markitect-section-editable');
|
||||
section.removeAttribute('data-section');
|
||||
});
|
||||
|
||||
// Mark new sections (skip elements inside edited wrappers)
|
||||
const sections = element.querySelectorAll('h1, h2, h3, h4, h5, h6, p, blockquote, pre, ul, ol');
|
||||
sections.forEach((section, index) => {
|
||||
let sectionIndex = 0;
|
||||
|
||||
sections.forEach((section) => {
|
||||
// Skip if this element is inside an edited wrapper
|
||||
if (section.closest('[data-edited]')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if already marked as edited wrapper
|
||||
if (section.hasAttribute('data-edited')) {
|
||||
return;
|
||||
}
|
||||
|
||||
section.classList.add('markitect-section-editable');
|
||||
section.setAttribute('data-section', index);
|
||||
section.setAttribute('data-section', sectionIndex++);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -488,9 +742,90 @@ class DocumentManager:
|
||||
textarea.value = this.htmlToMarkdown(originalContent);
|
||||
textarea.className = 'edit-mode';
|
||||
|
||||
// Get original element font size and style
|
||||
const computedStyle = window.getComputedStyle(section);
|
||||
const originalFontSize = computedStyle.fontSize;
|
||||
const originalLineHeight = computedStyle.lineHeight;
|
||||
|
||||
// Apply matching font size to textarea
|
||||
textarea.style.fontSize = originalFontSize;
|
||||
if (originalLineHeight !== 'normal') {
|
||||
textarea.style.lineHeight = originalLineHeight;
|
||||
}
|
||||
|
||||
// Auto-sizing function
|
||||
const autoResize = () => {
|
||||
// Temporarily disable transition for accurate measurement
|
||||
const transition = textarea.style.transition;
|
||||
textarea.style.transition = 'none';
|
||||
|
||||
// Reset height to measure scrollHeight
|
||||
textarea.style.height = 'auto';
|
||||
|
||||
// Calculate based on actual content with more reasonable constraints
|
||||
const contentHeight = textarea.scrollHeight;
|
||||
const padding = 24; // 12px top + 12px bottom
|
||||
|
||||
// More reasonable sizing: min 2 lines, max 15 lines
|
||||
const lineCount = textarea.value.split('\\n').length;
|
||||
const minHeight = Math.max(60, lineCount * 24 + padding); // ~24px per line
|
||||
const maxHeight = 360; // Maximum height constraint
|
||||
|
||||
const newHeight = Math.max(60, Math.min(maxHeight, Math.max(minHeight, contentHeight + 4)));
|
||||
textarea.style.height = newHeight + 'px';
|
||||
|
||||
// Re-enable transition
|
||||
textarea.style.transition = transition;
|
||||
};
|
||||
|
||||
// Auto-resize on input and paste
|
||||
textarea.addEventListener('input', autoResize);
|
||||
textarea.addEventListener('paste', () => setTimeout(autoResize, 10));
|
||||
|
||||
// Initial sizing after DOM update
|
||||
setTimeout(autoResize, 20);
|
||||
|
||||
textarea.addEventListener('blur', () => {
|
||||
section.innerHTML = marked.parse(textarea.value);
|
||||
this.markSections(section.parentElement);
|
||||
this.hasEdits = true; // Mark that edits have been made
|
||||
|
||||
// Check if the content contains paragraph breaks that should create separate sections
|
||||
const content = textarea.value.trim();
|
||||
const paragraphs = content.split(/\\n\\s*\\n/).filter(p => p.trim());
|
||||
|
||||
if (paragraphs.length > 1) {
|
||||
// Multiple paragraphs - create separate sections
|
||||
const parentNode = section.parentNode;
|
||||
const sectionIndex = section.getAttribute('data-section');
|
||||
const nextSibling = section.nextSibling; // Remember position
|
||||
|
||||
// Remove the original section
|
||||
parentNode.removeChild(section);
|
||||
|
||||
// Create separate sections for each paragraph and insert at correct position
|
||||
paragraphs.forEach((paragraph, index) => {
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = marked.parse(paragraph.trim());
|
||||
wrapper.classList.add('markitect-section-editable');
|
||||
wrapper.setAttribute('data-section', sectionIndex + '_' + index);
|
||||
wrapper.setAttribute('data-edited', 'true');
|
||||
|
||||
// Insert at the correct position (before nextSibling)
|
||||
parentNode.insertBefore(wrapper, nextSibling);
|
||||
});
|
||||
} else {
|
||||
// Single content block - create one wrapper
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = marked.parse(content);
|
||||
wrapper.classList.add('markitect-section-editable');
|
||||
wrapper.setAttribute('data-section', section.getAttribute('data-section'));
|
||||
wrapper.setAttribute('data-edited', 'true');
|
||||
|
||||
// Replace the section with the wrapper
|
||||
section.parentNode.replaceChild(wrapper, section);
|
||||
}
|
||||
|
||||
// Re-mark sections in the entire document, but skip edited wrappers
|
||||
this.markSections(document.getElementById('markdown-content'));
|
||||
});
|
||||
|
||||
section.innerHTML = '';
|
||||
@@ -499,8 +834,76 @@ class DocumentManager:
|
||||
}
|
||||
|
||||
htmlToMarkdown(html) {
|
||||
// Simple HTML to Markdown conversion
|
||||
return html.replace(/<[^>]*>/g, '').trim();
|
||||
// Create a temporary element to parse the HTML
|
||||
const temp = document.createElement('div');
|
||||
temp.innerHTML = html;
|
||||
|
||||
// Better HTML to Markdown conversion that preserves structure
|
||||
let markdown = '';
|
||||
|
||||
const processNode = (node) => {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return node.textContent;
|
||||
}
|
||||
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const tagName = node.tagName.toLowerCase();
|
||||
|
||||
switch (tagName) {
|
||||
case 'h1': return '# ' + node.textContent;
|
||||
case 'h2': return '## ' + node.textContent;
|
||||
case 'h3': return '### ' + node.textContent;
|
||||
case 'h4': return '#### ' + node.textContent;
|
||||
case 'h5': return '##### ' + node.textContent;
|
||||
case 'h6': return '###### ' + node.textContent;
|
||||
case 'p':
|
||||
// Handle paragraphs with potential inline formatting
|
||||
const childText = Array.from(node.childNodes).map(processNode).join('');
|
||||
return childText;
|
||||
case 'strong': case 'b':
|
||||
return '**' + node.textContent + '**';
|
||||
case 'em': case 'i':
|
||||
return '*' + node.textContent + '*';
|
||||
case 'code':
|
||||
return '`' + node.textContent + '`';
|
||||
case 'pre':
|
||||
// Handle code blocks
|
||||
const codeContent = node.textContent;
|
||||
return '```\\n' + codeContent + '\\n```';
|
||||
case 'blockquote':
|
||||
const quoteLines = node.textContent.split('\\n');
|
||||
return quoteLines.map(line => '> ' + line).join('\\n');
|
||||
case 'ul':
|
||||
// Handle unordered lists
|
||||
const ulItems = Array.from(node.querySelectorAll('li'));
|
||||
return ulItems.map(li => '- ' + li.textContent).join('\\n');
|
||||
case 'ol':
|
||||
// Handle ordered lists
|
||||
const olItems = Array.from(node.querySelectorAll('li'));
|
||||
return olItems.map((li, index) => (index + 1) + '. ' + li.textContent).join('\\n');
|
||||
case 'br':
|
||||
return '\\n';
|
||||
default:
|
||||
return node.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
// Process each child node and add appropriate spacing
|
||||
const nodes = Array.from(temp.childNodes);
|
||||
nodes.forEach((node, index) => {
|
||||
const result = processNode(node);
|
||||
if (result.trim()) {
|
||||
if (index > 0 && markdown.trim()) {
|
||||
markdown += '\\n\\n';
|
||||
}
|
||||
markdown += result;
|
||||
}
|
||||
});
|
||||
|
||||
return markdown.trim();
|
||||
}
|
||||
|
||||
setupKeyboardShortcuts() {
|
||||
@@ -563,6 +966,11 @@ class DocumentManager:
|
||||
}
|
||||
|
||||
getMarkdownContent() {
|
||||
// If no edits have been made, return the original markdown content
|
||||
if (!this.hasEdits) {
|
||||
return markdownContent;
|
||||
}
|
||||
|
||||
// Reconstruct markdown content from the current state of sections
|
||||
const content = document.getElementById('markdown-content');
|
||||
if (!content) {
|
||||
@@ -575,32 +983,69 @@ class DocumentManager:
|
||||
let reconstructed = '';
|
||||
|
||||
sections.forEach(section => {{
|
||||
const tagName = section.tagName.toLowerCase();
|
||||
const text = section.textContent.trim();
|
||||
// Handle edited wrappers differently
|
||||
if (section.hasAttribute('data-edited')) {{
|
||||
// For edited sections, convert the child elements back to markdown
|
||||
const childElements = section.children;
|
||||
for (let i = 0; i < childElements.length; i++) {{
|
||||
const child = childElements[i];
|
||||
const tagName = child.tagName.toLowerCase();
|
||||
const text = child.textContent.trim();
|
||||
|
||||
if (tagName.startsWith('h')) {{
|
||||
const level = parseInt(tagName.charAt(1));
|
||||
reconstructed += '#'.repeat(level) + ' ' + text + '\\n\\n';
|
||||
}} else if (tagName === 'p') {{
|
||||
reconstructed += text + '\\n\\n';
|
||||
}} else if (tagName === 'blockquote') {{
|
||||
reconstructed += '> ' + text + '\\n\\n';
|
||||
}} else if (tagName === 'pre') {{
|
||||
reconstructed += '```\\n' + text + '\\n```\\n\\n';
|
||||
}} else if (tagName === 'ul') {{
|
||||
const items = section.querySelectorAll('li');
|
||||
items.forEach(item => {{
|
||||
reconstructed += '- ' + item.textContent.trim() + '\\n';
|
||||
}});
|
||||
reconstructed += '\\n';
|
||||
}} else if (tagName === 'ol') {{
|
||||
const items = section.querySelectorAll('li');
|
||||
items.forEach((item, index) => {{
|
||||
reconstructed += (index + 1) + '. ' + item.textContent.trim() + '\\n';
|
||||
}});
|
||||
reconstructed += '\\n';
|
||||
if (tagName.startsWith('h')) {{
|
||||
const level = parseInt(tagName.charAt(1));
|
||||
reconstructed += '#'.repeat(level) + ' ' + text + '\\n\\n';
|
||||
}} else if (tagName === 'p') {{
|
||||
reconstructed += text + '\\n\\n';
|
||||
}} else if (tagName === 'blockquote') {{
|
||||
reconstructed += '> ' + text + '\\n\\n';
|
||||
}} else if (tagName === 'pre') {{
|
||||
reconstructed += '```\\n' + text + '\\n```\\n\\n';
|
||||
}} else if (tagName === 'ul') {{
|
||||
const items = child.querySelectorAll('li');
|
||||
items.forEach(item => {{
|
||||
reconstructed += '- ' + item.textContent.trim() + '\\n';
|
||||
}});
|
||||
reconstructed += '\\n';
|
||||
}} else if (tagName === 'ol') {{
|
||||
const items = child.querySelectorAll('li');
|
||||
items.forEach((item, index) => {{
|
||||
reconstructed += (index + 1) + '. ' + item.textContent.trim() + '\\n';
|
||||
}});
|
||||
reconstructed += '\\n';
|
||||
}} else {{
|
||||
reconstructed += text + '\\n\\n';
|
||||
}}
|
||||
}}
|
||||
}} else {{
|
||||
reconstructed += text + '\\n\\n';
|
||||
// Handle regular sections
|
||||
const tagName = section.tagName.toLowerCase();
|
||||
const text = section.textContent.trim();
|
||||
|
||||
if (tagName.startsWith('h')) {{
|
||||
const level = parseInt(tagName.charAt(1));
|
||||
reconstructed += '#'.repeat(level) + ' ' + text + '\\n\\n';
|
||||
}} else if (tagName === 'p') {{
|
||||
reconstructed += text + '\\n\\n';
|
||||
}} else if (tagName === 'blockquote') {{
|
||||
reconstructed += '> ' + text + '\\n\\n';
|
||||
}} else if (tagName === 'pre') {{
|
||||
reconstructed += '```\\n' + text + '\\n```\\n\\n';
|
||||
}} else if (tagName === 'ul') {{
|
||||
const items = section.querySelectorAll('li');
|
||||
items.forEach(item => {{
|
||||
reconstructed += '- ' + item.textContent.trim() + '\\n';
|
||||
}});
|
||||
reconstructed += '\\n';
|
||||
}} else if (tagName === 'ol') {{
|
||||
const items = section.querySelectorAll('li');
|
||||
items.forEach((item, index) => {{
|
||||
reconstructed += (index + 1) + '. ' + item.textContent.trim() + '\\n';
|
||||
}});
|
||||
reconstructed += '\\n';
|
||||
}} else {{
|
||||
reconstructed += text + '\\n\\n';
|
||||
}}
|
||||
}}
|
||||
}});
|
||||
|
||||
@@ -612,7 +1057,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_html = ""
|
||||
@@ -691,20 +1154,58 @@ class DocumentManager:
|
||||
version_info = "0.3.0"
|
||||
|
||||
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;">
|
||||
<div style="font-weight: bold; color: #1976d2;">📝 Markitect Edit Mode <span style="font-weight: normal; color: #666;">v{version_info}</span></div>
|
||||
<div id="status-message" style="margin-top: 8px;">Loading edit capabilities...</div>
|
||||
<div id="error-details" style="display: none; background: #ffebee; border: 1px solid #f44336; padding: 8px; margin-top: 8px; border-radius: 4px;">
|
||||
<div style="font-weight: bold; color: #c62828;">❌ Edit Mode Failed</div>
|
||||
<div id="error-text" style="margin-top: 4px; color: #666;"></div>
|
||||
<details style="margin-top: 8px;">
|
||||
<summary style="cursor: pointer; color: #1976d2;">🐛 Help us fix this issue</summary>
|
||||
<div style="margin-top: 8px; font-size: 12px; color: #666;">
|
||||
Please report this error with your browser info:
|
||||
<br>📋 Browser: <span id="browser-info"></span>
|
||||
<br>🔗 Create issue: <a href="https://github.com/anthropics/markitect/issues/new" target="_blank" style="color: #1976d2;">GitHub Issues</a>
|
||||
<!-- Floating Control Panel -->
|
||||
<div id="markitect-control-panel" class="markitect-control-panel">
|
||||
<!-- Control Ribbon - Always Visible -->
|
||||
<div class="markitect-control-ribbon" onclick="toggleControlPanel()" title="MarkiTect Editor Controls">
|
||||
📝
|
||||
</div>
|
||||
|
||||
<!-- Panel Header -->
|
||||
<div class="markitect-panel-header">
|
||||
<h3 class="markitect-panel-title">📝 MarkiTect Editor</h3>
|
||||
<p class="markitect-panel-version">v{version_info}</p>
|
||||
<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>
|
||||
</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>"""
|
||||
|
||||
@@ -785,9 +1286,27 @@ class DocumentManager:
|
||||
// Status update utility
|
||||
function updateStatus(message, isError = false) {{
|
||||
const statusMsg = document.getElementById('status-message');
|
||||
const statusIndicator = document.getElementById('status-indicator');
|
||||
const statusIcon = document.querySelector('.markitect-status-icon');
|
||||
|
||||
if (statusMsg) {{
|
||||
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 = '✅';
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from typing import Dict, Any
|
||||
|
||||
from markitect.plugins.base import CommandPlugin, PluginMetadata, PluginType
|
||||
from markitect.plugins.decorators import register_plugin
|
||||
from markitect.document_manager import DocumentManager
|
||||
# DocumentManager removed - using CleanDocumentManager directly
|
||||
from markitect.serializer import ASTSerializer
|
||||
|
||||
|
||||
@@ -1659,7 +1659,7 @@ def md_list_command(ctx, output_format, names_only):
|
||||
@click.option('--css', type=click.Path(),
|
||||
help='Custom CSS file to include')
|
||||
@click.option('--edit', is_flag=True,
|
||||
help='Open in live edit mode with preview')
|
||||
help='Open in interactive edit mode with stable section editing')
|
||||
@click.option('--editor-theme', default='github',
|
||||
type=click.Choice(['github', 'monokai', 'tomorrow', 'dark']),
|
||||
help='Editor theme for live edit mode (default: github)')
|
||||
@@ -1704,17 +1704,20 @@ def md_render_command(ctx, input_file, output, template, css, edit, editor_theme
|
||||
ensure_publication_directory(pub_dir)
|
||||
output_path = pub_dir / get_output_filename(input_path)
|
||||
|
||||
# Initialize document manager
|
||||
doc_manager = DocumentManager(config.get('db_manager'))
|
||||
# Initialize clean document manager
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager(config.get('db_manager'))
|
||||
|
||||
# Render the file
|
||||
if edit:
|
||||
# Live edit mode - generate HTML with editing capabilities
|
||||
# Edit mode - generate HTML with editing capabilities
|
||||
result = doc_manager.render_file(input_file, str(output_path),
|
||||
template=template, css=css,
|
||||
edit_mode=True, editor_theme=editor_theme,
|
||||
edit_mode=True,
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts)
|
||||
click.echo(f"✓ Rendered with editing capabilities to: {output_path}")
|
||||
|
||||
click.echo(f"✓ Rendered with interactive editing capabilities to: {output_path}")
|
||||
|
||||
if config.get('verbose', False):
|
||||
click.echo(f"Editor theme: {editor_theme}")
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "markitect"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
description = "Advanced Markdown engine for structured content"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
|
||||
@@ -226,7 +226,7 @@ class TestEditModeRegression:
|
||||
)
|
||||
|
||||
# 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="error-details"' in html_content
|
||||
assert 'reportEditModeError' in html_content
|
||||
|
||||
Reference in New Issue
Block a user