diff --git a/UserInterfaceFramework.md b/UserInterfaceFramework.md index 417e6992..c7e23776 100644 --- a/UserInterfaceFramework.md +++ b/UserInterfaceFramework.md @@ -248,6 +248,96 @@ Provides user confirmation for potentially destructive operations that cannot be --- +## 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 + +--- + ## Design Principles ### 1. **Theme Consistency** @@ -348,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 diff --git a/markitect/clean_document_manager.py b/markitect/clean_document_manager.py index ca13b88a..7663c614 100644 --- a/markitect/clean_document_manager.py +++ b/markitect/clean_document_manager.py @@ -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, @@ -619,6 +620,236 @@ class CleanDocumentManager: .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"" @@ -658,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 @@ -716,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, @@ -728,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 @@ -793,15 +1047,21 @@ 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); }} }} @@ -863,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; @@ -987,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'); + } } /** @@ -1020,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; @@ -1083,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; @@ -1113,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++; } } @@ -1188,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 @@ -1373,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; @@ -1397,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); @@ -1420,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); diff --git a/markitect/plugins/builtin/markdown_commands.py b/markitect/plugins/builtin/markdown_commands.py index 8ea94cd3..d3785ca6 100644 --- a/markitect/plugins/builtin/markdown_commands.py +++ b/markitect/plugins/builtin/markdown_commands.py @@ -1961,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)') @@ -1973,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. @@ -1992,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 @@ -2001,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) @@ -2034,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}")