diff --git a/docs/PLUGIN_SYSTEM.md b/docs/PLUGIN_SYSTEM.md new file mode 100644 index 00000000..c6e8e07a --- /dev/null +++ b/docs/PLUGIN_SYSTEM.md @@ -0,0 +1,452 @@ +# Markitect Plugin System Documentation + +## Overview + +The Markitect plugin system provides a modular architecture for extending rendering capabilities with independent JavaScript UI components. This system enables JavaScript-first development while maintaining clean integration with the Python ecosystem. + +## Architecture + +### Core Components + +1. **RenderingEnginePlugin**: Base class for UI rendering engines +2. **RenderingConfig**: Asset management and deployment configuration +3. **RenderingEngineManager**: Plugin discovery and lifecycle management +4. **PluginManager**: Integration with existing Markitect plugin system + +### Key Features + +- **Clean Separation**: JSON-based configuration interface (Python ↔ JavaScript) +- **Independent Development**: JavaScript components work standalone +- **Asset Management**: Configurable deployment strategies +- **Multiple Engines**: Support for different UI frameworks +- **Fallback Support**: Graceful degradation to standard rendering + +## Plugin Development + +### Creating a Rendering Engine Plugin + +1. **Extend RenderingEnginePlugin**: + +```python +from markitect.plugins.rendering import RenderingEnginePlugin, RenderingConfig +from markitect.plugins.base import PluginMetadata, PluginType + +class MyUIEngine(RenderingEnginePlugin): + def __init__(self): + super().__init__() + self._metadata = PluginMetadata( + name="my-ui-engine", + version="1.0.0", + description="Custom UI rendering engine", + author="Your Name", + plugin_type=PluginType.RENDERING + ) + + @property + def metadata(self) -> PluginMetadata: + return self._metadata + + def get_supported_modes(self) -> List[str]: + return ["edit", "view"] + + def get_required_assets(self) -> Dict[str, List[str]]: + return { + "js": ["static/js/main.js"], + "css": ["static/css/style.css"] + } + + def render_document(self, content: str, mode: str, config: RenderingConfig) -> str: + # Your rendering logic here + return html_output +``` + +2. **Directory Structure**: + +``` +my-ui-engine/ +├── static/ +│ ├── js/ # JavaScript components +│ └── css/ # Stylesheets +├── templates/ # HTML templates +├── images/ # Icons and images +├── test-documents/ # Sample markdown files +├── package.json # Node.js configuration +└── README.md # Plugin documentation +``` + +3. **Asset Management**: + +Assets are automatically deployed based on configuration: +- **Development**: Served from plugin source directory +- **Production**: Copied to `_markitect/plugins/{plugin-name}/` + +## TestDrive JSUI Plugin + +### Overview + +The TestDrive JSUI plugin demonstrates the plugin architecture with a complete JavaScript UI for markdown editing. + +### Features + +- **Modular Components**: Clean separation of UI components +- **Compass Positioning**: NW, NE, E, SE control panel layout +- **Section Management**: Click-to-edit markdown sections +- **Debug System**: Built-in debugging and logging +- **Asset Pipeline**: Configurable asset deployment + +### Directory Structure + +``` +testdrive-jsui/ +├── static/js/ +│ ├── core/ # Core systems +│ │ ├── debug-system.js +│ │ └── section-manager.js +│ ├── components/ # UI components +│ │ ├── debug-panel.js +│ │ ├── document-controls.js +│ │ └── dom-renderer.js +│ ├── controls/ # Control panels +│ │ ├── control-base.js +│ │ ├── contents-control.js # Northwest +│ │ ├── status-control.js # East +│ │ ├── debug-control.js # Southeast +│ │ └── edit-control.js # Northeast +│ ├── config-loader.js # Configuration interface +│ └── main-updated.js # Application entry point +├── static/css/ # Stylesheets (future) +├── images/ # Icons and images (future) +├── templates/ +│ └── index.html # Main HTML template +├── test-documents/ +│ └── sample.md # Test content +├── test.html # Standalone development +├── package.json # Node.js configuration +└── README.md # Plugin documentation +``` + +### JavaScript Architecture + +- **Configuration Interface**: Clean JSON data transfer via `markitect-config` script element +- **Modular Components**: Each component has single responsibility +- **Event System**: Pub/sub for component communication +- **Control System**: Abstract base class for UI controls +- **Compass Positioning**: Consistent control panel layout + +## CLI Integration + +### Command Line Usage + +```bash +# Use default engine (testdrive-jsui for edit/insert, standard for view) +markitect md-render --edit document.md + +# Specify engine explicitly +markitect md-render --engine testdrive-jsui --edit document.md + +# Use standard engine +markitect md-render --engine standard --edit document.md + +# View available engines +markitect md-render --help +``` + +### Engine Selection Logic + +1. **Default Selection**: + - Edit/Insert modes: `testdrive-jsui` + - View mode: `standard` + +2. **Explicit Selection**: Use `--engine` parameter + +3. **Fallback Strategy**: + - Engine not found → fallback to standard + - Mode not supported → fallback to standard + - Plugin error → fallback to standard + +### Integration Points + +The CLI integrates with the plugin system through: + +```python +# Engine discovery +plugin_manager = PluginManager() +rendering_manager = RenderingEngineManager(plugin_manager) + +# Engine selection +engine = rendering_manager.get_engine(engine_name) + +# Configuration +config = RenderingConfig( + asset_base_url="_markitect", + development_mode=False, + output_directory=output_path.parent +) + +# Rendering +html_content = engine.render_document(content, mode, config) +``` + +## Development Workflows + +### Standalone JavaScript Development + +1. **Setup**: + ```bash + cd testdrive-jsui + python -m http.server 8080 + ``` + +2. **Development**: + - Edit JavaScript files in `static/js/` + - Refresh browser to see changes + - Use `test.html` for testing + - Browser DevTools for debugging + +3. **Benefits**: + - No Python environment required + - Fast iteration cycle + - Standard web development tools + - Hot reloading + +### Integrated Development + +1. **Plugin Testing**: + ```bash + python demo_plugin_integration.py + ``` + +2. **CLI Testing**: + ```bash + markitect md-render --engine testdrive-jsui --edit test.md + ``` + +3. **Integration Verification**: + ```bash + python test_complete_integration.py + ``` + +## Asset Management + +### Development Mode + +```python +config = RenderingConfig( + asset_base_url=".", + development_mode=True, + plugin_source_dirs={"testdrive-jsui": Path("testdrive-jsui")} +) + +# Assets served as: file://testdrive-jsui/static/js/main.js +``` + +### Production Mode + +```python +config = RenderingConfig( + asset_base_url="_markitect", + development_mode=False, + output_directory=Path("/output") +) + +# Assets served as: _markitect/plugins/testdrive-jsui/static/js/main.js +``` + +### Asset Types + +- **js**: JavaScript files +- **css**: Stylesheets +- **images**: Icons, graphics +- **external**: CDN resources + +### Deployment Strategy + +1. **Assets Copying**: Plugin assets copied to `_markitect/plugins/{name}/` +2. **URL Generation**: Automatic URL generation for templates +3. **Cache Management**: Asset versioning and cache control +4. **Error Handling**: Fallback for missing assets + +## Configuration Interface + +### Python → JavaScript Data Transfer + +All dynamic data passes through a clean JSON interface: + +```html + +``` + +### JavaScript Configuration Loading + +```javascript +// Clean configuration loading +class MarkitectConfig { + constructor() { + this.loadConfig(); + } + + loadConfig() { + const configElement = document.getElementById('markitect-config'); + this.config = JSON.parse(configElement.textContent); + } +} +``` + +### Benefits + +- **No String Interpolation**: Prevents template literal escaping issues +- **Type Safety**: JSON validation and error handling +- **Clean Separation**: No JavaScript code in Python strings +- **Debuggable**: Easy to inspect configuration in browser + +## Testing + +### Plugin Testing + +```bash +# Basic plugin discovery +python test_plugin_discovery.py + +# CLI integration logic +python test_cli_simple.py + +# Complete scenario testing +python test_complete_integration.py + +# Full integration demo +python demo_plugin_integration.py +``` + +### Browser Testing + +1. **Standalone**: Open `testdrive-jsui/test.html` +2. **Generated**: Open CLI-generated HTML files +3. **DevTools**: Use browser debugging tools +4. **Console**: Check for JavaScript errors + +### Integration Testing + +- **Unit Tests**: Individual component testing +- **Integration Tests**: Component interaction testing +- **E2E Tests**: Full workflow testing +- **Regression Tests**: Ensure stability across changes + +## Extension Points + +### Adding New Engines + +1. Create new plugin extending `RenderingEnginePlugin` +2. Implement required methods (`get_supported_modes`, `render_document`, etc.) +3. Register in `RenderingEngineManager._register_builtin_rendering_engines()` +4. Test with CLI integration + +### Adding New Modes + +1. Add mode to engine's `get_supported_modes()` +2. Update `render_document()` to handle new mode +3. Test mode validation and rendering +4. Update CLI integration if needed + +### Adding New Asset Types + +1. Update `get_required_assets()` return format +2. Modify asset deployment logic in `RenderingConfig` +3. Update template system to handle new asset types +4. Test asset URL generation + +## Best Practices + +### Plugin Development + +- **Single Responsibility**: Each component has one clear purpose +- **Clean Interfaces**: Well-defined APIs between components +- **Error Handling**: Graceful degradation on failures +- **Documentation**: Clear README and code comments + +### JavaScript Development + +- **Modular Architecture**: Avoid monolithic JavaScript files +- **Event-Driven**: Use pub/sub for component communication +- **Configuration-Driven**: Avoid hardcoded values +- **Browser Compatibility**: Test across different browsers + +### Asset Management + +- **Relative Paths**: Use relative paths in asset definitions +- **Versioning**: Include version info for cache management +- **Optimization**: Minimize asset size for production +- **CDN Integration**: Use CDN for external dependencies + +### Testing Strategy + +- **Automated Testing**: Comprehensive test coverage +- **Manual Testing**: User workflow validation +- **Cross-Platform**: Test on different environments +- **Performance Testing**: Monitor rendering performance + +## Troubleshooting + +### Common Issues + +1. **Plugin Not Found**: + - Check plugin registration in `_register_builtin_rendering_engines()` + - Verify plugin class inheritance from `RenderingEnginePlugin` + - Check import paths and module availability + +2. **Asset Loading Errors**: + - Verify asset paths in `get_required_assets()` + - Check file permissions and existence + - Validate URL generation in different modes + +3. **Configuration Errors**: + - Check JSON syntax in configuration + - Verify configuration element ID (`markitect-config`) + - Test configuration loading in JavaScript + +4. **Rendering Failures**: + - Check template file existence and permissions + - Verify template placeholder replacement + - Test with minimal content for debugging + +### Debug Techniques + +- **Console Logging**: Use browser console for debugging +- **Debug Panel**: TestDrive JSUI includes debug information +- **Verbose Mode**: Use CLI `--verbose` flag for detailed output +- **Test Scripts**: Run individual test scripts for isolation + +## Future Enhancements + +### Planned Features + +- **Plugin Package Manager**: npm-like plugin distribution +- **Theme System Integration**: Plugin-aware theme system +- **Performance Monitoring**: Built-in performance tracking +- **Hot Reloading**: Automatic reload on file changes + +### Extension Opportunities + +- **React Integration**: React-based rendering engine +- **Vue Integration**: Vue.js-based rendering engine +- **TypeScript Support**: TypeScript plugin development +- **Testing Framework**: Automated JavaScript testing + +### Community + +- **Plugin Registry**: Central repository for community plugins +- **Documentation**: Expanded examples and tutorials +- **Templates**: Starter templates for new plugins +- **Best Practices**: Community guidelines and patterns + +--- + +*This plugin system enables JavaScript-first development while maintaining clean integration with the MarkiTect Python ecosystem, providing the best of both worlds for UI development and backend processing.* \ No newline at end of file diff --git a/markitect/plugins/builtin/markdown_commands.py b/markitect/plugins/builtin/markdown_commands.py index b2eda7e4..ed693b47 100644 --- a/markitect/plugins/builtin/markdown_commands.py +++ b/markitect/plugins/builtin/markdown_commands.py @@ -2033,6 +2033,8 @@ def md_list_command(ctx, output_format, names_only): 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('--engine', type=str, default=None, + help='Rendering engine to use (default: testdrive-jsui for edit/insert, standard for view)') @click.option('--editor-theme', default='github', type=click.Choice(['github', 'monokai', 'tomorrow', 'dark']), help='Editor theme for live edit mode (default: github)') @@ -2057,7 +2059,7 @@ def md_list_command(ctx, output_format, names_only): @click.option('--image-max-height', type=str, default=None, help='Maximum height for images (default: 20cm, supports px, em, %, cm, in, etc.)') @click.pass_context -def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_theme, +def md_render_command(ctx, input_file, output, theme, css, edit, insert, engine, editor_theme, keyboard_shortcuts, use_publication_dir, dont_use_publication_dir, nodogtag, ship_assets, no_ship_assets, verbose, silent, image_max_width, image_max_height): """ @@ -2171,21 +2173,83 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_ # For directory output, ship to the same directory as the HTML file _ship_assets(input_path, output_path.parent, verbose, silent) - # Initialize clean document manager - from markitect.clean_document_manager import CleanDocumentManager - doc_manager = CleanDocumentManager(config.get('db_manager')) + # Determine rendering engine to use + if engine is None: + # Default engine selection + if edit or insert: + engine = 'testdrive-jsui' # Default to testdrive-jsui for interactive modes + else: + engine = 'standard' # Use standard CleanDocumentManager for non-interactive + + # Use plugin system for rendering engines, fallback to standard + if engine != 'standard': + try: + from markitect.plugins import PluginManager, RenderingEngineManager, RenderingConfig + plugin_manager = PluginManager() + rendering_manager = RenderingEngineManager(plugin_manager) + + rendering_engine = rendering_manager.get_engine(engine) + if rendering_engine is None: + if not silent: + click.echo(f"⚠️ Rendering engine '{engine}' not found, falling back to standard", err=True) + engine = 'standard' + elif not silent: + modes = rendering_engine.get_supported_modes() + current_mode = 'edit' if edit else ('insert' if insert else 'view') + if not rendering_engine.validate_mode(current_mode): + click.echo(f"⚠️ Engine '{engine}' doesn't support mode '{current_mode}', falling back to standard", err=True) + engine = 'standard' + else: + click.echo(f"🎯 Using rendering engine: {engine} (supports: {', '.join(modes)})") + + except ImportError as e: + if not silent: + click.echo(f"⚠️ Plugin system not available ({e}), using standard rendering", err=True) + engine = 'standard' + + # Initialize document manager or rendering engine + if engine == 'standard': + from markitect.clean_document_manager import CleanDocumentManager + doc_manager = CleanDocumentManager(config.get('db_manager')) # Render the file if edit: - # Edit mode - generate HTML with editing capabilities - result = doc_manager.render_file(input_file, str(output_path), - template=theme, css=css, - edit_mode=True, - editor_theme=editor_theme, - keyboard_shortcuts=keyboard_shortcuts, - nodogtag=nodogtag, - image_max_width=final_image_max_width, - image_max_height=final_image_max_height) + if engine != 'standard': + # Plugin-based rendering for edit mode + try: + # Read markdown content + content = input_path.read_text(encoding='utf-8') + + # Configure rendering + render_config = RenderingConfig( + asset_base_url="_markitect", + development_mode=False, # Production deployment for CLI usage + output_directory=output_path.parent + ) + + # Render using plugin + html_content = rendering_engine.render_document(content, 'edit', render_config) + + # Write output + output_path.write_text(html_content, encoding='utf-8') + result = True + + except Exception as e: + if not silent: + click.echo(f"❌ Plugin rendering failed: {e}", err=True) + click.echo(" Falling back to standard rendering...", err=True) + engine = 'standard' + + if engine == 'standard': + # Standard edit mode - generate HTML with editing capabilities + result = doc_manager.render_file(input_file, str(output_path), + template=theme, css=css, + edit_mode=True, + editor_theme=editor_theme, + keyboard_shortcuts=keyboard_shortcuts, + nodogtag=nodogtag, + image_max_width=final_image_max_width, + image_max_height=final_image_max_height) if not silent: click.echo(f"✅ Rendered with INTERACTIVE editing mode to: {output_path}") @@ -2197,15 +2261,48 @@ def md_render_command(ctx, input_file, output, theme, css, edit, insert, editor_ 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, - image_max_width=final_image_max_width, - image_max_height=final_image_max_height) + if engine != 'standard': + # Plugin-based rendering for insert mode + try: + # Read markdown content + content = input_path.read_text(encoding='utf-8') + + # Configure rendering + render_config = RenderingConfig( + asset_base_url="_markitect", + development_mode=False, # Production deployment for CLI usage + output_directory=output_path.parent + ) + + # Render using plugin (note: insert mode may not be supported by all plugins) + if rendering_engine.validate_mode('insert'): + html_content = rendering_engine.render_document(content, 'insert', render_config) + else: + # Fallback to edit mode if insert not supported + html_content = rendering_engine.render_document(content, 'edit', render_config) + if not silent: + click.echo(f"ℹ️ Engine '{engine}' doesn't support insert mode, using edit mode instead") + + # Write output + output_path.write_text(html_content, encoding='utf-8') + result = True + + except Exception as e: + if not silent: + click.echo(f"❌ Plugin rendering failed: {e}", err=True) + click.echo(" Falling back to standard rendering...", err=True) + engine = 'standard' + + if engine == 'standard': + # Standard 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, + image_max_width=final_image_max_width, + image_max_height=final_image_max_height) if not silent: click.echo(f"✅ Rendered with INTERACTIVE insert mode to: {output_path}") diff --git a/markitect/plugins/rendering.py b/markitect/plugins/rendering.py index dbeedb97..2ff4ed53 100644 --- a/markitect/plugins/rendering.py +++ b/markitect/plugins/rendering.py @@ -183,7 +183,7 @@ class RenderingEngineManager: def _discover_rendering_engines(self): """Discover rendering engine plugins.""" - # Get all plugins from the main plugin manager + # First, try to load plugins from main plugin manager all_plugins = self.plugin_manager.discover_plugins() for plugin_name, plugin_info in all_plugins.items(): @@ -197,6 +197,22 @@ class RenderingEngineManager: except Exception as e: print(f"⚠️ Failed to load rendering engine {plugin_name}: {e}") + # Additionally, try to directly import and register known rendering engines + self._register_builtin_rendering_engines() + + def _register_builtin_rendering_engines(self): + """Register built-in rendering engines directly.""" + try: + # Import and register testdrive-jsui engine + from .testdrive_jsui import TestDriveJSUIEngine + engine = TestDriveJSUIEngine() + self._engines[engine.metadata.name] = engine + print(f"✅ Registered built-in rendering engine: {engine.metadata.name}") + except ImportError as e: + print(f"⚠️ Could not import testdrive-jsui engine: {e}") + except Exception as e: + print(f"⚠️ Failed to register testdrive-jsui engine: {e}") + def get_engine(self, name: str) -> Optional[RenderingEnginePlugin]: """Get a rendering engine by name.""" return self._engines.get(name) diff --git a/markitect/templates/edit-mode.html b/markitect/templates/edit-mode.html new file mode 100644 index 00000000..a37ae88e --- /dev/null +++ b/markitect/templates/edit-mode.html @@ -0,0 +1,131 @@ + + +
+ + +