Compare commits
3 Commits
v0.6.0
...
9f4e296dd3
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f4e296dd3 | |||
| c7a83070f8 | |||
| dd3a00040a |
49
TODO.md
49
TODO.md
@@ -13,30 +13,7 @@ The structure organizes **future tasks** by their impact, just as a changelog or
|
||||
This section is for tasks currently being discussed with or worked on by the coding assistant. These are the ephemeral, flow-of-thought tasks.
|
||||
|
||||
* **To Add:**
|
||||
* **Complete Theme System Refactor - Layered Theme Architecture**: Major refactor to replace simple template selection with sophisticated layered theme system (currently stashed)
|
||||
* **Phase 1 - Restore and Assess**:
|
||||
* Restore stashed changes with `git stash pop`
|
||||
* Run tests to identify current failures and validation issues
|
||||
* Assess remaining work by checking all files that still use `--template`
|
||||
* **Phase 2 - Complete CLI Parameter Migration**:
|
||||
* Update remaining CLI commands in asset_commands.py, cli.py, and other files
|
||||
* Fix parameter validation - add proper theme validation for the new string-based parameter
|
||||
* Update help text and documentation to reflect new layered theme capabilities
|
||||
* **Phase 3 - Fix Integration Issues**:
|
||||
* Fix function signature mismatches where functions expect `template` but receive `theme`
|
||||
* Add proper error handling for invalid themes (replace print statements with logging)
|
||||
* Test layered theme functionality - ensure `dark,academic` type combinations work
|
||||
* Verify legacy theme mapping works correctly
|
||||
* **Phase 4 - Quality Assurance**:
|
||||
* Run full test suite to ensure no regressions
|
||||
* Test all CLI commands with new theme parameter
|
||||
* Verify backward compatibility with existing templates
|
||||
* Update any remaining documentation
|
||||
* **Phase 5 - Clean Up and Commit**:
|
||||
* Remove dead code and legacy functions if no longer needed
|
||||
* Ensure consistent terminology throughout codebase
|
||||
* Write comprehensive commit message documenting the major theme system improvement
|
||||
* Update CHANGELOG.md with new theme layering capabilities
|
||||
* None currently identified
|
||||
|
||||
* **To Fix:**
|
||||
* None currently identified
|
||||
@@ -47,30 +24,6 @@ This section is for tasks currently being discussed with or worked on by the cod
|
||||
* **To Remove:**
|
||||
* None currently identified
|
||||
|
||||
***
|
||||
|
||||
## Theme System Refactor Context
|
||||
|
||||
**Current State**: Work-in-progress theme system refactor is stashed and partially complete.
|
||||
|
||||
**Completed Parts ✅**:
|
||||
- New Layered Theme Architecture: Complete LAYERED_THEMES system with UI, document, and branding scopes
|
||||
- Theme Parsing Functions: `parse_theme_string()` and `combine_theme_properties()`
|
||||
- CSS Generation Refactor: New `_get_template_css()` and `_generate_layered_css()` methods
|
||||
- CLI Parameter Change: Changed from `--template` to `--theme` throughout test files
|
||||
- Legacy Compatibility: LEGACY_THEME_MAPPING for backward compatibility
|
||||
|
||||
**Missing/Incomplete Parts ❌**:
|
||||
- CLI Parameter Validation: The new `--theme` parameter needs validation for invalid themes
|
||||
- Function Signature Inconsistencies: Some functions still accept `template` parameter but call it with `theme`
|
||||
- Additional Files: Other files in the codebase still use old `template` parameter
|
||||
- Error Handling: The warning system for unknown themes needs proper logging
|
||||
|
||||
**New Capabilities When Complete**:
|
||||
- Single themes: `basic`, `github`, `dark`, `academic`, `light`, `corporate`, `startup`
|
||||
- Layered themes: `dark,academic` combines dark UI with academic typography
|
||||
- Complex combinations: `light,github,corporate` for branded GitHub-style documents
|
||||
- Legacy compatibility: Existing `--template` usage continues to work
|
||||
|
||||
***
|
||||
|
||||
|
||||
@@ -208,23 +208,133 @@ Provides detailed information about the current editing session, including versi
|
||||
### Description
|
||||
Provides user confirmation for potentially destructive operations that cannot be easily undone.
|
||||
|
||||
### Current Implementation
|
||||
- **Method**: Browser native `confirm()` (temporary solution)
|
||||
### Current Implementation ✅ COMPLETED
|
||||
- **Method**: Custom theme-aware modal dialog
|
||||
- **Trigger**: "🔄 Reset All" button in floating action panel
|
||||
- **Message**: "Reset all content to original markdown? This will lose all edits and remove split sections."
|
||||
- **Message**: "Reset all content to original markdown?"
|
||||
- **Warning**: "This will permanently lose all edits and remove any split sections. This action cannot be undone."
|
||||
|
||||
### Features Implemented
|
||||
- **Theme-Aware Styling**: Adapts to all UI themes (standard, greyscale, electric, psychedelic)
|
||||
- **Clear Action Buttons**:
|
||||
- Primary action: "Reset Document" (red danger button)
|
||||
- Secondary action: "Keep Changes" (grey cancel button)
|
||||
- **Enhanced UX**:
|
||||
- Detailed consequence explanation with warning styling
|
||||
- Professional modal overlay with smooth animations
|
||||
- Proper focus management and accessibility
|
||||
- **Keyboard Support**:
|
||||
- ESC key to cancel
|
||||
- Enter key to confirm
|
||||
- Tab navigation between buttons
|
||||
|
||||
### Use Cases
|
||||
- **Reset All Sections**: Complete document reset to original state
|
||||
- **Future**: Delete operations, bulk changes, file operations
|
||||
- **Future**: Extensible for 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
|
||||
### Technical Implementation
|
||||
**CSS Classes**:
|
||||
- `.ui-edit-confirmation-modal` - Modal container
|
||||
- `.ui-edit-confirmation-content` - Main message
|
||||
- `.ui-edit-confirmation-warning` - Warning section
|
||||
- `.ui-edit-confirmation-buttons` - Button container
|
||||
- `.ui-edit-button-confirm` - Danger action button
|
||||
- `.ui-edit-button-cancel` - Cancel action button
|
||||
|
||||
**JavaScript Method**: `showConfirmation(message, confirmText, cancelText, warningText)`
|
||||
- Returns Promise<boolean> for async/await support
|
||||
- Theme-consistent styling via layered theme system
|
||||
- Proper event cleanup and accessibility features
|
||||
|
||||
---
|
||||
|
||||
## 7. Insert Mode Editor
|
||||
|
||||
**Component Name**: `Insert Mode Editor`
|
||||
**Type**: Structured editing mode with heading protection
|
||||
**Location**: Replaces section content during editing (contextual)
|
||||
|
||||
### Description ✅ COMPLETED
|
||||
A specialized editing mode that duplicates edit mode functionality while enforcing document structure integrity. Provides content editing with selective heading protection for levels 1-3, maintaining document outline consistency.
|
||||
|
||||
### Current Implementation ✅ COMPLETED
|
||||
- **CLI Activation**: `markitect md-render document.md --insert`
|
||||
- **Mode Detection**: Uses `MARKITECT_INSERT_MODE` JavaScript flag
|
||||
- **Heading Protection**: Levels 1-3 are read-only, displayed above content editor
|
||||
- **Content Editing**: Full editing capability for content following protected headings
|
||||
|
||||
### Features Implemented
|
||||
- **Structured Editing Interface**:
|
||||
- Protected heading display (read-only) for levels 1-3
|
||||
- Content-only textarea for body text editing
|
||||
- Level 4+ headings remain fully editable
|
||||
- **Heading Protection Logic**:
|
||||
- Visual distinction with warning-styled heading display
|
||||
- Prevents modification of heading text in protected sections
|
||||
- Server-side validation ensures heading integrity
|
||||
- **Section Management**:
|
||||
- Automatic section splitting on new heading introduction
|
||||
- New heading sections inherit protection based on level
|
||||
- Maintains document structure during complex edits
|
||||
- **Theme Integration**:
|
||||
- Adapts to all UI themes (standard, greyscale, electric, psychedelic)
|
||||
- Consistent styling with edit mode components
|
||||
- Special styling for protected heading display
|
||||
|
||||
### Use Cases
|
||||
- **Document Structure Preservation**: Maintain established outline while allowing content updates
|
||||
- **Collaborative Editing**: Prevent accidental heading modifications in shared documents
|
||||
- **Template-Based Content**: Edit content within predefined structural frameworks
|
||||
- **Controlled Authoring**: Allow content contributions without structural changes
|
||||
|
||||
### Technical Implementation
|
||||
**CLI Integration**:
|
||||
- `--insert` flag added to `md-render` command
|
||||
- Mutually exclusive with `--edit` flag
|
||||
- Validation prevents simultaneous mode activation
|
||||
|
||||
**CSS Classes**:
|
||||
- `.markitect-insert-mode` - Body class for insert mode
|
||||
- `.ui-insert-protected-panel` - Container for protected heading sections
|
||||
- `.ui-insert-heading-display` - Read-only heading display component
|
||||
- `.ui-insert-content-editor` - Content-only editing textarea
|
||||
|
||||
**JavaScript Configuration**:
|
||||
```javascript
|
||||
const MARKITECT_INSERT_MODE = true;
|
||||
const MARKITECT_EDITOR_CONFIG = {
|
||||
mode: 'insert',
|
||||
restrictedHeadingLevels: [1, 2, 3],
|
||||
// ... standard editor config
|
||||
};
|
||||
```
|
||||
|
||||
**Section Enhancement**:
|
||||
- `Section.detectHeadingLevel()` - Identify heading levels 1-6
|
||||
- `Section.isProtectedHeading()` - Check if heading is protected in current mode
|
||||
- `Section.getHeadingText()` - Extract heading text for display
|
||||
- `Section.getHeadingContent()` - Extract content after heading for editing
|
||||
|
||||
**Validation Logic**:
|
||||
- Pre-acceptance validation ensures protected headings remain unchanged
|
||||
- Error handling for attempted heading modifications
|
||||
- Content reconstruction maintains heading + content structure
|
||||
|
||||
### Behavioral Differences from Edit Mode
|
||||
| Feature | Edit Mode | Insert Mode |
|
||||
|---------|-----------|-------------|
|
||||
| Heading Levels 1-3 | ✏️ Fully Editable | 🔒 Read-Only Display |
|
||||
| Heading Levels 4-6 | ✏️ Fully Editable | ✏️ Fully Editable |
|
||||
| Content Editing | ✏️ Full Section | ✏️ Content Only (for protected) |
|
||||
| Section Splitting | ✅ All Headings | ✅ All Headings |
|
||||
| New Heading Creation | ✅ Unlimited | ✅ With Level-Based Protection |
|
||||
| Theme Support | ✅ All Themes | ✅ All Themes |
|
||||
|
||||
### Future Enhancements
|
||||
- **Configurable Protection Levels**: Allow customization of which heading levels are protected
|
||||
- **Conditional Protection**: Enable/disable protection based on section content or metadata
|
||||
- **Protection Indicators**: Visual badges showing protection status in section list
|
||||
- **Bulk Mode Switching**: Convert between edit and insert modes for existing documents
|
||||
|
||||
---
|
||||
|
||||
@@ -328,8 +438,9 @@ All components must adapt to the selected UI theme:
|
||||
| Toast System | ❌ No | ✅ Yes | ❌ N/A | ✅ Yes | ⚠️ Basic |
|
||||
| Document Canvas | ✅ Yes | ✅ Yes | ⚠️ Partial | ✅ Yes | ✅ Yes |
|
||||
| Section Editor | ✅ Yes | ⚠️ Partial | ⚠️ Basic | ⚠️ Basic | ⚠️ Partial |
|
||||
| Insert Mode Editor | ✅ Yes | ⚠️ Partial | ⚠️ Basic | ⚠️ Basic | ⚠️ Partial |
|
||||
| Status Modal | ❌ No | ❌ No | ❌ No | ❌ No | ❌ No |
|
||||
| Confirmation | ❌ No | ❌ No | ❌ No | ❌ No | ❌ No |
|
||||
| Confirmation | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
|
||||
|
||||
**Legend**: ✅ Full Support | ⚠️ Partial/Needs Work | ❌ Not Implemented
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ class CleanDocumentManager:
|
||||
}
|
||||
|
||||
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, nodogtag: bool = False) -> Dict[str, Any]:
|
||||
edit_mode: bool = False, insert_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.
|
||||
"""
|
||||
@@ -85,6 +85,7 @@ class CleanDocumentManager:
|
||||
css=css,
|
||||
template=template,
|
||||
edit_mode=edit_mode,
|
||||
insert_mode=insert_mode,
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts,
|
||||
original_filename=original_filename,
|
||||
@@ -481,8 +482,375 @@ class CleanDocumentManager:
|
||||
border-top: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
text-align: right;
|
||||
}}
|
||||
outline: none;
|
||||
}}"""
|
||||
|
||||
/* Confirmation Dialog Styles */
|
||||
.markitect-edit-mode .ui-edit-confirmation-modal {{
|
||||
max-width: 500px;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-confirmation-content {{
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 24px;
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-confirmation-warning {{
|
||||
background: {props.get('editor_warning_bg', '#fff3cd')};
|
||||
border: 1px solid {props.get('editor_warning_border', '#ffeaa7')};
|
||||
color: {props.get('editor_warning_text', '#856404')};
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
margin: 16px 0;
|
||||
font-size: 14px;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-confirmation-buttons {{
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-confirm {{
|
||||
background: {props.get('editor_danger_button', '#dc3545')};
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, transform 0.1s;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-confirm:hover {{
|
||||
background: {props.get('editor_danger_button_hover', '#c82333')};
|
||||
transform: translateY(-1px);
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-confirm:active {{
|
||||
transform: translateY(0);
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-confirm:focus {{
|
||||
outline: 2px solid {props.get('editor_focus_color', '#007bff')};
|
||||
outline-offset: 2px;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-cancel {{
|
||||
background: {props.get('editor_secondary_button', '#6c757d')};
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, transform 0.1s;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-cancel:hover {{
|
||||
background: {props.get('editor_secondary_button_hover', '#545b62')};
|
||||
transform: translateY(-1px);
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-cancel:active {{
|
||||
transform: translateY(0);
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-cancel:focus {{
|
||||
outline: 2px solid {props.get('editor_focus_color', '#007bff')};
|
||||
outline-offset: 2px;
|
||||
}}
|
||||
|
||||
/* Document Scroll Indicators */
|
||||
.ui-scroll-indicator {{
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
background: {props.get('editor_panel_bg', '#f8f9fa')};
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
border-radius: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.2s ease, background-color 0.2s ease;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 4px 12px {props.get('editor_shadow', 'rgba(0,0,0,0.15)')};
|
||||
}}
|
||||
.ui-scroll-indicator:hover {{
|
||||
transform: translateX(-50%) scale(1.05);
|
||||
}}
|
||||
.ui-scroll-indicator:not(.disabled):hover {{
|
||||
background: {props.get('editor_button_hover', '#e9ecef')};
|
||||
}}
|
||||
.ui-scroll-indicator.active {{
|
||||
opacity: 0.9;
|
||||
visibility: visible;
|
||||
}}
|
||||
.ui-scroll-indicator.disabled {{
|
||||
background: {props.get('editor_button_active', '#dee2e6')};
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}}
|
||||
.ui-scroll-indicator.disabled:hover {{
|
||||
transform: translateX(-50%);
|
||||
background: {props.get('editor_button_active', '#dee2e6')};
|
||||
}}
|
||||
.ui-scroll-indicator-up {{
|
||||
top: 20px;
|
||||
}}
|
||||
.ui-scroll-indicator-down {{
|
||||
bottom: 20px;
|
||||
}}
|
||||
.ui-scroll-indicator::before {{
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
transition: border-color 0.2s ease;
|
||||
}}
|
||||
.ui-scroll-indicator-up::before {{
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
border-bottom: 12px solid {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.ui-scroll-indicator-down::before {{
|
||||
border-left: 8px solid transparent;
|
||||
border-right: 8px solid transparent;
|
||||
border-top: 12px solid {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.ui-scroll-indicator.disabled.ui-scroll-indicator-up::before {{
|
||||
border-bottom-color: {props.get('editor_secondary_button', '#6c757d')};
|
||||
}}
|
||||
.ui-scroll-indicator.disabled.ui-scroll-indicator-down::before {{
|
||||
border-top-color: {props.get('editor_secondary_button', '#6c757d')};
|
||||
}}
|
||||
|
||||
/* Insert Mode Specific Styles */
|
||||
.markitect-insert-mode .ui-edit-floater-panel {{
|
||||
background: {props['editor_panel_bg']};
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
box-shadow: 0 4px 12px {props.get('editor_shadow', 'rgba(0,0,0,0.1)')};
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-floater-header {{
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-floater-header h3 {{
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-insert-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-insert-mode .ui-insert-protected-panel {{
|
||||
border-left: 4px solid #ff9800;
|
||||
}}
|
||||
.markitect-insert-mode .ui-insert-heading-display {{
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 8px 12px;
|
||||
background: {props.get('editor_warning_bg', '#fff3cd')};
|
||||
border: 1px solid {props.get('editor_warning_border', '#ffeaa7')};
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #007bff;
|
||||
color: {props.get('editor_warning_text', '#856404')};
|
||||
margin-bottom: 8px;
|
||||
}}
|
||||
.markitect-insert-mode .ui-insert-content-editor {{
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
background: {props.get('editor_button_bg', '#ffffff')};
|
||||
}}
|
||||
.markitect-insert-mode .ui-insert-content-editor:focus {{
|
||||
border-color: {props.get('editor_focus_color', '#007bff')};
|
||||
box-shadow: 0 0 0 2px {props.get('editor_focus_color', '#007bff')}33;
|
||||
}}
|
||||
.markitect-insert-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-insert-mode .ui-edit-button:hover {{
|
||||
background: {props.get('editor_button_hover', '#e9ecef')};
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button:active,
|
||||
.markitect-insert-mode .ui-edit-button.active {{
|
||||
background: {props.get('editor_button_active', '#dee2e6')};
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button-accept {{
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button-accept:hover {{
|
||||
background: #388e3c;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button-cancel {{
|
||||
background: #f44336;
|
||||
color: white;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button-cancel:hover {{
|
||||
background: #d32f2f;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button-reset {{
|
||||
background: #ff9800;
|
||||
color: white;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button-reset:hover {{
|
||||
background: #f57c00;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-section-frame {{
|
||||
border: 2px solid {props.get('editor_focus_color', '#007bff')};
|
||||
box-shadow: 0 0 0 3px {props.get('editor_focus_color', '#007bff')}33;
|
||||
}}
|
||||
|
||||
/* Modal Overlay and Dialog Styles for Insert Mode */
|
||||
.markitect-insert-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-insert-mode .ui-edit-modal-overlay.active {{
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}}
|
||||
.markitect-insert-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-insert-mode .ui-edit-modal-overlay.active .ui-edit-modal {{
|
||||
transform: scale(1) translateY(0);
|
||||
}}
|
||||
.markitect-insert-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-insert-mode .ui-edit-modal-title {{
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-modal-close {{
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-modal-close:hover {{
|
||||
background: {props.get('editor_button_hover', '#e9ecef')};
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-modal-body {{
|
||||
padding: 20px 24px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-modal-section {{
|
||||
margin-bottom: 8px;
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-modal-footer {{
|
||||
padding: 16px 24px 20px;
|
||||
border-top: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
text-align: right;
|
||||
}}
|
||||
|
||||
/* Confirmation Dialog Styles for Insert Mode */
|
||||
.markitect-insert-mode .ui-edit-confirmation-modal {{
|
||||
max-width: 500px;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-confirmation-content {{
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 24px;
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-confirmation-warning {{
|
||||
background: {props.get('editor_warning_bg', '#fff3cd')};
|
||||
color: {props.get('editor_warning_text', '#856404')};
|
||||
border: 1px solid {props.get('editor_warning_border', '#ffeaa7')};
|
||||
border-radius: 6px;
|
||||
padding: 12px 16px;
|
||||
margin: 16px 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-confirmation-buttons {{
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 24px;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button-confirm {{
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: 1px solid #dc3545;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button-confirm:hover {{
|
||||
background: #c82333;
|
||||
border-color: #bd2130;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button-cancel {{
|
||||
background: {props.get('editor_button_bg', '#ffffff')};
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}}
|
||||
.markitect-insert-mode .ui-edit-button-cancel:hover {{
|
||||
background: {props.get('editor_button_hover', '#e9ecef')};
|
||||
}}
|
||||
"""
|
||||
|
||||
return f"<style>{base_css}{heading_css}{text_css}{element_css}{link_css}{accent_css}{ui_css}</style>"
|
||||
|
||||
@@ -521,7 +889,7 @@ 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, nodogtag: bool = False) -> str:
|
||||
edit_mode: bool = False, insert_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
|
||||
@@ -579,6 +947,28 @@ class CleanDocumentManager:
|
||||
editor_config = f"""
|
||||
const MARKITECT_EDIT_MODE = true;
|
||||
const MARKITECT_EDITOR_CONFIG = {{
|
||||
mode: 'edit',
|
||||
theme: '{editor_theme}',
|
||||
keyboardShortcuts: {str(keyboard_shortcuts).lower()},
|
||||
autosave: false,
|
||||
sections: true,
|
||||
originalFilename: '{original_filename}',
|
||||
version: '{version_str}',
|
||||
repoName: '{version_info['repo_name'] if version_info else 'Markitect'}'
|
||||
}};
|
||||
|
||||
// Make config available globally
|
||||
window.editorConfig = MARKITECT_EDITOR_CONFIG;"""
|
||||
elif insert_mode:
|
||||
body_classes = ' class="markitect-insert-mode"'
|
||||
|
||||
# Configuration for insert mode editor
|
||||
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_INSERT_MODE = true;
|
||||
const MARKITECT_EDITOR_CONFIG = {{
|
||||
mode: 'insert',
|
||||
restrictedHeadingLevels: [1, 2, 3],
|
||||
theme: '{editor_theme}',
|
||||
keyboardShortcuts: {str(keyboard_shortcuts).lower()},
|
||||
autosave: false,
|
||||
@@ -591,7 +981,8 @@ class CleanDocumentManager:
|
||||
// Make config available globally
|
||||
window.editorConfig = MARKITECT_EDITOR_CONFIG;"""
|
||||
|
||||
# Load clean editor architecture
|
||||
# Load clean editor architecture for both edit and insert modes
|
||||
if edit_mode or insert_mode:
|
||||
editor_scripts = self._get_clean_editor_scripts()
|
||||
|
||||
# Generate the complete HTML template
|
||||
@@ -656,17 +1047,30 @@ class CleanDocumentManager:
|
||||
}}
|
||||
}}
|
||||
|
||||
// Step 2: Initialize edit capabilities if enabled
|
||||
if (typeof MARKITECT_EDIT_MODE !== 'undefined' && MARKITECT_EDIT_MODE) {{
|
||||
console.log("Initializing clean edit capabilities...");
|
||||
// Step 2: Initialize edit/insert capabilities if enabled
|
||||
if ((typeof MARKITECT_EDIT_MODE !== 'undefined' && MARKITECT_EDIT_MODE) ||
|
||||
(typeof MARKITECT_INSERT_MODE !== 'undefined' && MARKITECT_INSERT_MODE)) {{
|
||||
const mode = (typeof MARKITECT_INSERT_MODE !== 'undefined' && MARKITECT_INSERT_MODE) ? 'insert' : 'edit';
|
||||
console.log(`Initializing clean ${{mode}} capabilities...`);
|
||||
try {{
|
||||
console.log("Creating clean editor instance...");
|
||||
initializeCleanEditor();
|
||||
console.log("✓ Clean edit mode active - click any section to edit");
|
||||
if (mode === 'insert') {{
|
||||
console.log("✓ Clean insert mode active - click any section to edit (headings 1-3 protected)");
|
||||
}} else {{
|
||||
console.log("✓ Clean edit mode active - click any section to edit");
|
||||
}}
|
||||
}} catch (error) {{
|
||||
console.error("Clean edit mode failed to initialize:", error);
|
||||
console.error(`Clean ${{mode}} mode failed to initialize:`, error);
|
||||
}}
|
||||
}}
|
||||
|
||||
// Step 3: Initialize document scroll indicators (always available)
|
||||
try {{
|
||||
initializeScrollIndicators();
|
||||
}} catch (error) {{
|
||||
console.error("Scroll indicators failed to initialize:", error);
|
||||
}}
|
||||
}});
|
||||
|
||||
// Handle CDN loading errors
|
||||
@@ -719,6 +1123,7 @@ class Section {
|
||||
this.editingMarkdown = null;
|
||||
this.pendingMarkdown = null;
|
||||
this.sectionType = sectionType;
|
||||
this.headingLevel = Section.detectHeadingLevel(originalMarkdown);
|
||||
this.state = EditState.ORIGINAL;
|
||||
this.domElement = null;
|
||||
this.lastSaved = null;
|
||||
@@ -843,6 +1248,39 @@ class Section {
|
||||
}
|
||||
return SectionType.PARAGRAPH;
|
||||
}
|
||||
|
||||
static detectHeadingLevel(markdown) {
|
||||
const trimmed = markdown.trim();
|
||||
const match = trimmed.match(/^(#{1,6})\s/);
|
||||
return match ? match[1].length : null;
|
||||
}
|
||||
|
||||
isHeading() {
|
||||
return this.sectionType === SectionType.HEADING;
|
||||
}
|
||||
|
||||
isProtectedHeading() {
|
||||
if (!this.isHeading()) return false;
|
||||
// Check if we're in insert mode and if this heading level is protected
|
||||
const config = window.editorConfig || {};
|
||||
const restrictedLevels = config.restrictedHeadingLevels || [];
|
||||
return config.mode === 'insert' && restrictedLevels.includes(this.headingLevel);
|
||||
}
|
||||
|
||||
getHeadingText() {
|
||||
if (!this.isHeading()) return null;
|
||||
// Extract first line for heading text
|
||||
const firstLine = this.originalMarkdown.trim().split('\\n')[0];
|
||||
const match = firstLine.match(/^(#{1,6})\s+(.+)$/);
|
||||
return match ? match[2] : null;
|
||||
}
|
||||
|
||||
getHeadingContent() {
|
||||
if (!this.isHeading()) return this.currentMarkdown;
|
||||
const lines = this.currentMarkdown.split('\\n');
|
||||
// Return content after the heading line
|
||||
return lines.slice(1).join('\\n');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -876,7 +1314,7 @@ class SectionManager {
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const isHeading = /^#{1,6}\\s/.test(line);
|
||||
const isHeading = /^#{1,6}\s/.test(line);
|
||||
const isNewParagraph = line.trim() && i > 0 && !lines[i-1].trim();
|
||||
const isNewSection = isHeading || isNewParagraph;
|
||||
|
||||
@@ -939,6 +1377,15 @@ class SectionManager {
|
||||
throw new Error(`Section ${sectionId} not found`);
|
||||
}
|
||||
|
||||
// For protected headings in insert mode, validate that heading hasn't changed
|
||||
if (section.isProtectedHeading()) {
|
||||
const originalHeadingLine = section.originalMarkdown.split('\\n')[0];
|
||||
const newHeadingLine = section.editingMarkdown.split('\\n')[0];
|
||||
if (originalHeadingLine !== newHeadingLine) {
|
||||
throw new Error(`Cannot modify protected heading in insert mode. Heading level ${section.headingLevel} is read-only.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the edited content contains new headings that would create splits
|
||||
const newContent = section.editingMarkdown;
|
||||
const originalContent = section.originalMarkdown;
|
||||
@@ -969,14 +1416,14 @@ class SectionManager {
|
||||
|
||||
// Count headings in new content
|
||||
for (const line of lines) {
|
||||
if (/^#{1,6}\\s/.test(line.trim())) {
|
||||
if (/^#{1,6}\s/.test(line.trim())) {
|
||||
newHeadingCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Count headings in original content
|
||||
for (const line of originalLines) {
|
||||
if (/^#{1,6}\\s/.test(line.trim())) {
|
||||
if (/^#{1,6}\s/.test(line.trim())) {
|
||||
originalHeadingCount++;
|
||||
}
|
||||
}
|
||||
@@ -1044,7 +1491,7 @@ class SectionManager {
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const isHeading = /^#{1,6}\\s/.test(line.trim());
|
||||
const isHeading = /^#{1,6}\s/.test(line.trim());
|
||||
|
||||
if (isHeading) {
|
||||
// When we encounter a heading, complete any previous section
|
||||
@@ -1229,18 +1676,54 @@ class DOMRenderer {
|
||||
|
||||
this.hideCurrentEditor();
|
||||
|
||||
const section = this.sectionManager.sections.get(sectionId);
|
||||
const isProtectedHeading = section && section.isProtectedHeading();
|
||||
|
||||
const editorContainer = document.createElement('div');
|
||||
editorContainer.className = 'ui-edit-inline-panel';
|
||||
editorContainer.className = isProtectedHeading ? 'ui-edit-inline-panel ui-insert-protected-panel' : 'ui-edit-inline-panel';
|
||||
editorContainer.style.cssText = `
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
// If this is a protected heading, show the heading display
|
||||
if (isProtectedHeading) {
|
||||
const headingDisplay = document.createElement('div');
|
||||
headingDisplay.className = 'ui-insert-heading-display';
|
||||
const headingText = section.getHeadingText();
|
||||
const headingLevel = section.headingLevel;
|
||||
const headingMarkdown = '#'.repeat(headingLevel) + ' ' + headingText;
|
||||
headingDisplay.textContent = headingMarkdown;
|
||||
headingDisplay.style.cssText = `
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 8px 12px;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #007bff;
|
||||
color: #333;
|
||||
`;
|
||||
editorContainer.appendChild(headingDisplay);
|
||||
}
|
||||
|
||||
// Create content editing area
|
||||
const editingArea = document.createElement('div');
|
||||
editingArea.style.cssText = `
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
`;
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.className = 'ui-edit-textarea ui-edit-textarea-main';
|
||||
textarea.value = content;
|
||||
textarea.className = isProtectedHeading ? 'ui-edit-textarea ui-insert-content-editor' : 'ui-edit-textarea ui-edit-textarea-main';
|
||||
|
||||
// For protected headings, only show content after the heading
|
||||
const textareaContent = isProtectedHeading ? section.getHeadingContent() : content;
|
||||
textarea.value = textareaContent;
|
||||
|
||||
textarea.style.cssText = `
|
||||
flex: 1;
|
||||
min-height: 100px;
|
||||
@@ -1253,7 +1736,14 @@ class DOMRenderer {
|
||||
`;
|
||||
|
||||
textarea.addEventListener('input', () => {
|
||||
this.sectionManager.updateContent(sectionId, textarea.value);
|
||||
if (isProtectedHeading) {
|
||||
// Reconstruct full content with protected heading
|
||||
const headingLine = section.originalMarkdown.split('\\n')[0];
|
||||
const fullContent = headingLine + '\\n' + textarea.value;
|
||||
this.sectionManager.updateContent(sectionId, fullContent);
|
||||
} else {
|
||||
this.sectionManager.updateContent(sectionId, textarea.value);
|
||||
}
|
||||
});
|
||||
textarea.addEventListener('keydown', this.handleKeydown);
|
||||
|
||||
@@ -1276,8 +1766,9 @@ class DOMRenderer {
|
||||
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);
|
||||
editingArea.appendChild(textarea);
|
||||
editingArea.appendChild(controls);
|
||||
editorContainer.appendChild(editingArea);
|
||||
|
||||
element.innerHTML = '';
|
||||
element.appendChild(editorContainer);
|
||||
@@ -1675,8 +2166,143 @@ class MarkitectCleanEditor {
|
||||
}
|
||||
}
|
||||
|
||||
resetAllSections() {
|
||||
if (confirm('Reset all content to original markdown? This will lose all edits and remove split sections.')) {
|
||||
/**
|
||||
* Show custom confirmation dialog with theme-consistent styling
|
||||
* @param {string} message - The confirmation message
|
||||
* @param {string} confirmText - Text for confirm button (default: "Confirm")
|
||||
* @param {string} cancelText - Text for cancel button (default: "Cancel")
|
||||
* @param {string} warningText - Optional warning text to highlight consequences
|
||||
* @returns {Promise<boolean>} - True if confirmed, false if cancelled
|
||||
*/
|
||||
showConfirmation(message, confirmText = "Confirm", cancelText = "Cancel", warningText = null) {
|
||||
return new Promise((resolve) => {
|
||||
// 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 ui-edit-confirmation-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 = 'Confirm Action';
|
||||
|
||||
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';
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'ui-edit-confirmation-content';
|
||||
content.textContent = message;
|
||||
body.appendChild(content);
|
||||
|
||||
// Add warning section if provided
|
||||
if (warningText) {
|
||||
const warning = document.createElement('div');
|
||||
warning.className = 'ui-edit-confirmation-warning';
|
||||
warning.textContent = warningText;
|
||||
body.appendChild(warning);
|
||||
}
|
||||
|
||||
// Create footer with action buttons
|
||||
const footer = document.createElement('div');
|
||||
footer.className = 'ui-edit-modal-footer';
|
||||
|
||||
const buttonContainer = document.createElement('div');
|
||||
buttonContainer.className = 'ui-edit-confirmation-buttons';
|
||||
|
||||
const cancelBtn = document.createElement('button');
|
||||
cancelBtn.className = 'ui-edit-button-cancel';
|
||||
cancelBtn.textContent = cancelText;
|
||||
|
||||
const confirmBtn = document.createElement('button');
|
||||
confirmBtn.className = 'ui-edit-button-confirm';
|
||||
confirmBtn.textContent = confirmText;
|
||||
|
||||
buttonContainer.appendChild(cancelBtn);
|
||||
buttonContainer.appendChild(confirmBtn);
|
||||
footer.appendChild(buttonContainer);
|
||||
|
||||
// Assemble modal
|
||||
modal.appendChild(header);
|
||||
modal.appendChild(body);
|
||||
modal.appendChild(footer);
|
||||
overlay.appendChild(modal);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Function to close modal and resolve
|
||||
const closeModal = (result) => {
|
||||
overlay.remove();
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
// Event listeners
|
||||
closeBtn.addEventListener('click', () => closeModal(false));
|
||||
cancelBtn.addEventListener('click', () => closeModal(false));
|
||||
confirmBtn.addEventListener('click', () => closeModal(true));
|
||||
|
||||
// Close on overlay click
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
closeModal(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard support
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeModal(false);
|
||||
} else if (e.key === 'Enter') {
|
||||
closeModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
// Clean up event listener when modal is closed
|
||||
const originalResolve = resolve;
|
||||
resolve = (result) => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
originalResolve(result);
|
||||
};
|
||||
|
||||
// Show modal with animation
|
||||
setTimeout(() => {
|
||||
overlay.classList.add('active');
|
||||
// Focus the confirm button for accessibility
|
||||
confirmBtn.focus();
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
|
||||
async resetAllSections() {
|
||||
const confirmed = await this.showConfirmation(
|
||||
'Reset all content to original markdown?',
|
||||
'Reset Document',
|
||||
'Keep Changes',
|
||||
'This will permanently lose all edits and remove any split sections. This action cannot be undone.'
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
// Clear the section manager completely
|
||||
this.sectionManager.sections.clear();
|
||||
// Note: No longer tracking single editingSection
|
||||
@@ -1912,6 +2538,136 @@ function initializeCleanEditor() {
|
||||
console.log('✅ Clean section editor initialized successfully');
|
||||
}
|
||||
|
||||
// Document scroll indicators
|
||||
function initializeScrollIndicators() {
|
||||
// Create scroll indicators
|
||||
const scrollUpIndicator = document.createElement('div');
|
||||
scrollUpIndicator.className = 'ui-scroll-indicator ui-scroll-indicator-up';
|
||||
scrollUpIndicator.setAttribute('aria-label', 'Scroll to top');
|
||||
scrollUpIndicator.setAttribute('title', 'Scroll up');
|
||||
|
||||
const scrollDownIndicator = document.createElement('div');
|
||||
scrollDownIndicator.className = 'ui-scroll-indicator ui-scroll-indicator-down';
|
||||
scrollDownIndicator.setAttribute('aria-label', 'Scroll to bottom');
|
||||
scrollDownIndicator.setAttribute('title', 'Scroll down');
|
||||
|
||||
document.body.appendChild(scrollUpIndicator);
|
||||
document.body.appendChild(scrollDownIndicator);
|
||||
|
||||
let scrollIndicatorTimeout = null;
|
||||
|
||||
// Function to show/hide indicators based on scroll position and mouse position
|
||||
function updateScrollIndicators(mouseY = null) {
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
const windowHeight = window.innerHeight;
|
||||
const documentHeight = document.documentElement.scrollHeight;
|
||||
|
||||
// Determine if scrolling is possible in each direction
|
||||
const canScrollUp = scrollTop > 0;
|
||||
const canScrollDown = scrollTop < documentHeight - windowHeight;
|
||||
|
||||
// Only show indicators if there's any scroll possibility or if document is short
|
||||
let showUp = false;
|
||||
let showDown = false;
|
||||
|
||||
// Show indicators on mouseover near top/bottom of viewport
|
||||
if (mouseY !== null) {
|
||||
const topZone = 100; // pixels from top
|
||||
const bottomZone = windowHeight - 100; // pixels from bottom
|
||||
|
||||
if (mouseY <= topZone) {
|
||||
showUp = true;
|
||||
}
|
||||
if (mouseY >= bottomZone) {
|
||||
showDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update indicator visibility and state
|
||||
if (showUp) {
|
||||
scrollUpIndicator.classList.add('active');
|
||||
if (canScrollUp) {
|
||||
scrollUpIndicator.classList.remove('disabled');
|
||||
} else {
|
||||
scrollUpIndicator.classList.add('disabled');
|
||||
}
|
||||
} else {
|
||||
scrollUpIndicator.classList.remove('active');
|
||||
}
|
||||
|
||||
if (showDown) {
|
||||
scrollDownIndicator.classList.add('active');
|
||||
if (canScrollDown) {
|
||||
scrollDownIndicator.classList.remove('disabled');
|
||||
} else {
|
||||
scrollDownIndicator.classList.add('disabled');
|
||||
}
|
||||
} else {
|
||||
scrollDownIndicator.classList.remove('active');
|
||||
}
|
||||
|
||||
// Auto-hide after a delay when mouse moves away
|
||||
if (scrollIndicatorTimeout) {
|
||||
clearTimeout(scrollIndicatorTimeout);
|
||||
}
|
||||
scrollIndicatorTimeout = setTimeout(() => {
|
||||
scrollUpIndicator.classList.remove('active');
|
||||
scrollDownIndicator.classList.remove('active');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Mouse move handler
|
||||
function handleMouseMove(e) {
|
||||
updateScrollIndicators(e.clientY);
|
||||
}
|
||||
|
||||
// Smooth scroll function
|
||||
function smoothScroll(targetY, duration = 500) {
|
||||
const startY = window.pageYOffset;
|
||||
const difference = targetY - startY;
|
||||
const startTime = performance.now();
|
||||
|
||||
function step(currentTime) {
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
// Easing function (ease-out)
|
||||
const easeOut = 1 - Math.pow(1 - progress, 3);
|
||||
|
||||
window.scrollTo(0, startY + difference * easeOut);
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
// Click handlers for smooth scrolling
|
||||
scrollUpIndicator.addEventListener('click', () => {
|
||||
const currentScroll = window.pageYOffset;
|
||||
const targetScroll = Math.max(0, currentScroll - window.innerHeight * 0.8);
|
||||
smoothScroll(targetScroll);
|
||||
});
|
||||
|
||||
scrollDownIndicator.addEventListener('click', () => {
|
||||
const currentScroll = window.pageYOffset;
|
||||
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
|
||||
const targetScroll = Math.min(maxScroll, currentScroll + window.innerHeight * 0.8);
|
||||
smoothScroll(targetScroll);
|
||||
});
|
||||
|
||||
// Event listeners
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('scroll', () => updateScrollIndicators());
|
||||
|
||||
// Initial check
|
||||
updateScrollIndicators();
|
||||
|
||||
console.log('✅ Document scroll indicators initialized');
|
||||
}
|
||||
|
||||
// Export for testing and usage
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { Section, SectionManager, DOMRenderer, MarkitectCleanEditor };
|
||||
|
||||
@@ -75,7 +75,14 @@ LAYERED_THEMES = {
|
||||
'editor_button_active': '#dee2e6',
|
||||
'editor_text_color': '#212529',
|
||||
'editor_focus_color': '#0066cc',
|
||||
'editor_shadow': 'rgba(0,0,0,0.1)'
|
||||
'editor_shadow': 'rgba(0,0,0,0.1)',
|
||||
'editor_danger_button': '#dc3545',
|
||||
'editor_danger_button_hover': '#c82333',
|
||||
'editor_secondary_button': '#6c757d',
|
||||
'editor_secondary_button_hover': '#545b62',
|
||||
'editor_warning_bg': '#fff3cd',
|
||||
'editor_warning_border': '#ffeaa7',
|
||||
'editor_warning_text': '#856404'
|
||||
}
|
||||
},
|
||||
'greyscale': {
|
||||
@@ -93,10 +100,13 @@ LAYERED_THEMES = {
|
||||
'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'
|
||||
'editor_danger_button': '#8b0000',
|
||||
'editor_danger_button_hover': '#700000',
|
||||
'editor_secondary_button': '#666666',
|
||||
'editor_secondary_button_hover': '#555555',
|
||||
'editor_warning_bg': '#f0f0f0',
|
||||
'editor_warning_border': '#cccccc',
|
||||
'editor_warning_text': '#555555'
|
||||
}
|
||||
},
|
||||
'electric': {
|
||||
@@ -109,7 +119,14 @@ LAYERED_THEMES = {
|
||||
'editor_button_active': '#0099ff',
|
||||
'editor_text_color': '#00ffff',
|
||||
'editor_focus_color': '#ffff00',
|
||||
'editor_shadow': '0 0 20px rgba(0,255,255,0.5), 0 0 40px rgba(255,255,0,0.2)'
|
||||
'editor_shadow': '0 0 20px rgba(0,255,255,0.5), 0 0 40px rgba(255,255,0,0.2)',
|
||||
'editor_danger_button': '#ff3366',
|
||||
'editor_danger_button_hover': '#ff0033',
|
||||
'editor_secondary_button': '#006699',
|
||||
'editor_secondary_button_hover': '#004d73',
|
||||
'editor_warning_bg': '#003366',
|
||||
'editor_warning_border': '#00ffff',
|
||||
'editor_warning_text': '#ffff00'
|
||||
}
|
||||
},
|
||||
'psychedelic': {
|
||||
@@ -122,7 +139,14 @@ LAYERED_THEMES = {
|
||||
'editor_button_active': 'rgba(255,20,147,0.5)',
|
||||
'editor_text_color': '#ffffff',
|
||||
'editor_focus_color': '#ff1493',
|
||||
'editor_shadow': 'rgba(255,20,147,0.4)'
|
||||
'editor_shadow': 'rgba(255,20,147,0.4)',
|
||||
'editor_danger_button': 'linear-gradient(45deg, #ff0066, #cc0044)',
|
||||
'editor_danger_button_hover': 'linear-gradient(45deg, #ff3388, #dd1155)',
|
||||
'editor_secondary_button': 'linear-gradient(45deg, #8a2be2, #4b0082)',
|
||||
'editor_secondary_button_hover': 'linear-gradient(45deg, #9932cc, #6a1a9a)',
|
||||
'editor_warning_bg': 'linear-gradient(45deg, #ffa500, #ff8c00)',
|
||||
'editor_warning_border': '#ff1493',
|
||||
'editor_warning_text': '#ffffff'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1937,6 +1961,8 @@ def md_list_command(ctx, output_format, names_only):
|
||||
help='Custom CSS file to include')
|
||||
@click.option('--edit', is_flag=True,
|
||||
help='Open in interactive edit mode with stable section editing')
|
||||
@click.option('--insert', is_flag=True,
|
||||
help='Open in interactive insert mode with heading protection (levels 1-3 read-only)')
|
||||
@click.option('--editor-theme', default='github',
|
||||
type=click.Choice(['github', 'monokai', 'tomorrow', 'dark']),
|
||||
help='Editor theme for live edit mode (default: github)')
|
||||
@@ -1949,7 +1975,7 @@ def md_list_command(ctx, output_format, names_only):
|
||||
@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,
|
||||
def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_theme,
|
||||
keyboard_shortcuts, use_publication_dir, dont_use_publication_dir, nodogtag):
|
||||
"""
|
||||
Render a markdown file to HTML with basic templates and live preview capabilities.
|
||||
@@ -1968,6 +1994,7 @@ def md_render_command(ctx, input_file, output, theme, css, edit, editor_theme,
|
||||
markitect md-render README.md
|
||||
markitect md-render docs/guide.md --output guide.html --theme github
|
||||
markitect md-render draft.md --edit --editor-theme monokai
|
||||
markitect md-render draft.md --insert --editor-theme monokai
|
||||
markitect md-render doc.md --theme dark --css custom.css
|
||||
markitect md-render doc.md --theme dark,academic
|
||||
markitect md-render doc.md --theme light,github,corporate
|
||||
@@ -1977,6 +2004,10 @@ def md_render_command(ctx, input_file, output, theme, css, edit, editor_theme,
|
||||
try:
|
||||
input_path = Path(input_file)
|
||||
|
||||
# Validate mode flags
|
||||
if edit and insert:
|
||||
raise click.BadParameter("Cannot use both --edit and --insert flags simultaneously. Choose one mode.")
|
||||
|
||||
# Determine output path
|
||||
if output:
|
||||
output_path = Path(output)
|
||||
@@ -2010,10 +2041,29 @@ def md_render_command(ctx, input_file, output, theme, css, edit, editor_theme,
|
||||
click.echo(f"Keyboard shortcuts: {'enabled' if keyboard_shortcuts else 'disabled'}")
|
||||
click.echo(f"Theme: {theme or 'default'}")
|
||||
click.echo(f"CSS: {css or 'default'}")
|
||||
elif insert:
|
||||
# Insert mode - generate HTML with insert capabilities and heading protection
|
||||
result = doc_manager.render_file(input_file, str(output_path),
|
||||
template=theme, css=css,
|
||||
insert_mode=True,
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts,
|
||||
nodogtag=nodogtag)
|
||||
|
||||
click.echo(f"✓ Rendered with interactive insert capabilities to: {output_path}")
|
||||
|
||||
if config.get('verbose', False):
|
||||
click.echo(f"Editor theme: {editor_theme}")
|
||||
click.echo(f"Keyboard shortcuts: {'enabled' if keyboard_shortcuts else 'disabled'}")
|
||||
click.echo(f"Heading protection: levels 1-3 read-only")
|
||||
click.echo(f"Theme: {theme or 'default'}")
|
||||
click.echo(f"CSS: {css or 'default'}")
|
||||
else:
|
||||
# Static render
|
||||
result = doc_manager.render_file(input_file, str(output_path),
|
||||
template=theme, css=css,
|
||||
edit_mode=False,
|
||||
insert_mode=False,
|
||||
nodogtag=nodogtag)
|
||||
click.echo(f"✓ Rendered to: {output_path}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user