feat: complete clean editor implementation with comprehensive UI framework
Major architectural improvements and feature enhancements: ## Core Features Added - ✨ Custom status modal system replacing browser alerts with theme-consistent branding - ✨ HTML generation dogtag with timestamp and username linking - ✨ All document links now open in new tabs without triggering edit mode - ✨ Comprehensive UI framework documentation (UserInterfaceFramework.md) ## Architecture Improvements - 🔧 Complete cleanup of document_manager.py - removed 2000+ lines of legacy code - 🔧 Clean wrapper implementation maintaining backward compatibility - 🔧 Enhanced database integration with proper front matter parsing - 🔧 Improved AST processing and cache file generation ## UI/UX Enhancements - 🎨 Theme-aware modal dialogs with proper CSS styling and accessibility - 🎨 Consistent CSS class naming conventions across all UI components - 🎨 Enhanced link behavior for better document navigation - 🎨 Professional status information display ## Developer Experience - 📝 Comprehensive UI component documentation for future development - 🧪 Updated test suite to work with clean implementation - 🧪 Fixed multiple test compatibility issues - 🧪 Enhanced error handling and validation ## Technical Details - Added store_document method to CleanDocumentManager - Enhanced ingest_file method with proper title extraction - Implemented theme-consistent modal overlay patterns - Added --nodogtag CLI option for clean output when needed - Fixed CSS escape sequences and JavaScript syntax issues This release establishes a solid foundation for the clean editor architecture while maintaining full backward compatibility with existing functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -102,5 +102,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- **Build System**: Enhanced build targets with venv Python and PYTHONPATH support
|
- **Build System**: Enhanced build targets with venv Python and PYTHONPATH support
|
||||||
- **Target Naming**: Renamed workspace targets to TDD Workspace with tdd- prefix
|
- **Target Naming**: Renamed workspace targets to TDD Workspace with tdd- prefix
|
||||||
|
|
||||||
All notable changes to MarkiTect will be documented in this file.
|
xxx
|
||||||
|
|
||||||
|
|||||||
338
UserInterfaceFramework.md
Normal file
338
UserInterfaceFramework.md
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
# User Interface Framework Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document defines the canonical terminology and specifications for all UI components in the Markitect markdown editor interface. This framework establishes a common vocabulary for interface evolution discussions and future development.
|
||||||
|
|
||||||
|
## Component Architecture
|
||||||
|
|
||||||
|
The editor interface consists of 6 main components organized in layers:
|
||||||
|
|
||||||
|
### Layer Priority (Z-Index)
|
||||||
|
1. **Toast Notifications** (z-index: 10001) - Highest priority
|
||||||
|
2. **Editor Floating Action Panel** (z-index: 1000) - High priority
|
||||||
|
3. **Modal Dialogs** (z-index: 999) - Modal layer
|
||||||
|
4. **Inline Section Editors** (z-index: 100) - Contextual editing
|
||||||
|
5. **Document Canvas** (z-index: 1) - Content layer
|
||||||
|
6. **Background** (z-index: 0) - Base layer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Editor Floating Action Panel
|
||||||
|
|
||||||
|
**Component Name**: `Editor`
|
||||||
|
**Type**: Floating action panel with status indicator
|
||||||
|
**Location**: Top-right corner (fixed positioning)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
A persistent control hub providing document-level actions and real-time status feedback. Always visible and contextually aware of editing state.
|
||||||
|
|
||||||
|
### Technical Specifications
|
||||||
|
- **Container ID**: `ui-edit-floater`
|
||||||
|
- **CSS Classes**: `ui-edit-floater-panel`
|
||||||
|
- **Position**: `position: fixed; top: 20px; right: 20px;`
|
||||||
|
- **Z-Index**: 1000
|
||||||
|
- **Update Frequency**: Status refreshes every 2 seconds via `setInterval`
|
||||||
|
|
||||||
|
### Components
|
||||||
|
1. **Header Section**
|
||||||
|
- **Title**: "📝 Editor" (emoji + text)
|
||||||
|
- **Status Display**: Dynamic text showing current state
|
||||||
|
|
||||||
|
2. **Action Buttons**
|
||||||
|
- **💾 Save Document** (green accept style)
|
||||||
|
- **🔄 Reset All** (orange reset style)
|
||||||
|
- **📊 Show Status** (grey secondary style)
|
||||||
|
|
||||||
|
### Status States
|
||||||
|
- `"Ready"` - Default idle state
|
||||||
|
- `"Editing [N] section(s)"` - Active editing in progress
|
||||||
|
- `"[N] section(s) modified"` - Unsaved changes exist
|
||||||
|
- `"All sections saved ✓"` - All work is saved (with checkmark)
|
||||||
|
|
||||||
|
### Theme Integration
|
||||||
|
- Colors and styling adapt to selected UI theme (standard, greyscale, electric, psychedelic)
|
||||||
|
- Header text color matches theme text color
|
||||||
|
- Panel background follows theme panel styling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Toast Notification System
|
||||||
|
|
||||||
|
**Component Name**: `Toast`
|
||||||
|
**Type**: Auto-dismissing temporary status messages
|
||||||
|
**Location**: Top-center (horizontally centered)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
Provides immediate visual feedback for user actions through temporary, non-blocking messages that appear and automatically disappear.
|
||||||
|
|
||||||
|
### Technical Specifications
|
||||||
|
- **Position**: `position: fixed; top: 20px; left: 50%; transform: translateX(-50%);`
|
||||||
|
- **Z-Index**: 10001 (highest priority)
|
||||||
|
- **Auto-Dismiss**: 3 seconds
|
||||||
|
- **Max Width**: 400px
|
||||||
|
- **Animation**: Smooth appear/disappear
|
||||||
|
|
||||||
|
### Message Types
|
||||||
|
1. **Success Toast** (green `#28a745`)
|
||||||
|
- "Document saved as: [filename]"
|
||||||
|
- "✂️ Section split into [N] sections!"
|
||||||
|
|
||||||
|
2. **Info Toast** (blue `#007acc`)
|
||||||
|
- "Document reset to original structure"
|
||||||
|
|
||||||
|
3. **Error Toast** (red `#dc3545`)
|
||||||
|
- Error condition messages
|
||||||
|
|
||||||
|
### Visual Styling
|
||||||
|
- **Shape**: Rounded corners (4px border-radius)
|
||||||
|
- **Typography**: White text, 14px font size, center aligned
|
||||||
|
- **Shadow**: `0 2px 8px rgba(0,0,0,0.2)`
|
||||||
|
- **Padding**: `12px 20px`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Document Canvas
|
||||||
|
|
||||||
|
**Component Name**: `Document` or `Canvas`
|
||||||
|
**Type**: Main content rendering area
|
||||||
|
**Location**: Central content area
|
||||||
|
|
||||||
|
### Description
|
||||||
|
The primary workspace where markdown content is rendered and made interactive for editing. Displays content as formatted HTML while providing editing affordances.
|
||||||
|
|
||||||
|
### Technical Specifications
|
||||||
|
- **Container ID**: `markdown-content`
|
||||||
|
- **CSS Classes**: Content uses semantic classes (`ui-edit-section`)
|
||||||
|
- **Layout**: Responsive, centered with max-width constraints
|
||||||
|
- **Interaction**: Click-to-edit paradigm
|
||||||
|
|
||||||
|
### Section Elements
|
||||||
|
Each content section is individually interactive:
|
||||||
|
- **Hover Effect**: Subtle background (`rgba(0, 0, 0, 0.02)`) and border hint
|
||||||
|
- **Click Target**: Entire section area is clickable
|
||||||
|
- **Visual Feedback**: Smooth transitions (0.2s ease)
|
||||||
|
- **Section Types**: Headings, paragraphs, lists, code blocks, blockquotes
|
||||||
|
|
||||||
|
### Content Rendering
|
||||||
|
- **Primary**: Uses `marked.js` for markdown parsing
|
||||||
|
- **Fallback**: Basic HTML conversion if library fails
|
||||||
|
- **Graceful Degradation**: Always displays content, even with errors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Inline Section Editor
|
||||||
|
|
||||||
|
**Component Name**: `Section Editor` or `Inline Editor`
|
||||||
|
**Type**: Contextual editing widget
|
||||||
|
**Location**: Replaces section content during editing
|
||||||
|
|
||||||
|
### Description
|
||||||
|
A contextual editing interface that appears when a section is activated for editing. Provides textarea input and action controls for section-level operations.
|
||||||
|
|
||||||
|
### Technical Specifications
|
||||||
|
- **Container CSS**: `ui-edit-inline-panel`
|
||||||
|
- **Layout**: Horizontal flex layout (textarea + button column)
|
||||||
|
- **Theme Integration**: Inherits floating panel styling from active UI theme
|
||||||
|
- **Focus Management**: Auto-focus on textarea when activated
|
||||||
|
|
||||||
|
### Components
|
||||||
|
1. **Textarea**
|
||||||
|
- **CSS Classes**: `ui-edit-textarea ui-edit-textarea-main`
|
||||||
|
- **Font**: Monospace font family for code editing
|
||||||
|
- **Features**: Vertical resize, focus styling, theme-aware colors
|
||||||
|
|
||||||
|
2. **Action Buttons** (vertical column)
|
||||||
|
- **✓ Accept** (`ui-edit-button-accept`) - Save changes
|
||||||
|
- **✗ Cancel** (`ui-edit-button-cancel`) - Discard changes
|
||||||
|
- **🔄 Reset** (`ui-edit-button-reset`) - Restore original content
|
||||||
|
|
||||||
|
### Behavior
|
||||||
|
- **Multi-Section**: Supports multiple concurrent section editing
|
||||||
|
- **State Persistence**: Maintains editing state until explicitly resolved
|
||||||
|
- **Keyboard Support**: Planned for future enhancement
|
||||||
|
- **Auto-Split**: Automatically splits sections when new headings are added
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Status Information Modal
|
||||||
|
|
||||||
|
**Component Name**: `Status Modal` or `Info Dialog`
|
||||||
|
**Type**: Modal dialog for comprehensive status display
|
||||||
|
**Location**: Center screen (modal overlay)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
Provides detailed information about the current editing session, including version info, document statistics, file details, and help documentation.
|
||||||
|
|
||||||
|
### Current Implementation
|
||||||
|
- **Method**: Browser native `alert()` (temporary solution)
|
||||||
|
- **Trigger**: "📊 Show Status" button in floating action panel
|
||||||
|
- **Content**: Multi-section formatted text
|
||||||
|
|
||||||
|
### Information Sections
|
||||||
|
1. **Application Header**
|
||||||
|
- Application name and version
|
||||||
|
- Git commit info and development status
|
||||||
|
|
||||||
|
2. **File Information**
|
||||||
|
- Generated save filename
|
||||||
|
- Source filename
|
||||||
|
- Current URL
|
||||||
|
|
||||||
|
3. **Document Statistics**
|
||||||
|
- Total sections count
|
||||||
|
- Modified sections count
|
||||||
|
- Currently editing sections count
|
||||||
|
- Unsaved changes indicator
|
||||||
|
|
||||||
|
4. **Help Documentation**
|
||||||
|
- Section behavior explanation
|
||||||
|
- Editing controls reference
|
||||||
|
- Keyboard shortcuts (future)
|
||||||
|
|
||||||
|
### Future Enhancement Plan
|
||||||
|
**Target**: Replace browser alert with custom modal dialog
|
||||||
|
- **Styling**: Theme-aware modal with proper typography
|
||||||
|
- **Interaction**: Close button, better formatting
|
||||||
|
- **Features**: Copy-to-clipboard, expandable sections
|
||||||
|
- **Accessibility**: Proper ARIA labels, keyboard navigation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Confirmation Dialog
|
||||||
|
|
||||||
|
**Component Name**: `Confirmation Dialog`
|
||||||
|
**Type**: Modal confirmation for destructive actions
|
||||||
|
**Location**: Center screen (modal overlay)
|
||||||
|
|
||||||
|
### Description
|
||||||
|
Provides user confirmation for potentially destructive operations that cannot be easily undone.
|
||||||
|
|
||||||
|
### Current Implementation
|
||||||
|
- **Method**: Browser native `confirm()` (temporary solution)
|
||||||
|
- **Trigger**: "🔄 Reset All" button in floating action panel
|
||||||
|
- **Message**: "Reset all content to original markdown? This will lose all edits and remove split sections."
|
||||||
|
|
||||||
|
### Use Cases
|
||||||
|
- **Reset All Sections**: Complete document reset to original state
|
||||||
|
- **Future**: Delete operations, bulk changes, file operations
|
||||||
|
|
||||||
|
### Future Enhancement Plan
|
||||||
|
**Target**: Replace browser confirm with custom modal dialog
|
||||||
|
- **Styling**: Theme-aware modal with clear action buttons
|
||||||
|
- **Features**:
|
||||||
|
- Clear primary/secondary action buttons
|
||||||
|
- Detailed consequence explanation
|
||||||
|
- Optional "Don't ask again" for non-critical confirmations
|
||||||
|
- **Accessibility**: Proper focus management, keyboard support
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
### 1. **Theme Consistency**
|
||||||
|
All components must adapt to the selected UI theme:
|
||||||
|
- **Standard**: Light grey palette with blue accents
|
||||||
|
- **Greyscale**: Monochromatic grey scale
|
||||||
|
- **Electric**: Dark blue with cyan/yellow accents and glow effects
|
||||||
|
- **Psychedelic**: Vibrant gradient backgrounds with white text
|
||||||
|
|
||||||
|
### 2. **Non-Blocking Interactions**
|
||||||
|
- **Toast notifications**: Auto-dismiss, don't require user action
|
||||||
|
- **Floating action panel**: Always accessible, doesn't block content
|
||||||
|
- **Inline editors**: Contextual, don't interfere with other sections
|
||||||
|
|
||||||
|
### 3. **Graceful Degradation**
|
||||||
|
- **Content always visible**: Even if JavaScript fails
|
||||||
|
- **Progressive enhancement**: Core functionality works without advanced features
|
||||||
|
- **Fallback implementations**: Basic browser dialogs until custom implementations ready
|
||||||
|
|
||||||
|
### 4. **Responsive Design**
|
||||||
|
- **Mobile-first**: Components adapt to smaller screens
|
||||||
|
- **Touch-friendly**: Appropriate touch targets and gestures
|
||||||
|
- **Scalable**: Works across different zoom levels and resolutions
|
||||||
|
|
||||||
|
### 5. **Accessibility**
|
||||||
|
- **Keyboard navigation**: All interactive elements accessible via keyboard
|
||||||
|
- **Screen reader support**: Proper ARIA labels and semantic markup
|
||||||
|
- **High contrast**: Sufficient color contrast ratios in all themes
|
||||||
|
- **Focus management**: Clear focus indicators and logical tab order
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development Conventions
|
||||||
|
|
||||||
|
### CSS Class Naming
|
||||||
|
**Pattern**: `{scope}-{component}-{element}-{modifier}`
|
||||||
|
|
||||||
|
**Scopes**:
|
||||||
|
- `ui` - User interface elements
|
||||||
|
- `md` - Mode (light/dark)
|
||||||
|
- `dc` - Document content
|
||||||
|
- `br` - Branding
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
- `ui-edit-floater-panel`
|
||||||
|
- `ui-edit-button-accept`
|
||||||
|
- `ui-edit-textarea-main`
|
||||||
|
- `ui-edit-section-frame`
|
||||||
|
|
||||||
|
### JavaScript Event Naming
|
||||||
|
**Pattern**: `{action}-{target}`
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
- `edit-started`
|
||||||
|
- `changes-accepted`
|
||||||
|
- `section-split`
|
||||||
|
- `content-updated`
|
||||||
|
|
||||||
|
### Component State Management
|
||||||
|
- **Centralized**: Section state managed by `SectionManager`
|
||||||
|
- **Event-driven**: Components communicate via events
|
||||||
|
- **Immutable updates**: State changes create new state objects
|
||||||
|
- **Consistent**: Same patterns across all components
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Future Enhancement Roadmap
|
||||||
|
|
||||||
|
### Phase 1: Modal System Replacement
|
||||||
|
- Replace browser `alert()` and `confirm()` with custom implementations
|
||||||
|
- Add proper theme integration and accessibility features
|
||||||
|
- Implement keyboard navigation and focus management
|
||||||
|
|
||||||
|
### Phase 2: Enhanced Interactions
|
||||||
|
- Add keyboard shortcuts for common operations
|
||||||
|
- Implement drag-and-drop section reordering
|
||||||
|
- Add section templates and quick-insert functionality
|
||||||
|
|
||||||
|
### Phase 3: Advanced Features
|
||||||
|
- Multi-document editing with tabs
|
||||||
|
- Real-time collaboration indicators
|
||||||
|
- Advanced search and replace within sections
|
||||||
|
- Export options beyond basic markdown
|
||||||
|
|
||||||
|
### Phase 4: Performance Optimization
|
||||||
|
- Virtual scrolling for large documents
|
||||||
|
- Lazy loading of section editors
|
||||||
|
- Optimized rendering for mobile devices
|
||||||
|
- Advanced caching strategies
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Component Integration Matrix
|
||||||
|
|
||||||
|
| Component | Theme Aware | Mobile Ready | Keyboard Nav | Touch Friendly | Accessible |
|
||||||
|
|-----------|-------------|--------------|--------------|----------------|------------|
|
||||||
|
| Editor Panel | ✅ Yes | ⚠️ Partial | ❌ Planned | ⚠️ Basic | ⚠️ Partial |
|
||||||
|
| Toast System | ❌ No | ✅ Yes | ❌ N/A | ✅ Yes | ⚠️ Basic |
|
||||||
|
| Document Canvas | ✅ Yes | ✅ Yes | ⚠️ Partial | ✅ Yes | ✅ Yes |
|
||||||
|
| Section Editor | ✅ Yes | ⚠️ Partial | ⚠️ Basic | ⚠️ Basic | ⚠️ Partial |
|
||||||
|
| Status Modal | ❌ No | ❌ No | ❌ No | ❌ No | ❌ No |
|
||||||
|
| Confirmation | ❌ No | ❌ No | ❌ No | ❌ No | ❌ No |
|
||||||
|
|
||||||
|
**Legend**: ✅ Full Support | ⚠️ Partial/Needs Work | ❌ Not Implemented
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This framework provides the foundation for consistent UI development and evolution. All future interface changes should reference these component definitions and maintain the established patterns and conventions.
|
||||||
@@ -16,8 +16,47 @@ class CleanDocumentManager:
|
|||||||
def __init__(self, db_manager=None):
|
def __init__(self, db_manager=None):
|
||||||
self.db_manager = db_manager
|
self.db_manager = db_manager
|
||||||
|
|
||||||
|
def store_document(self, file_path: str, content: str, ast: list = None, front_matter: dict = None):
|
||||||
|
"""Store a document in the database."""
|
||||||
|
if self.db_manager:
|
||||||
|
from pathlib import Path
|
||||||
|
filename = Path(file_path).name
|
||||||
|
return self.db_manager.store_markdown_file(filename, content)
|
||||||
|
|
||||||
|
def get_file(self, file_path: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Retrieve a markdown file from the database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to the markdown file to retrieve
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing file content and metadata
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError: If file is not found in database
|
||||||
|
"""
|
||||||
|
if not self.db_manager:
|
||||||
|
raise ValueError("Database manager not initialized")
|
||||||
|
|
||||||
|
# Get file from database
|
||||||
|
file_data = self.db_manager.get_markdown_file(file_path)
|
||||||
|
|
||||||
|
if file_data is None:
|
||||||
|
raise FileNotFoundError(f"File '{file_path}' not found in database")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'content': file_data.get('content', ''),
|
||||||
|
'metadata': {
|
||||||
|
'filename': file_data.get('filename', file_path),
|
||||||
|
'front_matter': file_data.get('front_matter'),
|
||||||
|
'size': len(file_data.get('content', '')),
|
||||||
|
'modified': file_data.get('modified')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def render_file(self, input_file: str, output_file: str, template: str = None, css: str = None,
|
def render_file(self, input_file: str, output_file: str, template: str = None, css: str = None,
|
||||||
edit_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True) -> Dict[str, Any]:
|
edit_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True, nodogtag: bool = False) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Render a markdown file to HTML with optional clean editing capabilities.
|
Render a markdown file to HTML with optional clean editing capabilities.
|
||||||
"""
|
"""
|
||||||
@@ -49,7 +88,8 @@ class CleanDocumentManager:
|
|||||||
editor_theme=editor_theme,
|
editor_theme=editor_theme,
|
||||||
keyboard_shortcuts=keyboard_shortcuts,
|
keyboard_shortcuts=keyboard_shortcuts,
|
||||||
original_filename=original_filename,
|
original_filename=original_filename,
|
||||||
version_info=version_info
|
version_info=version_info,
|
||||||
|
nodogtag=nodogtag
|
||||||
)
|
)
|
||||||
|
|
||||||
# Write HTML file
|
# Write HTML file
|
||||||
@@ -73,51 +113,17 @@ class CleanDocumentManager:
|
|||||||
|
|
||||||
def _get_version_info(self) -> dict:
|
def _get_version_info(self) -> dict:
|
||||||
"""Get repository name and version information."""
|
"""Get repository name and version information."""
|
||||||
version_info = {
|
from .__version__ import get_version_info
|
||||||
|
|
||||||
|
version_info = get_version_info()
|
||||||
|
|
||||||
|
# Transform to the format expected by the editor
|
||||||
|
return {
|
||||||
'repo_name': 'Markitect',
|
'repo_name': 'Markitect',
|
||||||
'version': '0.3.0',
|
'version': version_info['full_version'],
|
||||||
'git_info': ''
|
'git_info': '' # Already included in full_version
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
|
||||||
# Try to get version from package metadata
|
|
||||||
from importlib.metadata import version as get_version
|
|
||||||
version_info['version'] = get_version('markitect')
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Try to get git information
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Get git commit hash and status
|
|
||||||
try:
|
|
||||||
git_hash = subprocess.check_output(
|
|
||||||
['git', 'rev-parse', '--short', 'HEAD'],
|
|
||||||
cwd=Path(__file__).parent,
|
|
||||||
stderr=subprocess.DEVNULL
|
|
||||||
).decode().strip()
|
|
||||||
|
|
||||||
# Check if there are uncommitted changes
|
|
||||||
try:
|
|
||||||
subprocess.check_output(
|
|
||||||
['git', 'diff-index', '--quiet', 'HEAD', '--'],
|
|
||||||
cwd=Path(__file__).parent,
|
|
||||||
stderr=subprocess.DEVNULL
|
|
||||||
)
|
|
||||||
git_status = ''
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
git_status = '-modified'
|
|
||||||
|
|
||||||
version_info['git_info'] = f" (git:{git_hash}{git_status})"
|
|
||||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
||||||
pass
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return version_info
|
|
||||||
|
|
||||||
def _get_template_css(self, template: str = None) -> str:
|
def _get_template_css(self, template: str = None) -> str:
|
||||||
"""Generate layered theme CSS styles."""
|
"""Generate layered theme CSS styles."""
|
||||||
# Import layered theme functions
|
# Import layered theme functions
|
||||||
@@ -310,10 +316,29 @@ class CleanDocumentManager:
|
|||||||
.markitect-edit-mode .ui-edit-floater-header {{
|
.markitect-edit-mode .ui-edit-floater-header {{
|
||||||
color: {props.get('editor_text_color', '#212529')};
|
color: {props.get('editor_text_color', '#212529')};
|
||||||
}}
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-floater-header h3 {{
|
||||||
|
color: {props.get('editor_text_color', '#212529')};
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-inline-panel {{
|
||||||
|
background: {props['editor_panel_bg']};
|
||||||
|
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||||
|
box-shadow: 0 2px 8px {props.get('editor_shadow', 'rgba(0,0,0,0.1)')};
|
||||||
|
color: {props.get('editor_text_color', '#212529')};
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
}}
|
||||||
.markitect-edit-mode .ui-edit-button {{
|
.markitect-edit-mode .ui-edit-button {{
|
||||||
background: {props.get('editor_button_bg', '#ffffff')};
|
background: {props.get('editor_button_bg', '#ffffff')};
|
||||||
color: {props.get('editor_text_color', '#212529')};
|
color: {props.get('editor_text_color', '#212529')};
|
||||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
min-width: 70px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
}}
|
}}
|
||||||
.markitect-edit-mode .ui-edit-button:hover {{
|
.markitect-edit-mode .ui-edit-button:hover {{
|
||||||
background: {props.get('editor_button_hover', '#e9ecef')};
|
background: {props.get('editor_button_hover', '#e9ecef')};
|
||||||
@@ -323,17 +348,36 @@ class CleanDocumentManager:
|
|||||||
background: {props.get('editor_button_active', '#dee2e6')};
|
background: {props.get('editor_button_active', '#dee2e6')};
|
||||||
}}
|
}}
|
||||||
.markitect-edit-mode .ui-edit-button-accept {{
|
.markitect-edit-mode .ui-edit-button-accept {{
|
||||||
background: {props.get('editor_button_bg', '#4caf50')};
|
background: {props.get('editor_accept_bg', '#4caf50')};
|
||||||
|
color: white;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-button-accept:hover {{
|
||||||
|
background: {props.get('editor_accept_hover', '#388e3c')};
|
||||||
}}
|
}}
|
||||||
.markitect-edit-mode .ui-edit-button-cancel {{
|
.markitect-edit-mode .ui-edit-button-cancel {{
|
||||||
background: {props.get('editor_button_bg', '#f44336')};
|
background: {props.get('editor_cancel_bg', '#f44336')};
|
||||||
|
color: white;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-button-cancel:hover {{
|
||||||
|
background: {props.get('editor_cancel_hover', '#d32f2f')};
|
||||||
}}
|
}}
|
||||||
.markitect-edit-mode .ui-edit-button-reset {{
|
.markitect-edit-mode .ui-edit-button-reset {{
|
||||||
background: {props.get('editor_button_bg', '#ff9800')};
|
background: {props.get('editor_reset_bg', '#ff9800')};
|
||||||
|
color: white;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-button-reset:hover {{
|
||||||
|
background: {props.get('editor_reset_hover', '#f57c00')};
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-button-secondary {{
|
||||||
|
background: {props.get('editor_secondary_bg', '#6c757d')};
|
||||||
|
color: white;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-button-secondary:hover {{
|
||||||
|
background: {props.get('editor_secondary_hover', '#545b62')};
|
||||||
}}
|
}}
|
||||||
.markitect-edit-mode .ui-edit-section-frame {{
|
.markitect-edit-mode .ui-edit-section-frame {{
|
||||||
border: 2px solid {props.get('editor_focus_color', '#0066cc')};
|
border: 2px solid {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))};
|
||||||
box-shadow: 0 0 0 3px {props.get('editor_focus_color', '#0066cc')}33;
|
box-shadow: 0 0 0 3px {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))}33;
|
||||||
}}
|
}}
|
||||||
.markitect-edit-mode .ui-edit-textarea {{
|
.markitect-edit-mode .ui-edit-textarea {{
|
||||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||||
@@ -341,8 +385,103 @@ class CleanDocumentManager:
|
|||||||
background: {props.get('editor_button_bg', '#ffffff')};
|
background: {props.get('editor_button_bg', '#ffffff')};
|
||||||
}}
|
}}
|
||||||
.markitect-edit-mode .ui-edit-textarea:focus {{
|
.markitect-edit-mode .ui-edit-textarea:focus {{
|
||||||
border-color: {props.get('editor_focus_color', '#0066cc')};
|
border-color: {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))};
|
||||||
box-shadow: 0 0 0 2px {props.get('editor_focus_color', '#0066cc')}33;
|
box-shadow: 0 0 0 2px {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))}33;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-overlay {{
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.3s, visibility 0.3s;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-overlay.active {{
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal {{
|
||||||
|
background: {props['editor_panel_bg']};
|
||||||
|
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||||
|
box-shadow: 0 8px 32px {props.get('editor_shadow', 'rgba(0,0,0,0.2)')};
|
||||||
|
color: {props.get('editor_text_color', '#212529')};
|
||||||
|
border-radius: 8px;
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 80vh;
|
||||||
|
width: 90%;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: scale(0.9) translateY(-20px);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-overlay.active .ui-edit-modal {{
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-header {{
|
||||||
|
padding: 20px 24px 16px;
|
||||||
|
border-bottom: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-title {{
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: {props.get('editor_text_color', '#212529')};
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-close {{
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: {props.get('editor_text_color', '#212529')};
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-close:hover {{
|
||||||
|
background: {props.get('editor_button_hover', '#e9ecef')};
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-body {{
|
||||||
|
padding: 20px 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 60vh;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-content {{
|
||||||
|
white-space: pre-line;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-section {{
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-section:last-child {{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-section-title {{
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: {props.get('editor_text_color', '#212529')};
|
||||||
|
}}
|
||||||
|
.markitect-edit-mode .ui-edit-modal-footer {{
|
||||||
|
padding: 16px 24px 20px;
|
||||||
|
border-top: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||||
|
text-align: right;
|
||||||
|
}}
|
||||||
|
outline: none;
|
||||||
}}"""
|
}}"""
|
||||||
|
|
||||||
return f"<style>{base_css}{heading_css}{text_css}{element_css}{link_css}{accent_css}{ui_css}</style>"
|
return f"<style>{base_css}{heading_css}{text_css}{element_css}{link_css}{accent_css}{ui_css}</style>"
|
||||||
@@ -382,11 +521,34 @@ class CleanDocumentManager:
|
|||||||
return self._generate_layered_css(layered_props)
|
return self._generate_layered_css(layered_props)
|
||||||
|
|
||||||
def _generate_html_template(self, markdown_content: str, title: str, css: str = None, template: str = None,
|
def _generate_html_template(self, markdown_content: str, title: str, css: str = None, template: str = None,
|
||||||
edit_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True, original_filename: str = 'document', version_info: dict = None) -> str:
|
edit_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True, original_filename: str = 'document', version_info: dict = None, nodogtag: bool = False) -> str:
|
||||||
"""Generate clean HTML template."""
|
"""Generate clean HTML template."""
|
||||||
|
|
||||||
|
# Add dogtag to markdown content if not disabled
|
||||||
|
if not nodogtag:
|
||||||
|
import datetime
|
||||||
|
import getpass
|
||||||
|
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
datetime_str = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
try:
|
||||||
|
username = getpass.getuser()
|
||||||
|
except:
|
||||||
|
username = "user"
|
||||||
|
|
||||||
|
# Create username link only for 'worsch', otherwise just show username
|
||||||
|
if username == 'worsch':
|
||||||
|
username_link = f'<a href="https://coulomb.social/open/worsch" target="_blank">{username}</a>'
|
||||||
|
else:
|
||||||
|
username_link = username
|
||||||
|
|
||||||
|
dogtag = f'\n\n---\n*-- html from markdown by <a href="https://coulomb.social/open/MarkiTect" target="_blank">MarkiTect</a> on {datetime_str} by {username_link}*'
|
||||||
|
markdown_content_with_dogtag = markdown_content + dogtag
|
||||||
|
else:
|
||||||
|
markdown_content_with_dogtag = markdown_content
|
||||||
|
|
||||||
# Escape the markdown content for JavaScript
|
# Escape the markdown content for JavaScript
|
||||||
js_markdown_content = json.dumps(markdown_content)
|
js_markdown_content = json.dumps(markdown_content_with_dogtag)
|
||||||
|
|
||||||
# Handle CSS styles
|
# Handle CSS styles
|
||||||
css_content = ""
|
css_content = ""
|
||||||
@@ -413,7 +575,7 @@ class CleanDocumentManager:
|
|||||||
body_classes = ' class="markitect-edit-mode"'
|
body_classes = ' class="markitect-edit-mode"'
|
||||||
|
|
||||||
# Configuration for clean editor
|
# Configuration for clean editor
|
||||||
version_str = f"{version_info['repo_name']} v{version_info['version']}{version_info['git_info']}" if version_info else "Markitect v0.3.0"
|
version_str = f"{version_info['repo_name']} v{version_info['version']}{version_info['git_info']}" if version_info else "Markitect v0.5.0.dev"
|
||||||
editor_config = f"""
|
editor_config = f"""
|
||||||
const MARKITECT_EDIT_MODE = true;
|
const MARKITECT_EDIT_MODE = true;
|
||||||
const MARKITECT_EDITOR_CONFIG = {{
|
const MARKITECT_EDITOR_CONFIG = {{
|
||||||
@@ -466,7 +628,10 @@ class CleanDocumentManager:
|
|||||||
if (contentDiv) {{
|
if (contentDiv) {{
|
||||||
if (typeof marked !== 'undefined') {{
|
if (typeof marked !== 'undefined') {{
|
||||||
try {{
|
try {{
|
||||||
contentDiv.innerHTML = marked.parse(markdownContent);
|
const html = marked.parse(markdownContent);
|
||||||
|
// Add target="_blank" to all links
|
||||||
|
const htmlWithTargetBlank = html.replace(/<a href="([^"]*)"([^>]*)>/g, '<a href="$1" target="_blank"$2>');
|
||||||
|
contentDiv.innerHTML = htmlWithTargetBlank;
|
||||||
console.log("✓ Content rendered successfully");
|
console.log("✓ Content rendered successfully");
|
||||||
console.log('✓ Markdown rendered successfully');
|
console.log('✓ Markdown rendered successfully');
|
||||||
}} catch (error) {{
|
}} catch (error) {{
|
||||||
@@ -1017,7 +1182,10 @@ class DOMRenderer {
|
|||||||
element.setAttribute('data-section-id', section.id);
|
element.setAttribute('data-section-id', section.id);
|
||||||
|
|
||||||
if (typeof marked !== 'undefined') {
|
if (typeof marked !== 'undefined') {
|
||||||
element.innerHTML = marked.parse(section.currentMarkdown);
|
const html = marked.parse(section.currentMarkdown);
|
||||||
|
// Add target="_blank" to all links
|
||||||
|
const htmlWithTargetBlank = html.replace(/<a href="([^"]*)"([^>]*)>/g, '<a href="$1" target="_blank"$2>');
|
||||||
|
element.innerHTML = htmlWithTargetBlank;
|
||||||
} else {
|
} else {
|
||||||
element.innerHTML = `<p>${section.currentMarkdown}</p>`;
|
element.innerHTML = `<p>${section.currentMarkdown}</p>`;
|
||||||
}
|
}
|
||||||
@@ -1029,8 +1197,8 @@ class DOMRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSectionClick(event) {
|
handleSectionClick(event) {
|
||||||
// Don't handle clicks on form elements or buttons
|
// Don't handle clicks on form elements, buttons, or links
|
||||||
if (event.target.closest('textarea, button, input')) {
|
if (event.target.closest('textarea, button, input, a')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1062,6 +1230,7 @@ class DOMRenderer {
|
|||||||
this.hideCurrentEditor();
|
this.hideCurrentEditor();
|
||||||
|
|
||||||
const editorContainer = document.createElement('div');
|
const editorContainer = document.createElement('div');
|
||||||
|
editorContainer.className = 'ui-edit-inline-panel';
|
||||||
editorContainer.style.cssText = `
|
editorContainer.style.cssText = `
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -1076,7 +1245,6 @@ class DOMRenderer {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||||
border: 2px solid #007acc;
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -1096,36 +1264,17 @@ class DOMRenderer {
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const createButton = (text, color, handler) => {
|
const createButton = (text, className, handler) => {
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.textContent = text;
|
btn.textContent = text;
|
||||||
|
btn.className = className;
|
||||||
// Add CSS classes based on button type
|
|
||||||
btn.className = 'ui-edit-button';
|
|
||||||
if (text.includes('Accept')) {
|
|
||||||
btn.className += ' ui-edit-button-accept';
|
|
||||||
} else if (text.includes('Cancel')) {
|
|
||||||
btn.className += ' ui-edit-button-cancel';
|
|
||||||
} else if (text.includes('Reset')) {
|
|
||||||
btn.className += ' ui-edit-button-reset';
|
|
||||||
}
|
|
||||||
btn.style.cssText = `
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: white;
|
|
||||||
background: ${color};
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
min-width: 70px;
|
|
||||||
`;
|
|
||||||
btn.addEventListener('click', handler);
|
btn.addEventListener('click', handler);
|
||||||
return btn;
|
return btn;
|
||||||
};
|
};
|
||||||
|
|
||||||
controls.appendChild(createButton('✓ Accept', '#4caf50', () => this.handleAccept(sectionId)));
|
controls.appendChild(createButton('✓ Accept', 'ui-edit-button ui-edit-button-accept', () => this.handleAccept(sectionId)));
|
||||||
controls.appendChild(createButton('✗ Cancel', '#f44336', () => this.handleCancel(sectionId)));
|
controls.appendChild(createButton('✗ Cancel', 'ui-edit-button ui-edit-button-cancel', () => this.handleCancel(sectionId)));
|
||||||
controls.appendChild(createButton('🔄 Reset', '#ff9800', () => this.handleReset(sectionId)));
|
controls.appendChild(createButton('🔄 Reset', 'ui-edit-button ui-edit-button-reset', () => this.handleReset(sectionId)));
|
||||||
|
|
||||||
editorContainer.appendChild(textarea);
|
editorContainer.appendChild(textarea);
|
||||||
editorContainer.appendChild(controls);
|
editorContainer.appendChild(controls);
|
||||||
@@ -1159,7 +1308,10 @@ class DOMRenderer {
|
|||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
||||||
if (typeof marked !== 'undefined') {
|
if (typeof marked !== 'undefined') {
|
||||||
element.innerHTML = marked.parse(content);
|
const html = marked.parse(content);
|
||||||
|
// Add target="_blank" to all links
|
||||||
|
const htmlWithTargetBlank = html.replace(/<a href="([^"]*)"([^>]*)>/g, '<a href="$1" target="_blank"$2>');
|
||||||
|
element.innerHTML = htmlWithTargetBlank;
|
||||||
} else {
|
} else {
|
||||||
element.innerHTML = `<p>${content}</p>`;
|
element.innerHTML = `<p>${content}</p>`;
|
||||||
}
|
}
|
||||||
@@ -1169,7 +1321,7 @@ class DOMRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupSectionElement(element) {
|
setupSectionElement(element) {
|
||||||
element.className = 'ui-edit-section ui-edit-section-frame';
|
element.className = 'ui-edit-section';
|
||||||
element.style.cssText = `
|
element.style.cssText = `
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
@@ -1185,8 +1337,8 @@ class DOMRenderer {
|
|||||||
|
|
||||||
// Create new handlers and store references
|
// Create new handlers and store references
|
||||||
element._mouseenterHandler = () => {
|
element._mouseenterHandler = () => {
|
||||||
element.style.backgroundColor = 'rgba(33, 150, 243, 0.05)';
|
element.style.backgroundColor = 'rgba(0, 0, 0, 0.02)';
|
||||||
element.style.borderColor = 'rgba(33, 150, 243, 0.2)';
|
element.style.borderColor = 'rgba(0, 0, 0, 0.1)';
|
||||||
};
|
};
|
||||||
|
|
||||||
element._mouseleaveHandler = () => {
|
element._mouseleaveHandler = () => {
|
||||||
@@ -1370,62 +1522,36 @@ class MarkitectCleanEditor {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
background: white;
|
|
||||||
border: 2px solid #007acc;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
font-family: system-ui, -apple-system, sans-serif;
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add internal styling
|
// Add internal styling for structural layout (theme colors come from CSS)
|
||||||
const style = document.createElement('style');
|
const style = document.createElement('style');
|
||||||
style.textContent = `
|
style.textContent = `
|
||||||
#markitect-global-controls .control-header h3 {
|
.ui-edit-floater-header h3 {
|
||||||
margin: 0 0 8px 0;
|
margin: 0 0 8px 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: #007acc;
|
|
||||||
}
|
}
|
||||||
#markitect-global-controls .control-status {
|
.ui-edit-floater-status {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #666;
|
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
#markitect-global-controls .control-btn {
|
.ui-edit-button {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
border: 1px solid transparent;
|
||||||
#markitect-global-controls .control-btn.primary {
|
|
||||||
background: #007acc;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
#markitect-global-controls .control-btn.primary:hover {
|
|
||||||
background: #005a9f;
|
|
||||||
}
|
|
||||||
#markitect-global-controls .control-btn.warning {
|
|
||||||
background: #ff9800;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
#markitect-global-controls .control-btn.warning:hover {
|
|
||||||
background: #f57c00;
|
|
||||||
}
|
|
||||||
#markitect-global-controls .control-btn.secondary {
|
|
||||||
background: #6c757d;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
#markitect-global-controls .control-btn.secondary:hover {
|
|
||||||
background: #545b62;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
@@ -1451,13 +1577,13 @@ class MarkitectCleanEditor {
|
|||||||
|
|
||||||
if (editing > 0) {
|
if (editing > 0) {
|
||||||
statusEl.textContent = `Editing ${editing} section${editing !== 1 ? 's' : ''}`;
|
statusEl.textContent = `Editing ${editing} section${editing !== 1 ? 's' : ''}`;
|
||||||
statusEl.style.color = '#007acc';
|
statusEl.className = 'ui-edit-floater-status editing';
|
||||||
} else if (modified > 0) {
|
} else if (modified > 0) {
|
||||||
statusEl.textContent = `${modified} section${modified !== 1 ? 's' : ''} modified`;
|
statusEl.textContent = `${modified} section${modified !== 1 ? 's' : ''} modified`;
|
||||||
statusEl.style.color = '#ff9800';
|
statusEl.style.color = '';
|
||||||
} else {
|
} else {
|
||||||
statusEl.textContent = 'All sections saved';
|
statusEl.textContent = 'All sections saved ✓';
|
||||||
statusEl.style.color = '#28a745';
|
statusEl.style.color = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1572,32 +1698,158 @@ class MarkitectCleanEditor {
|
|||||||
// Get the actual save filename that will be used
|
// Get the actual save filename that will be used
|
||||||
const saveFilename = this.generateSaveFilename();
|
const saveFilename = this.generateSaveFilename();
|
||||||
|
|
||||||
const message = `${window.editorConfig.repoName} ${window.editorConfig.version}
|
// Create structured content for the modal
|
||||||
Save file: ${saveFilename}
|
const modalContent = {
|
||||||
|
title: `📊 ${window.editorConfig.repoName} Status`,
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
title: 'Application Information',
|
||||||
|
content: `${window.editorConfig.version}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'File Information',
|
||||||
|
content: `Save file: ${saveFilename}
|
||||||
Source: ${window.editorConfig.originalFilename}
|
Source: ${window.editorConfig.originalFilename}
|
||||||
${window.location.protocol}//${window.location.host}${window.location.pathname}
|
URL: ${window.location.protocol}//${window.location.host}${window.location.pathname}`
|
||||||
|
},
|
||||||
Document Status:
|
{
|
||||||
• Total sections: ${total}
|
title: 'Document Status',
|
||||||
|
content: `• Total sections: ${total}
|
||||||
• Modified sections: ${modified}
|
• Modified sections: ${modified}
|
||||||
• Currently editing: ${editing}
|
• Currently editing: ${editing}
|
||||||
• Unsaved changes: ${modified > 0 || editing > 0 ? 'Yes' : 'No'}
|
• Unsaved changes: ${modified > 0 || editing > 0 ? 'Yes' : 'No'}`
|
||||||
|
},
|
||||||
SECTION BEHAVIOR:
|
{
|
||||||
• Each section is a logical unit (heading + content until next heading)
|
title: 'Section Behavior',
|
||||||
|
content: `• Each section is a logical unit (heading + content until next heading)
|
||||||
• Content with line breaks stays in one section
|
• Content with line breaks stays in one section
|
||||||
• To split content: Create new headings (# ## ###)
|
• To split content: Create new headings (# ## ###)
|
||||||
• Sections don't auto-split on line breaks
|
• Sections don't auto-split on line breaks`
|
||||||
|
},
|
||||||
EDITING CONTROLS:
|
{
|
||||||
• Click any section to edit its content
|
title: 'Editing Controls',
|
||||||
|
content: `• Click any section to edit its content
|
||||||
• Accept (✓) - Save changes to that section
|
• Accept (✓) - Save changes to that section
|
||||||
• Cancel (✗) - Discard changes, return to previous state
|
• Cancel (✗) - Discard changes, return to previous state
|
||||||
• Reset (🔄) - Restore original content for that section
|
• Reset (🔄) - Restore original content for that section
|
||||||
• Save Document - Download all current content
|
• Save Document - Download all current content
|
||||||
• Reset All - Restore entire document to original state`;
|
• Reset All - Restore entire document to original state`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
alert(message);
|
this.showModal(modalContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal(content) {
|
||||||
|
// Remove any existing modal
|
||||||
|
const existingModal = document.querySelector('.ui-edit-modal-overlay');
|
||||||
|
if (existingModal) {
|
||||||
|
existingModal.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create modal overlay
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'ui-edit-modal-overlay';
|
||||||
|
|
||||||
|
// Create modal content
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'ui-edit-modal';
|
||||||
|
|
||||||
|
// Create header
|
||||||
|
const header = document.createElement('div');
|
||||||
|
header.className = 'ui-edit-modal-header';
|
||||||
|
|
||||||
|
const title = document.createElement('h3');
|
||||||
|
title.className = 'ui-edit-modal-title';
|
||||||
|
title.textContent = content.title;
|
||||||
|
|
||||||
|
const closeBtn = document.createElement('button');
|
||||||
|
closeBtn.className = 'ui-edit-modal-close';
|
||||||
|
closeBtn.innerHTML = '×';
|
||||||
|
closeBtn.setAttribute('aria-label', 'Close');
|
||||||
|
|
||||||
|
header.appendChild(title);
|
||||||
|
header.appendChild(closeBtn);
|
||||||
|
|
||||||
|
// Create body
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.className = 'ui-edit-modal-body';
|
||||||
|
|
||||||
|
// Add sections
|
||||||
|
content.sections.forEach(section => {
|
||||||
|
const sectionDiv = document.createElement('div');
|
||||||
|
sectionDiv.className = 'ui-edit-modal-section';
|
||||||
|
|
||||||
|
const sectionTitle = document.createElement('div');
|
||||||
|
sectionTitle.className = 'ui-edit-modal-section-title';
|
||||||
|
sectionTitle.textContent = section.title;
|
||||||
|
|
||||||
|
const sectionContent = document.createElement('div');
|
||||||
|
sectionContent.className = 'ui-edit-modal-content';
|
||||||
|
sectionContent.textContent = section.content;
|
||||||
|
|
||||||
|
sectionDiv.appendChild(sectionTitle);
|
||||||
|
sectionDiv.appendChild(sectionContent);
|
||||||
|
body.appendChild(sectionDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create footer with close button
|
||||||
|
const footer = document.createElement('div');
|
||||||
|
footer.className = 'ui-edit-modal-footer';
|
||||||
|
|
||||||
|
const footerCloseBtn = document.createElement('button');
|
||||||
|
footerCloseBtn.className = 'ui-edit-button ui-edit-button-accept';
|
||||||
|
footerCloseBtn.textContent = 'Close';
|
||||||
|
footer.appendChild(footerCloseBtn);
|
||||||
|
|
||||||
|
// Assemble modal
|
||||||
|
modal.appendChild(header);
|
||||||
|
modal.appendChild(body);
|
||||||
|
modal.appendChild(footer);
|
||||||
|
overlay.appendChild(modal);
|
||||||
|
|
||||||
|
// Add to page
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
// Close handlers
|
||||||
|
const closeModal = () => {
|
||||||
|
overlay.classList.remove('active');
|
||||||
|
setTimeout(() => {
|
||||||
|
if (overlay.parentNode) {
|
||||||
|
overlay.parentNode.removeChild(overlay);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
closeBtn.addEventListener('click', closeModal);
|
||||||
|
footerCloseBtn.addEventListener('click', closeModal);
|
||||||
|
|
||||||
|
// Close on overlay click (but not modal content)
|
||||||
|
overlay.addEventListener('click', (e) => {
|
||||||
|
if (e.target === overlay) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close on Escape key
|
||||||
|
const handleKeydown = (e) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
closeModal();
|
||||||
|
document.removeEventListener('keydown', handleKeydown);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', handleKeydown);
|
||||||
|
|
||||||
|
// Show modal with animation
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
overlay.classList.add('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Focus management
|
||||||
|
setTimeout(() => {
|
||||||
|
closeBtn.focus();
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessage(message, type = 'info') {
|
showMessage(message, type = 'info') {
|
||||||
@@ -1666,4 +1918,4 @@ if (typeof module !== 'undefined' && module.exports) {
|
|||||||
} else {
|
} else {
|
||||||
window.MarkitectEditor = { Section, SectionManager, DOMRenderer, MarkitectCleanEditor };
|
window.MarkitectEditor = { Section, SectionManager, DOMRenderer, MarkitectCleanEditor };
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -88,7 +88,15 @@ LAYERED_THEMES = {
|
|||||||
'editor_button_active': '#d4d4d4',
|
'editor_button_active': '#d4d4d4',
|
||||||
'editor_text_color': '#333333',
|
'editor_text_color': '#333333',
|
||||||
'editor_focus_color': '#666666',
|
'editor_focus_color': '#666666',
|
||||||
'editor_shadow': 'rgba(0,0,0,0.15)'
|
'editor_shadow': 'rgba(0,0,0,0.15)',
|
||||||
|
'editor_accept_bg': '#888888',
|
||||||
|
'editor_accept_hover': '#777777',
|
||||||
|
'editor_cancel_bg': '#999999',
|
||||||
|
'editor_cancel_hover': '#808080',
|
||||||
|
'editor_reset_bg': '#aaaaaa',
|
||||||
|
'editor_reset_hover': '#999999',
|
||||||
|
'editor_secondary_bg': '#bbbbbb',
|
||||||
|
'editor_secondary_hover': '#aaaaaa'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'electric': {
|
'electric': {
|
||||||
@@ -101,7 +109,7 @@ LAYERED_THEMES = {
|
|||||||
'editor_button_active': '#0099ff',
|
'editor_button_active': '#0099ff',
|
||||||
'editor_text_color': '#00ffff',
|
'editor_text_color': '#00ffff',
|
||||||
'editor_focus_color': '#ffff00',
|
'editor_focus_color': '#ffff00',
|
||||||
'editor_shadow': 'rgba(0,255,255,0.3)'
|
'editor_shadow': '0 0 20px rgba(0,255,255,0.5), 0 0 40px rgba(255,255,0,0.2)'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'psychedelic': {
|
'psychedelic': {
|
||||||
@@ -1938,9 +1946,11 @@ def md_list_command(ctx, output_format, names_only):
|
|||||||
help='Use publication directory for output')
|
help='Use publication directory for output')
|
||||||
@click.option('--dont-use-publication-dir', is_flag=True,
|
@click.option('--dont-use-publication-dir', is_flag=True,
|
||||||
help='Don\'t use publication directory for output')
|
help='Don\'t use publication directory for output')
|
||||||
|
@click.option('--nodogtag', is_flag=True,
|
||||||
|
help='Don\'t add HTML generation dogtag at end of document')
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def md_render_command(ctx, input_file, output, theme, css, edit, editor_theme,
|
def md_render_command(ctx, input_file, output, theme, css, edit, editor_theme,
|
||||||
keyboard_shortcuts, use_publication_dir, dont_use_publication_dir):
|
keyboard_shortcuts, use_publication_dir, dont_use_publication_dir, nodogtag):
|
||||||
"""
|
"""
|
||||||
Render a markdown file to HTML with basic templates and live preview capabilities.
|
Render a markdown file to HTML with basic templates and live preview capabilities.
|
||||||
|
|
||||||
@@ -1990,7 +2000,8 @@ def md_render_command(ctx, input_file, output, theme, css, edit, editor_theme,
|
|||||||
template=theme, css=css,
|
template=theme, css=css,
|
||||||
edit_mode=True,
|
edit_mode=True,
|
||||||
editor_theme=editor_theme,
|
editor_theme=editor_theme,
|
||||||
keyboard_shortcuts=keyboard_shortcuts)
|
keyboard_shortcuts=keyboard_shortcuts,
|
||||||
|
nodogtag=nodogtag)
|
||||||
|
|
||||||
click.echo(f"✓ Rendered with interactive editing capabilities to: {output_path}")
|
click.echo(f"✓ Rendered with interactive editing capabilities to: {output_path}")
|
||||||
|
|
||||||
@@ -2002,7 +2013,8 @@ def md_render_command(ctx, input_file, output, theme, css, edit, editor_theme,
|
|||||||
else:
|
else:
|
||||||
# Static render
|
# Static render
|
||||||
result = doc_manager.render_file(input_file, str(output_path),
|
result = doc_manager.render_file(input_file, str(output_path),
|
||||||
template=theme, css=css)
|
template=theme, css=css,
|
||||||
|
nodogtag=nodogtag)
|
||||||
click.echo(f"✓ Rendered to: {output_path}")
|
click.echo(f"✓ Rendered to: {output_path}")
|
||||||
|
|
||||||
if config.get('verbose', False):
|
if config.get('verbose', False):
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ This is a **test** document with some *italic* text and a [link](https://example
|
|||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file)])
|
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file), '--nodogtag'])
|
||||||
|
|
||||||
# Should execute successfully
|
# Should execute successfully
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
@@ -99,7 +99,7 @@ This is a **test** document with some *italic* text and a [link](https://example
|
|||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file)])
|
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file), '--nodogtag'])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert output_file.exists()
|
assert output_file.exists()
|
||||||
@@ -128,7 +128,7 @@ This is a **test** document with some *italic* text and a [link](https://example
|
|||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file)])
|
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file), '--nodogtag'])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert output_file.exists()
|
assert output_file.exists()
|
||||||
@@ -155,7 +155,7 @@ This is a **test** document with some *italic* text and a [link](https://example
|
|||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file)])
|
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file), '--nodogtag'])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert output_file.exists()
|
assert output_file.exists()
|
||||||
@@ -185,7 +185,7 @@ This is a **test** document with some *italic* text and a [link](https://example
|
|||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file)])
|
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file), '--nodogtag'])
|
||||||
|
|
||||||
# Should handle empty file gracefully
|
# Should handle empty file gracefully
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
@@ -221,7 +221,7 @@ And some inline `code` too.
|
|||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file)])
|
result = runner.invoke(md_render_command, [str(input_file), '--output', str(output_file), '--nodogtag'])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert output_file.exists()
|
assert output_file.exists()
|
||||||
|
|||||||
@@ -73,9 +73,9 @@ Content paragraph that should be editable.
|
|||||||
html_content = output_file.read_text()
|
html_content = output_file.read_text()
|
||||||
|
|
||||||
# Should include editor library and edit mode flag
|
# Should include editor library and edit mode flag
|
||||||
assert 'markitect-floating-header' in html_content
|
assert 'ui-edit-floater-panel' in html_content
|
||||||
assert 'MARKITECT_EDIT_MODE' in html_content
|
assert 'MARKITECT_EDIT_MODE' in html_content
|
||||||
assert 'MarkitectEditor' in html_content
|
assert 'MarkitectCleanEditor' in html_content
|
||||||
|
|
||||||
def test_edit_flag_with_all_templates(self):
|
def test_edit_flag_with_all_templates(self):
|
||||||
"""Test --edit flag works with all template types - Issue #133."""
|
"""Test --edit flag works with all template types - Issue #133."""
|
||||||
@@ -103,8 +103,8 @@ Content paragraph that should be editable.
|
|||||||
|
|
||||||
html_content = output_file.read_text()
|
html_content = output_file.read_text()
|
||||||
# Should work with template styles
|
# Should work with template styles
|
||||||
assert 'markitect-floating-header' in html_content
|
assert 'ui-edit-floater-panel' in html_content
|
||||||
assert 'MarkitectEditor' in html_content
|
assert 'MarkitectCleanEditor' in html_content
|
||||||
|
|
||||||
def test_editor_library_loading_configuration(self):
|
def test_editor_library_loading_configuration(self):
|
||||||
"""Test editor library loading and configuration options - Issue #133."""
|
"""Test editor library loading and configuration options - Issue #133."""
|
||||||
@@ -145,7 +145,8 @@ Content paragraph that should be editable.
|
|||||||
'md-render',
|
'md-render',
|
||||||
str(input_file),
|
str(input_file),
|
||||||
'--output', str(output_file),
|
'--output', str(output_file),
|
||||||
'--theme', 'github'
|
'--theme', 'github',
|
||||||
|
'--nodogtag'
|
||||||
])
|
])
|
||||||
|
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
@@ -155,7 +156,7 @@ Content paragraph that should be editable.
|
|||||||
|
|
||||||
# Should NOT include editor library without --edit flag
|
# Should NOT include editor library without --edit flag
|
||||||
assert 'markitect-editor' not in html_content
|
assert 'markitect-editor' not in html_content
|
||||||
assert 'MARKITECT_EDIT_MODE' not in html_content
|
assert 'const MARKITECT_EDIT_MODE = true' not in html_content
|
||||||
|
|
||||||
# Should include existing functionality
|
# Should include existing functionality
|
||||||
assert 'marked.min.js' in html_content
|
assert 'marked.min.js' in html_content
|
||||||
@@ -208,8 +209,8 @@ Content paragraph that should be editable.
|
|||||||
|
|
||||||
# Should include both custom CSS and editor
|
# Should include both custom CSS and editor
|
||||||
assert 'Courier New' in html_content
|
assert 'Courier New' in html_content
|
||||||
assert 'markitect-floating-header' in html_content
|
assert 'ui-edit-floater-panel' in html_content
|
||||||
assert 'MarkitectEditor' in html_content
|
assert 'MarkitectCleanEditor' in html_content
|
||||||
|
|
||||||
def test_large_document_editing_performance(self):
|
def test_large_document_editing_performance(self):
|
||||||
"""Test editing flag with large markdown documents - Issue #133."""
|
"""Test editing flag with large markdown documents - Issue #133."""
|
||||||
@@ -236,7 +237,7 @@ Content paragraph that should be editable.
|
|||||||
|
|
||||||
# Should handle large documents gracefully
|
# Should handle large documents gracefully
|
||||||
assert len(html_content) > 20000 # Should be substantial (adjusted from 50k)
|
assert len(html_content) > 20000 # Should be substantial (adjusted from 50k)
|
||||||
assert 'MarkitectEditor' in html_content
|
assert 'MarkitectCleanEditor' in html_content
|
||||||
assert 'MARKITECT_EDIT_MODE' in html_content
|
assert 'MARKITECT_EDIT_MODE' in html_content
|
||||||
|
|
||||||
def test_front_matter_preservation_with_editing(self):
|
def test_front_matter_preservation_with_editing(self):
|
||||||
@@ -273,7 +274,7 @@ This content should be editable while preserving front matter.
|
|||||||
|
|
||||||
# Should preserve front matter in JavaScript payload and include editing
|
# Should preserve front matter in JavaScript payload and include editing
|
||||||
assert 'Test Author' in html_content or 'Editable Document' in html_content
|
assert 'Test Author' in html_content or 'Editable Document' in html_content
|
||||||
assert 'MarkitectEditor' in html_content
|
assert 'MarkitectCleanEditor' in html_content
|
||||||
assert 'MARKITECT_EDIT_MODE' in html_content
|
assert 'MARKITECT_EDIT_MODE' in html_content
|
||||||
|
|
||||||
def test_error_handling_invalid_edit_options(self):
|
def test_error_handling_invalid_edit_options(self):
|
||||||
@@ -316,7 +317,7 @@ This content should be editable while preserving front matter.
|
|||||||
html_content = output_file.read_text()
|
html_content = output_file.read_text()
|
||||||
|
|
||||||
# Should include bundled editor (not relying on CDN)
|
# Should include bundled editor (not relying on CDN)
|
||||||
assert 'MarkitectEditor' in html_content
|
assert 'MarkitectCleanEditor' in html_content
|
||||||
assert 'MARKITECT_EDIT_MODE' in html_content
|
assert 'MARKITECT_EDIT_MODE' in html_content
|
||||||
# The implementation uses bundled JavaScript, not CDN, so no fallback needed
|
# The implementation uses bundled JavaScript, not CDN, so no fallback needed
|
||||||
|
|
||||||
@@ -343,7 +344,7 @@ This content should be editable while preserving front matter.
|
|||||||
# Should include mobile-friendly meta tags
|
# Should include mobile-friendly meta tags
|
||||||
assert 'viewport' in html_content
|
assert 'viewport' in html_content
|
||||||
assert 'width=device-width' in html_content
|
assert 'width=device-width' in html_content
|
||||||
assert 'MarkitectEditor' in html_content
|
assert 'MarkitectCleanEditor' in html_content
|
||||||
|
|
||||||
def test_keyboard_shortcuts_configuration(self):
|
def test_keyboard_shortcuts_configuration(self):
|
||||||
"""Test keyboard shortcuts can be configured for editing - Issue #133."""
|
"""Test keyboard shortcuts can be configured for editing - Issue #133."""
|
||||||
@@ -430,4 +431,4 @@ def example_function():
|
|||||||
|
|
||||||
# Should detect and mark various section types
|
# Should detect and mark various section types
|
||||||
assert 'data-section' in html_content or 'markitect-section-editable' in html_content
|
assert 'data-section' in html_content or 'markitect-section-editable' in html_content
|
||||||
assert 'MarkitectEditor' in html_content
|
assert 'MarkitectCleanEditor' in html_content
|
||||||
@@ -233,7 +233,7 @@ class TestIndexPageGeneration:
|
|||||||
{"path": self.test_dir / "doc1.html", "title": "Document One", "relative_path": "doc1.html"}
|
{"path": self.test_dir / "doc1.html", "title": "Document One", "relative_path": "doc1.html"}
|
||||||
]
|
]
|
||||||
|
|
||||||
html_content = generate_index_html(html_files, "Test Index", template="github")
|
html_content = generate_index_html(html_files, "Test Index", theme="github")
|
||||||
|
|
||||||
parser = SimpleHTMLParser()
|
parser = SimpleHTMLParser()
|
||||||
parser.feed(html_content)
|
parser.feed(html_content)
|
||||||
|
|||||||
@@ -17,14 +17,10 @@ class TestEditModeRegression:
|
|||||||
|
|
||||||
def test_edit_mode_generates_valid_javascript(self):
|
def test_edit_mode_generates_valid_javascript(self):
|
||||||
"""Test that edit mode generates syntactically valid JavaScript."""
|
"""Test that edit mode generates syntactically valid JavaScript."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
|
|
||||||
# Test markdown content
|
# Test markdown content
|
||||||
test_content = "# Test Header\n\nThis is a test paragraph.\n\n## Section 2\n\nAnother paragraph."
|
test_content = "# Test Header\n\nThis is a test paragraph.\n\n## Section 2\n\nAnother paragraph."
|
||||||
@@ -64,14 +60,10 @@ class TestEditModeRegression:
|
|||||||
|
|
||||||
def test_edit_mode_contains_required_functions(self):
|
def test_edit_mode_contains_required_functions(self):
|
||||||
"""Test that edit mode HTML contains all required JavaScript functions."""
|
"""Test that edit mode HTML contains all required JavaScript functions."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
|
|
||||||
html_content = doc_manager._generate_html_template(
|
html_content = doc_manager._generate_html_template(
|
||||||
title="Test",
|
title="Test",
|
||||||
@@ -81,12 +73,11 @@ class TestEditModeRegression:
|
|||||||
|
|
||||||
# Check for critical functions that must be present
|
# Check for critical functions that must be present
|
||||||
required_functions = [
|
required_functions = [
|
||||||
'MarkitectEditor',
|
'MarkitectCleanEditor',
|
||||||
'updateStatus',
|
'SectionManager',
|
||||||
'reportEditModeError',
|
'Section',
|
||||||
'makeContentEditable',
|
'DOMRenderer',
|
||||||
'handleSectionClick',
|
'initializeCleanEditor'
|
||||||
'editSection'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
for func_name in required_functions:
|
for func_name in required_functions:
|
||||||
@@ -94,14 +85,10 @@ class TestEditModeRegression:
|
|||||||
|
|
||||||
def test_edit_mode_no_broken_string_literals(self):
|
def test_edit_mode_no_broken_string_literals(self):
|
||||||
"""Test that there are no broken string literals in the generated JavaScript."""
|
"""Test that there are no broken string literals in the generated JavaScript."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
|
|
||||||
html_content = doc_manager._generate_html_template(
|
html_content = doc_manager._generate_html_template(
|
||||||
title="Test",
|
title="Test",
|
||||||
@@ -126,14 +113,10 @@ class TestEditModeRegression:
|
|||||||
|
|
||||||
def test_edit_mode_proper_brace_escaping(self):
|
def test_edit_mode_proper_brace_escaping(self):
|
||||||
"""Test that braces are properly escaped in f-string templates."""
|
"""Test that braces are properly escaped in f-string templates."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
|
|
||||||
html_content = doc_manager._generate_html_template(
|
html_content = doc_manager._generate_html_template(
|
||||||
title="Test",
|
title="Test",
|
||||||
@@ -157,14 +140,10 @@ class TestEditModeRegression:
|
|||||||
|
|
||||||
def test_edit_mode_template_literal_syntax(self):
|
def test_edit_mode_template_literal_syntax(self):
|
||||||
"""Test that template literals are properly escaped."""
|
"""Test that template literals are properly escaped."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
|
|
||||||
html_content = doc_manager._generate_html_template(
|
html_content = doc_manager._generate_html_template(
|
||||||
title="Test",
|
title="Test",
|
||||||
@@ -188,14 +167,10 @@ class TestEditModeRegression:
|
|||||||
|
|
||||||
def test_edit_mode_contains_content_div(self):
|
def test_edit_mode_contains_content_div(self):
|
||||||
"""Test that edit mode HTML contains the markdown-content div."""
|
"""Test that edit mode HTML contains the markdown-content div."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
|
|
||||||
html_content = doc_manager._generate_html_template(
|
html_content = doc_manager._generate_html_template(
|
||||||
title="Test",
|
title="Test",
|
||||||
@@ -210,14 +185,10 @@ class TestEditModeRegression:
|
|||||||
|
|
||||||
def test_edit_mode_error_handling_elements(self):
|
def test_edit_mode_error_handling_elements(self):
|
||||||
"""Test that edit mode includes proper error handling UI elements."""
|
"""Test that edit mode includes proper error handling UI elements."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
|
|
||||||
html_content = doc_manager._generate_html_template(
|
html_content = doc_manager._generate_html_template(
|
||||||
title="Test",
|
title="Test",
|
||||||
@@ -225,22 +196,18 @@ class TestEditModeRegression:
|
|||||||
edit_mode=True
|
edit_mode=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Should contain error handling elements
|
# Should contain clean editor elements
|
||||||
assert 'id="markitect-control-panel"' in html_content
|
assert 'MARKITECT_EDIT_MODE' in html_content
|
||||||
assert 'id="status-message"' in html_content
|
assert 'class="markitect-edit-mode"' in html_content
|
||||||
assert 'id="error-details"' in html_content
|
assert 'initializeCleanEditor' in html_content
|
||||||
assert 'reportEditModeError' in html_content
|
assert 'console.error' in html_content # Error handling
|
||||||
|
|
||||||
def test_edit_mode_vs_normal_mode_differences(self):
|
def test_edit_mode_vs_normal_mode_differences(self):
|
||||||
"""Test that edit mode and normal mode generate different output appropriately."""
|
"""Test that edit mode and normal mode generate different output appropriately."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
test_content = "# Test Header\n\nTest content."
|
test_content = "# Test Header\n\nTest content."
|
||||||
|
|
||||||
# Generate both modes
|
# Generate both modes
|
||||||
@@ -265,14 +232,10 @@ class TestEditModeRegression:
|
|||||||
|
|
||||||
def test_edit_mode_javascript_execution_flow(self):
|
def test_edit_mode_javascript_execution_flow(self):
|
||||||
"""Test the logical flow of JavaScript execution in edit mode."""
|
"""Test the logical flow of JavaScript execution in edit mode."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
|
|
||||||
html_content = doc_manager._generate_html_template(
|
html_content = doc_manager._generate_html_template(
|
||||||
title="Test",
|
title="Test",
|
||||||
@@ -288,9 +251,9 @@ class TestEditModeRegression:
|
|||||||
flow_elements = [
|
flow_elements = [
|
||||||
'DOMContentLoaded', # Event listener setup
|
'DOMContentLoaded', # Event listener setup
|
||||||
'MARKITECT_EDIT_MODE', # Mode check
|
'MARKITECT_EDIT_MODE', # Mode check
|
||||||
'new MarkitectEditor', # Editor instantiation
|
'initializeCleanEditor', # Editor initialization
|
||||||
'makeContentEditable', # Content enhancement
|
'marked.parse', # Content rendering
|
||||||
'handleSectionClick' # Interaction handler
|
'MarkitectCleanEditor' # Clean editor class
|
||||||
]
|
]
|
||||||
|
|
||||||
for element in flow_elements:
|
for element in flow_elements:
|
||||||
@@ -298,14 +261,10 @@ class TestEditModeRegression:
|
|||||||
|
|
||||||
def test_newline_escaping_in_javascript_strings(self):
|
def test_newline_escaping_in_javascript_strings(self):
|
||||||
"""Test that newlines in JavaScript strings are properly escaped."""
|
"""Test that newlines in JavaScript strings are properly escaped."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
|
|
||||||
html_content = doc_manager._generate_html_template(
|
html_content = doc_manager._generate_html_template(
|
||||||
title="Test",
|
title="Test",
|
||||||
@@ -333,14 +292,10 @@ class TestEditModeIntegration:
|
|||||||
|
|
||||||
def test_save_functionality_javascript_presence(self):
|
def test_save_functionality_javascript_presence(self):
|
||||||
"""Test that the save functionality JavaScript is properly included."""
|
"""Test that the save functionality JavaScript is properly included."""
|
||||||
from markitect.document_manager import DocumentManager
|
from markitect.clean_document_manager import CleanDocumentManager
|
||||||
|
|
||||||
# Create a mock DocumentManager to avoid database dependency
|
# Create a CleanDocumentManager
|
||||||
class MockDatabaseManager:
|
doc_manager = CleanDocumentManager()
|
||||||
pass
|
|
||||||
|
|
||||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
|
||||||
doc_manager.database_manager = MockDatabaseManager()
|
|
||||||
|
|
||||||
html_content = doc_manager._generate_html_template(
|
html_content = doc_manager._generate_html_template(
|
||||||
title="Test",
|
title="Test",
|
||||||
@@ -350,9 +305,9 @@ class TestEditModeIntegration:
|
|||||||
|
|
||||||
# Check for save-related functionality
|
# Check for save-related functionality
|
||||||
save_elements = [
|
save_elements = [
|
||||||
'Save & Download', # Button text
|
'💾 Save Document', # Button text from clean implementation
|
||||||
'markitectEditor.save()', # Save function call
|
'generateSaveFilename', # Save filename generation
|
||||||
'getMarkdownContent', # Content extraction
|
'getDocumentMarkdown', # Content extraction
|
||||||
'Blob', # File creation
|
'Blob', # File creation
|
||||||
'download' # Download attribute
|
'download' # Download attribute
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user