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
|
||||
- **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):
|
||||
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,
|
||||
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.
|
||||
"""
|
||||
@@ -49,7 +88,8 @@ class CleanDocumentManager:
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts,
|
||||
original_filename=original_filename,
|
||||
version_info=version_info
|
||||
version_info=version_info,
|
||||
nodogtag=nodogtag
|
||||
)
|
||||
|
||||
# Write HTML file
|
||||
@@ -73,51 +113,17 @@ class CleanDocumentManager:
|
||||
|
||||
def _get_version_info(self) -> dict:
|
||||
"""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',
|
||||
'version': '0.3.0',
|
||||
'git_info': ''
|
||||
'version': version_info['full_version'],
|
||||
'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:
|
||||
"""Generate layered theme CSS styles."""
|
||||
# Import layered theme functions
|
||||
@@ -310,10 +316,29 @@ class CleanDocumentManager:
|
||||
.markitect-edit-mode .ui-edit-floater-header {{
|
||||
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 {{
|
||||
background: {props.get('editor_button_bg', '#ffffff')};
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
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 {{
|
||||
background: {props.get('editor_button_hover', '#e9ecef')};
|
||||
@@ -323,17 +348,36 @@ class CleanDocumentManager:
|
||||
background: {props.get('editor_button_active', '#dee2e6')};
|
||||
}}
|
||||
.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 {{
|
||||
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 {{
|
||||
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 {{
|
||||
border: 2px solid {props.get('editor_focus_color', '#0066cc')};
|
||||
box-shadow: 0 0 0 3px {props.get('editor_focus_color', '#0066cc')}33;
|
||||
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', props.get('editor_panel_border', '#dee2e6'))}33;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-textarea {{
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
@@ -341,8 +385,103 @@ class CleanDocumentManager:
|
||||
background: {props.get('editor_button_bg', '#ffffff')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-textarea:focus {{
|
||||
border-color: {props.get('editor_focus_color', '#0066cc')};
|
||||
box-shadow: 0 0 0 2px {props.get('editor_focus_color', '#0066cc')}33;
|
||||
border-color: {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))};
|
||||
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>"
|
||||
@@ -382,11 +521,34 @@ class CleanDocumentManager:
|
||||
return self._generate_layered_css(layered_props)
|
||||
|
||||
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."""
|
||||
|
||||
# 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
|
||||
js_markdown_content = json.dumps(markdown_content)
|
||||
js_markdown_content = json.dumps(markdown_content_with_dogtag)
|
||||
|
||||
# Handle CSS styles
|
||||
css_content = ""
|
||||
@@ -413,7 +575,7 @@ class CleanDocumentManager:
|
||||
body_classes = ' class="markitect-edit-mode"'
|
||||
|
||||
# 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"""
|
||||
const MARKITECT_EDIT_MODE = true;
|
||||
const MARKITECT_EDITOR_CONFIG = {{
|
||||
@@ -466,7 +628,10 @@ class CleanDocumentManager:
|
||||
if (contentDiv) {{
|
||||
if (typeof marked !== 'undefined') {{
|
||||
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('✓ Markdown rendered successfully');
|
||||
}} catch (error) {{
|
||||
@@ -1017,7 +1182,10 @@ class DOMRenderer {
|
||||
element.setAttribute('data-section-id', section.id);
|
||||
|
||||
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 {
|
||||
element.innerHTML = `<p>${section.currentMarkdown}</p>`;
|
||||
}
|
||||
@@ -1029,8 +1197,8 @@ class DOMRenderer {
|
||||
}
|
||||
|
||||
handleSectionClick(event) {
|
||||
// Don't handle clicks on form elements or buttons
|
||||
if (event.target.closest('textarea, button, input')) {
|
||||
// Don't handle clicks on form elements, buttons, or links
|
||||
if (event.target.closest('textarea, button, input, a')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1062,6 +1230,7 @@ class DOMRenderer {
|
||||
this.hideCurrentEditor();
|
||||
|
||||
const editorContainer = document.createElement('div');
|
||||
editorContainer.className = 'ui-edit-inline-panel';
|
||||
editorContainer.style.cssText = `
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
@@ -1076,7 +1245,6 @@ class DOMRenderer {
|
||||
flex: 1;
|
||||
min-height: 100px;
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
border: 2px solid #007acc;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
@@ -1096,36 +1264,17 @@ class DOMRenderer {
|
||||
gap: 6px;
|
||||
`;
|
||||
|
||||
const createButton = (text, color, handler) => {
|
||||
const createButton = (text, className, handler) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = text;
|
||||
|
||||
// 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.className = className;
|
||||
btn.addEventListener('click', handler);
|
||||
return btn;
|
||||
};
|
||||
|
||||
controls.appendChild(createButton('✓ Accept', '#4caf50', () => this.handleAccept(sectionId)));
|
||||
controls.appendChild(createButton('✗ Cancel', '#f44336', () => this.handleCancel(sectionId)));
|
||||
controls.appendChild(createButton('🔄 Reset', '#ff9800', () => this.handleReset(sectionId)));
|
||||
controls.appendChild(createButton('✓ Accept', 'ui-edit-button ui-edit-button-accept', () => this.handleAccept(sectionId)));
|
||||
controls.appendChild(createButton('✗ Cancel', 'ui-edit-button ui-edit-button-cancel', () => this.handleCancel(sectionId)));
|
||||
controls.appendChild(createButton('🔄 Reset', 'ui-edit-button ui-edit-button-reset', () => this.handleReset(sectionId)));
|
||||
|
||||
editorContainer.appendChild(textarea);
|
||||
editorContainer.appendChild(controls);
|
||||
@@ -1159,7 +1308,10 @@ class DOMRenderer {
|
||||
if (!element) return;
|
||||
|
||||
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 {
|
||||
element.innerHTML = `<p>${content}</p>`;
|
||||
}
|
||||
@@ -1169,7 +1321,7 @@ class DOMRenderer {
|
||||
}
|
||||
|
||||
setupSectionElement(element) {
|
||||
element.className = 'ui-edit-section ui-edit-section-frame';
|
||||
element.className = 'ui-edit-section';
|
||||
element.style.cssText = `
|
||||
margin: 16px 0;
|
||||
padding: 12px;
|
||||
@@ -1185,8 +1337,8 @@ class DOMRenderer {
|
||||
|
||||
// Create new handlers and store references
|
||||
element._mouseenterHandler = () => {
|
||||
element.style.backgroundColor = 'rgba(33, 150, 243, 0.05)';
|
||||
element.style.borderColor = 'rgba(33, 150, 243, 0.2)';
|
||||
element.style.backgroundColor = 'rgba(0, 0, 0, 0.02)';
|
||||
element.style.borderColor = 'rgba(0, 0, 0, 0.1)';
|
||||
};
|
||||
|
||||
element._mouseleaveHandler = () => {
|
||||
@@ -1370,62 +1522,36 @@ class MarkitectCleanEditor {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: white;
|
||||
border: 2px solid #007acc;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 1000;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
min-width: 200px;
|
||||
max-width: 250px;
|
||||
`;
|
||||
|
||||
// Add internal styling
|
||||
// Add internal styling for structural layout (theme colors come from CSS)
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#markitect-global-controls .control-header h3 {
|
||||
.ui-edit-floater-header h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
color: #007acc;
|
||||
}
|
||||
#markitect-global-controls .control-status {
|
||||
.ui-edit-floater-status {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
#markitect-global-controls .control-btn {
|
||||
.ui-edit-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 6px 0;
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
#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;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
@@ -1451,13 +1577,13 @@ class MarkitectCleanEditor {
|
||||
|
||||
if (editing > 0) {
|
||||
statusEl.textContent = `Editing ${editing} section${editing !== 1 ? 's' : ''}`;
|
||||
statusEl.style.color = '#007acc';
|
||||
statusEl.className = 'ui-edit-floater-status editing';
|
||||
} else if (modified > 0) {
|
||||
statusEl.textContent = `${modified} section${modified !== 1 ? 's' : ''} modified`;
|
||||
statusEl.style.color = '#ff9800';
|
||||
statusEl.style.color = '';
|
||||
} else {
|
||||
statusEl.textContent = 'All sections saved';
|
||||
statusEl.style.color = '#28a745';
|
||||
statusEl.textContent = 'All sections saved ✓';
|
||||
statusEl.style.color = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1572,32 +1698,158 @@ class MarkitectCleanEditor {
|
||||
// Get the actual save filename that will be used
|
||||
const saveFilename = this.generateSaveFilename();
|
||||
|
||||
const message = `${window.editorConfig.repoName} ${window.editorConfig.version}
|
||||
Save file: ${saveFilename}
|
||||
// Create structured content for the modal
|
||||
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}
|
||||
${window.location.protocol}//${window.location.host}${window.location.pathname}
|
||||
|
||||
Document Status:
|
||||
• Total sections: ${total}
|
||||
URL: ${window.location.protocol}//${window.location.host}${window.location.pathname}`
|
||||
},
|
||||
{
|
||||
title: 'Document Status',
|
||||
content: `• Total sections: ${total}
|
||||
• Modified sections: ${modified}
|
||||
• Currently editing: ${editing}
|
||||
• Unsaved changes: ${modified > 0 || editing > 0 ? 'Yes' : 'No'}
|
||||
|
||||
SECTION BEHAVIOR:
|
||||
• Each section is a logical unit (heading + content until next heading)
|
||||
• Unsaved changes: ${modified > 0 || editing > 0 ? 'Yes' : 'No'}`
|
||||
},
|
||||
{
|
||||
title: 'Section Behavior',
|
||||
content: `• Each section is a logical unit (heading + content until next heading)
|
||||
• Content with line breaks stays in one section
|
||||
• To split content: Create new headings (# ## ###)
|
||||
• Sections don't auto-split on line breaks
|
||||
|
||||
EDITING CONTROLS:
|
||||
• Click any section to edit its content
|
||||
• Sections don't auto-split on line breaks`
|
||||
},
|
||||
{
|
||||
title: 'Editing Controls',
|
||||
content: `• Click any section to edit its content
|
||||
• Accept (✓) - Save changes to that section
|
||||
• Cancel (✗) - Discard changes, return to previous state
|
||||
• Reset (🔄) - Restore original content for that section
|
||||
• 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') {
|
||||
@@ -1666,4 +1918,4 @@ if (typeof module !== 'undefined' && module.exports) {
|
||||
} else {
|
||||
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_text_color': '#333333',
|
||||
'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': {
|
||||
@@ -101,7 +109,7 @@ LAYERED_THEMES = {
|
||||
'editor_button_active': '#0099ff',
|
||||
'editor_text_color': '#00ffff',
|
||||
'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': {
|
||||
@@ -1938,9 +1946,11 @@ def md_list_command(ctx, output_format, names_only):
|
||||
help='Use publication directory for output')
|
||||
@click.option('--dont-use-publication-dir', is_flag=True,
|
||||
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
|
||||
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.
|
||||
|
||||
@@ -1990,7 +2000,8 @@ def md_render_command(ctx, input_file, output, theme, css, edit, editor_theme,
|
||||
template=theme, css=css,
|
||||
edit_mode=True,
|
||||
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}")
|
||||
|
||||
@@ -2002,7 +2013,8 @@ def md_render_command(ctx, input_file, output, theme, css, edit, editor_theme,
|
||||
else:
|
||||
# Static render
|
||||
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}")
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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 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
|
||||
|
||||
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 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
|
||||
|
||||
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 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
|
||||
|
||||
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
|
||||
assert result.exit_code == 0
|
||||
@@ -221,7 +221,7 @@ And some inline `code` too.
|
||||
from click.testing import 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 output_file.exists()
|
||||
|
||||
@@ -73,9 +73,9 @@ Content paragraph that should be editable.
|
||||
html_content = output_file.read_text()
|
||||
|
||||
# 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 'MarkitectEditor' in html_content
|
||||
assert 'MarkitectCleanEditor' in html_content
|
||||
|
||||
def test_edit_flag_with_all_templates(self):
|
||||
"""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()
|
||||
# Should work with template styles
|
||||
assert 'markitect-floating-header' in html_content
|
||||
assert 'MarkitectEditor' in html_content
|
||||
assert 'ui-edit-floater-panel' in html_content
|
||||
assert 'MarkitectCleanEditor' in html_content
|
||||
|
||||
def test_editor_library_loading_configuration(self):
|
||||
"""Test editor library loading and configuration options - Issue #133."""
|
||||
@@ -145,7 +145,8 @@ Content paragraph that should be editable.
|
||||
'md-render',
|
||||
str(input_file),
|
||||
'--output', str(output_file),
|
||||
'--theme', 'github'
|
||||
'--theme', 'github',
|
||||
'--nodogtag'
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
@@ -155,7 +156,7 @@ Content paragraph that should be editable.
|
||||
|
||||
# Should NOT include editor library without --edit flag
|
||||
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
|
||||
assert 'marked.min.js' in html_content
|
||||
@@ -208,8 +209,8 @@ Content paragraph that should be editable.
|
||||
|
||||
# Should include both custom CSS and editor
|
||||
assert 'Courier New' in html_content
|
||||
assert 'markitect-floating-header' in html_content
|
||||
assert 'MarkitectEditor' in html_content
|
||||
assert 'ui-edit-floater-panel' in html_content
|
||||
assert 'MarkitectCleanEditor' in html_content
|
||||
|
||||
def test_large_document_editing_performance(self):
|
||||
"""Test editing flag with large markdown documents - Issue #133."""
|
||||
@@ -236,7 +237,7 @@ Content paragraph that should be editable.
|
||||
|
||||
# Should handle large documents gracefully
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
# 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
|
||||
# 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
|
||||
assert 'viewport' 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):
|
||||
"""Test keyboard shortcuts can be configured for editing - Issue #133."""
|
||||
@@ -430,4 +431,4 @@ def example_function():
|
||||
|
||||
# Should detect and mark various section types
|
||||
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"}
|
||||
]
|
||||
|
||||
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.feed(html_content)
|
||||
|
||||
@@ -17,14 +17,10 @@ class TestEditModeRegression:
|
||||
|
||||
def test_edit_mode_generates_valid_javascript(self):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
|
||||
# Test markdown content
|
||||
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):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
|
||||
html_content = doc_manager._generate_html_template(
|
||||
title="Test",
|
||||
@@ -81,12 +73,11 @@ class TestEditModeRegression:
|
||||
|
||||
# Check for critical functions that must be present
|
||||
required_functions = [
|
||||
'MarkitectEditor',
|
||||
'updateStatus',
|
||||
'reportEditModeError',
|
||||
'makeContentEditable',
|
||||
'handleSectionClick',
|
||||
'editSection'
|
||||
'MarkitectCleanEditor',
|
||||
'SectionManager',
|
||||
'Section',
|
||||
'DOMRenderer',
|
||||
'initializeCleanEditor'
|
||||
]
|
||||
|
||||
for func_name in required_functions:
|
||||
@@ -94,14 +85,10 @@ class TestEditModeRegression:
|
||||
|
||||
def test_edit_mode_no_broken_string_literals(self):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
|
||||
html_content = doc_manager._generate_html_template(
|
||||
title="Test",
|
||||
@@ -126,14 +113,10 @@ class TestEditModeRegression:
|
||||
|
||||
def test_edit_mode_proper_brace_escaping(self):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
|
||||
html_content = doc_manager._generate_html_template(
|
||||
title="Test",
|
||||
@@ -157,14 +140,10 @@ class TestEditModeRegression:
|
||||
|
||||
def test_edit_mode_template_literal_syntax(self):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
|
||||
html_content = doc_manager._generate_html_template(
|
||||
title="Test",
|
||||
@@ -188,14 +167,10 @@ class TestEditModeRegression:
|
||||
|
||||
def test_edit_mode_contains_content_div(self):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
|
||||
html_content = doc_manager._generate_html_template(
|
||||
title="Test",
|
||||
@@ -210,14 +185,10 @@ class TestEditModeRegression:
|
||||
|
||||
def test_edit_mode_error_handling_elements(self):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
|
||||
html_content = doc_manager._generate_html_template(
|
||||
title="Test",
|
||||
@@ -225,22 +196,18 @@ class TestEditModeRegression:
|
||||
edit_mode=True
|
||||
)
|
||||
|
||||
# Should contain error handling elements
|
||||
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
|
||||
# Should contain clean editor elements
|
||||
assert 'MARKITECT_EDIT_MODE' in html_content
|
||||
assert 'class="markitect-edit-mode"' in html_content
|
||||
assert 'initializeCleanEditor' in html_content
|
||||
assert 'console.error' in html_content # Error handling
|
||||
|
||||
def test_edit_mode_vs_normal_mode_differences(self):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
test_content = "# Test Header\n\nTest content."
|
||||
|
||||
# Generate both modes
|
||||
@@ -265,14 +232,10 @@ class TestEditModeRegression:
|
||||
|
||||
def test_edit_mode_javascript_execution_flow(self):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
|
||||
html_content = doc_manager._generate_html_template(
|
||||
title="Test",
|
||||
@@ -288,9 +251,9 @@ class TestEditModeRegression:
|
||||
flow_elements = [
|
||||
'DOMContentLoaded', # Event listener setup
|
||||
'MARKITECT_EDIT_MODE', # Mode check
|
||||
'new MarkitectEditor', # Editor instantiation
|
||||
'makeContentEditable', # Content enhancement
|
||||
'handleSectionClick' # Interaction handler
|
||||
'initializeCleanEditor', # Editor initialization
|
||||
'marked.parse', # Content rendering
|
||||
'MarkitectCleanEditor' # Clean editor class
|
||||
]
|
||||
|
||||
for element in flow_elements:
|
||||
@@ -298,14 +261,10 @@ class TestEditModeRegression:
|
||||
|
||||
def test_newline_escaping_in_javascript_strings(self):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
|
||||
html_content = doc_manager._generate_html_template(
|
||||
title="Test",
|
||||
@@ -333,14 +292,10 @@ class TestEditModeIntegration:
|
||||
|
||||
def test_save_functionality_javascript_presence(self):
|
||||
"""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
|
||||
class MockDatabaseManager:
|
||||
pass
|
||||
|
||||
doc_manager = DocumentManager.__new__(DocumentManager)
|
||||
doc_manager.database_manager = MockDatabaseManager()
|
||||
# Create a CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager()
|
||||
|
||||
html_content = doc_manager._generate_html_template(
|
||||
title="Test",
|
||||
@@ -350,9 +305,9 @@ class TestEditModeIntegration:
|
||||
|
||||
# Check for save-related functionality
|
||||
save_elements = [
|
||||
'Save & Download', # Button text
|
||||
'markitectEditor.save()', # Save function call
|
||||
'getMarkdownContent', # Content extraction
|
||||
'💾 Save Document', # Button text from clean implementation
|
||||
'generateSaveFilename', # Save filename generation
|
||||
'getDocumentMarkdown', # Content extraction
|
||||
'Blob', # File creation
|
||||
'download' # Download attribute
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user