""" Clean Document Manager - Simplified version with only clean editor support """ import json import re from pathlib import Path from typing import Dict, Any, Optional class CleanDocumentManager: """ Simplified document manager that only supports the clean editor implementation. All legacy code has been removed for clarity and maintainability. """ def __init__(self, db_manager=None): self.db_manager = db_manager def store_document(self, file_path: str, content: str, ast: list = None, front_matter: dict = None): """Store a document in the database.""" if self.db_manager: from pathlib import Path filename = Path(file_path).name return self.db_manager.store_markdown_file(filename, content) def get_file(self, file_path: str) -> Dict[str, Any]: """ Retrieve a markdown file from the database. Args: file_path: Path to the markdown file to retrieve Returns: Dictionary containing file content and metadata Raises: FileNotFoundError: If file is not found in database """ if not self.db_manager: raise ValueError("Database manager not initialized") # Get file from database file_data = self.db_manager.get_markdown_file(file_path) if file_data is None: raise FileNotFoundError(f"File '{file_path}' not found in database") return { 'content': file_data.get('content', ''), 'metadata': { 'filename': file_data.get('filename', file_path), 'front_matter': file_data.get('front_matter'), 'size': len(file_data.get('content', '')), 'modified': file_data.get('modified') } } def render_file(self, input_file: str, output_file: str, template: str = None, css: str = None, edit_mode: bool = False, insert_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True, nodogtag: bool = False, image_max_width: str = '12cm', image_max_height: str = '20cm') -> Dict[str, Any]: """ Render a markdown file to HTML with optional clean editing capabilities. """ input_path = Path(input_file) output_path = Path(output_file) if not input_path.exists(): raise FileNotFoundError(f"Input file not found: {input_file}") # Read markdown content raw_markdown_content = input_path.read_text(encoding='utf-8') # Process base64 images - relocate payloads to document end markdown_content, base64_references = self._process_base64_images(raw_markdown_content) # Extract title from markdown (first h1 heading) title = self._extract_title_from_markdown(markdown_content) # Get original filename without extension original_filename = input_path.stem # Get version information version_info = self._get_version_info() # Generate HTML content html_content = self._generate_html_template( markdown_content=markdown_content, title=title, css=css, template=template, edit_mode=edit_mode, insert_mode=insert_mode, editor_theme=editor_theme, keyboard_shortcuts=keyboard_shortcuts, original_filename=original_filename, version_info=version_info, nodogtag=nodogtag, image_max_width=image_max_width, image_max_height=image_max_height, base64_references=base64_references ) # Write HTML file output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text(html_content, encoding='utf-8') return { 'success': True, 'input_file': str(input_path), 'output_file': str(output_path), 'edit_mode': edit_mode, 'editor_theme': editor_theme } def _extract_title_from_markdown(self, markdown_content: str) -> str: """Extract title from first h1 heading in markdown.""" match = re.search(r'^#\s+(.+)', markdown_content, re.MULTILINE) if match: return match.group(1).strip() return "Markdown Document" def _process_base64_images(self, markdown_content: str) -> tuple: """ Process base64 encoded images in markdown content. - Extracts base64 image data URLs - Replaces them with reference links - Returns processed content and reference mapping Returns: tuple: (processed_markdown, base64_references_dict) """ import re import uuid # Pattern to match base64 image data URLs base64_pattern = r'!\[([^\]]*)\]\(data:image/([^;]+);base64,([^)]+)\)' base64_references = {} reference_definitions = [] processed_content = markdown_content # Find all base64 images matches = list(re.finditer(base64_pattern, markdown_content)) for i, match in enumerate(matches): alt_text = match.group(1) image_type = match.group(2) # png, jpeg, svg+xml, etc. base64_data = match.group(3) # Generate a unique reference ID ref_id = f"base64-image-{i+1}" # Store the mapping base64_references[ref_id] = { 'alt': alt_text, 'type': image_type, 'data': base64_data, 'full_data_url': f"data:image/{image_type};base64,{base64_data}" } # Replace the inline base64 with reference original_match = match.group(0) reference_link = f"![{alt_text}][{ref_id}]" processed_content = processed_content.replace(original_match, reference_link, 1) # Create reference definition for the end of document reference_definitions.append(f"[{ref_id}]: data:image/{image_type};base64,{base64_data}") # Add reference definitions to the end of the document if any base64 images were found if reference_definitions: # Ensure there's a blank line before the references if not processed_content.endswith('\n\n'): if processed_content.endswith('\n'): processed_content += '\n' else: processed_content += '\n\n' # Add a comment to indicate the base64 reference section processed_content += "\n" processed_content += '\n'.join(reference_definitions) + '\n' return processed_content, base64_references def _get_version_info(self) -> dict: """Get repository name and version information.""" from .__version__ import get_version_info version_info = get_version_info() # Transform to the format expected by the editor return { 'repo_name': 'Markitect', 'version': version_info['full_version'], 'git_info': '' # Already included in full_version } def _get_template_css(self, template: str = None, image_max_width: str = '12cm', image_max_height: str = '20cm') -> str: """Generate layered theme CSS styles.""" # Import layered theme functions from markitect.plugins.builtin.markdown_commands import ( parse_theme_string, combine_theme_properties, TEMPLATE_STYLES ) # Handle layered themes or fall back to legacy if template and ',' in template: # New layered theme system theme_list = parse_theme_string(template) combined_props = combine_theme_properties(theme_list) return self._generate_layered_css(combined_props, image_max_width, image_max_height) else: # Legacy single theme or fallback if not template or template not in TEMPLATE_STYLES: # Use default layered themes or the specified theme theme_list = parse_theme_string(template or 'basic') combined_props = combine_theme_properties(theme_list) return self._generate_layered_css(combined_props, image_max_width, image_max_height) else: # Legacy theme - convert to layered theme_list = parse_theme_string(template) combined_props = combine_theme_properties(theme_list) return self._generate_layered_css(combined_props, image_max_width, image_max_height) def _generate_layered_css(self, properties: dict, image_max_width: str = '12cm', image_max_height: str = '20cm') -> str: """Generate CSS from combined theme properties.""" # Set defaults for missing properties (properties override defaults) defaults = { 'body_background': '#ffffff', 'body_color': '#333333', 'font_family': '-apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif', 'max_width': '800px', 'heading_color': '#333333', # Use same as body color by default 'heading_style': 'simple', 'text_align': 'left', 'code_background': '#f6f8fa', 'code_color': '#333333', 'border_color': '#d0d7de', 'blockquote_border': '#dfe2e5', 'blockquote_color': '#6a737d', 'table_border': '#d0d7de', 'table_header_bg': '#f6f8fa', 'accent_color': None, 'secondary_color': None } # Merge defaults first, then override with theme properties props = {**defaults, **properties} # Base CSS base_css = f""" body {{ font-family: {props['font_family']}; max-width: {props['max_width']}; margin: 0 auto; padding: 2rem; line-height: 1.6; color: {props['body_color']}; background-color: {props['body_background']}; }} #markdown-content {{ min-height: 200px; }}""" # Heading styles heading_css = "" if props['heading_style'] == 'underlined': heading_css = f""" h1, h2, h3, h4, h5, h6 {{ color: {props['heading_color']}; border-bottom: 1px solid {props['border_color']}; padding-bottom: 0.3em; }}""" elif props['heading_style'] == 'centered': heading_css = f""" h1, h2, h3, h4, h5, h6 {{ color: {props['heading_color']}; margin-top: 2rem; margin-bottom: 1rem; }} h1 {{ text-align: center; font-size: 2.2em; border-bottom: 2px solid {props['heading_color']}; padding-bottom: 0.5rem; }}""" else: # simple heading_css = f""" h1, h2, h3, h4, h5, h6 {{ color: {props['heading_color']}; }}""" # Text alignment text_css = "" if props['text_align'] == 'justify': text_css = """ p { text-align: justify; margin-bottom: 1.2rem; }""" # Element styling element_css = f""" pre {{ background-color: {props['code_background']}; color: {props['code_color']}; padding: 1rem; border-radius: 6px; overflow-x: auto; border: 1px solid {props['border_color']}; }} code {{ background-color: {props['code_background']}; color: {props['code_color']}; padding: 0.2em 0.4em; border-radius: 3px; font-size: 0.9em; }} pre code {{ background: none; padding: 0; }} blockquote {{ border-left: 4px solid {props['blockquote_border']}; margin: 0; padding-left: 1rem; color: {props['blockquote_color']}; }} table {{ font-size: 0.85em; border-collapse: collapse; margin: 1rem 0; width: 100%; border: 1px solid {props['table_border']}; }} th, td {{ font-size: inherit; border: 1px solid {props['table_border']}; padding: 0.5rem; text-align: left; }} th {{ background-color: {props['table_header_bg']}; font-weight: 600; }}""" # Link styling link_css = "" if props.get('link_color'): link_css = f""" a {{ color: {props['link_color']}; text-decoration: underline; }}""" if props.get('link_hover_color'): link_css += f""" a:hover {{ color: {props['link_hover_color']}; }}""" else: link_css += """ a:hover { opacity: 0.8; }""" # Branding accents (if specified and no link_color already set) accent_css = "" if props.get('accent_color') and not props.get('link_color'): accent_css = f""" a {{ color: {props['accent_color']}; }} a:hover {{ opacity: 0.8; }}""" # UI theme styling for editor interface elements ui_css = "" if props.get('editor_panel_bg'): ui_css = f""" .markitect-edit-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-edit-mode .ui-edit-floater-header {{ color: {props.get('editor_text_color', '#212529')}; }} .markitect-edit-mode .ui-edit-floater-header h3 {{ color: {props.get('editor_text_color', '#212529')}; }} .markitect-edit-mode .ui-edit-inline-panel {{ background: {props['editor_panel_bg']}; border: 1px solid {props.get('editor_panel_border', '#dee2e6')}; box-shadow: 0 2px 8px {props.get('editor_shadow', 'rgba(0,0,0,0.1)')}; color: {props.get('editor_text_color', '#212529')}; border-radius: 8px; padding: 12px; margin: 8px 0; }} .markitect-edit-mode .ui-edit-button {{ background: {props.get('editor_button_bg', '#ffffff')}; color: {props.get('editor_text_color', '#212529')}; border: 1px solid {props.get('editor_panel_border', '#dee2e6')}; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; min-width: 70px; font-weight: 500; transition: all 0.2s; }} .markitect-edit-mode .ui-edit-button:hover {{ background: {props.get('editor_button_hover', '#e9ecef')}; }} .markitect-edit-mode .ui-edit-button:active, .markitect-edit-mode .ui-edit-button.active {{ background: {props.get('editor_button_active', '#dee2e6')}; }} .markitect-edit-mode .ui-edit-button-accept {{ background: {props.get('editor_accept_bg', '#4caf50')}; color: white; }} .markitect-edit-mode .ui-edit-button-accept:hover {{ background: {props.get('editor_accept_hover', '#388e3c')}; }} .markitect-edit-mode .ui-edit-button-cancel {{ background: {props.get('editor_cancel_bg', '#f44336')}; color: white; }} .markitect-edit-mode .ui-edit-button-cancel:hover {{ background: {props.get('editor_cancel_hover', '#d32f2f')}; }} .markitect-edit-mode .ui-edit-button-reset {{ background: {props.get('editor_reset_bg', '#ff9800')}; color: white; }} .markitect-edit-mode .ui-edit-button-reset:hover {{ background: {props.get('editor_reset_hover', '#f57c00')}; }} .markitect-edit-mode .ui-edit-button-secondary {{ background: {props.get('editor_secondary_bg', '#6c757d')}; color: white; }} .markitect-edit-mode .ui-edit-button-secondary:hover {{ background: {props.get('editor_secondary_hover', '#545b62')}; }} .markitect-edit-mode .ui-edit-section-frame {{ border: 2px solid {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))}; box-shadow: 0 0 0 3px {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))}33; }} .markitect-edit-mode .ui-edit-textarea {{ border: 1px solid {props.get('editor_panel_border', '#dee2e6')}; color: {props.get('editor_text_color', '#212529')}; background: {props.get('editor_button_bg', '#ffffff')}; }} .markitect-edit-mode .ui-edit-textarea:focus {{ border-color: {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))}; box-shadow: 0 0 0 2px {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))}33; }} .markitect-edit-mode .ui-edit-modal-overlay {{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 999; display: flex; align-items: center; justify-content: center; opacity: 0; visibility: hidden; transition: opacity 0.3s, visibility 0.3s; }} .markitect-edit-mode .ui-edit-modal-overlay.active {{ opacity: 1; visibility: visible; }} .markitect-edit-mode .ui-edit-modal {{ background: {props['editor_panel_bg']}; border: 1px solid {props.get('editor_panel_border', '#dee2e6')}; box-shadow: 0 8px 32px {props.get('editor_shadow', 'rgba(0,0,0,0.2)')}; color: {props.get('editor_text_color', '#212529')}; border-radius: 8px; max-width: 600px; max-height: 80vh; width: 90%; overflow: hidden; transform: scale(0.9) translateY(-20px); transition: transform 0.3s; }} .markitect-edit-mode .ui-edit-modal-overlay.active .ui-edit-modal {{ transform: scale(1) translateY(0); }} .markitect-edit-mode .ui-edit-modal-header {{ padding: 20px 24px 16px; border-bottom: 1px solid {props.get('editor_panel_border', '#dee2e6')}; display: flex; justify-content: space-between; align-items: center; }} .markitect-edit-mode .ui-edit-modal-title {{ margin: 0; font-size: 18px; font-weight: 600; color: {props.get('editor_text_color', '#212529')}; }} .markitect-edit-mode .ui-edit-modal-close {{ background: none; border: none; font-size: 24px; cursor: pointer; color: {props.get('editor_text_color', '#212529')}; padding: 4px; border-radius: 4px; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; }} .markitect-edit-mode .ui-edit-modal-close:hover {{ background: {props.get('editor_button_hover', '#e9ecef')}; }} .markitect-edit-mode .ui-edit-modal-body {{ padding: 20px 24px; overflow-y: auto; max-height: 60vh; }} .markitect-edit-mode .ui-edit-modal-content {{ white-space: pre-line; line-height: 1.5; font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif; font-size: 14px; }} .markitect-edit-mode .ui-edit-modal-section {{ margin-bottom: 16px; }} .markitect-edit-mode .ui-edit-modal-section:last-child {{ margin-bottom: 0; }} .markitect-edit-mode .ui-edit-modal-section-title {{ font-weight: 600; margin-bottom: 8px; color: {props.get('editor_text_color', '#212529')}; }} .markitect-edit-mode .ui-edit-modal-footer {{ padding: 16px 24px 20px; border-top: 1px solid {props.get('editor_panel_border', '#dee2e6')}; text-align: right; }} /* 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')}; }} """ # Image size CSS with configurable limits image_css = f""" img {{ max-width: {image_max_width}; max-height: {image_max_height}; height: auto; display: block; margin: 1rem auto; }}""" return f"" def _get_legacy_template_css(self, template: str, image_max_width: str = '12cm', image_max_height: str = '20cm') -> str: """Legacy CSS generation - kept for backward compatibility.""" # Import template styles from markitect.plugins.builtin.markdown_commands import TEMPLATE_STYLES # Use basic as default if no template specified if not template or template not in TEMPLATE_STYLES: template = 'basic' style_config = TEMPLATE_STYLES[template] # Base CSS that's common to all templates base_css = f""" body {{ font-family: {style_config['font_family']}; max-width: {style_config['max_width']}; margin: 0 auto; padding: 2rem; line-height: 1.6; color: {style_config['body_color']}; }} #markdown-content {{ min-height: 200px; }}""" # Convert legacy template config to layered format legacy_config = TEMPLATE_STYLES[template] layered_props = { 'font_family': legacy_config['font_family'], 'max_width': legacy_config['max_width'], 'body_color': legacy_config['body_color'], } return self._generate_layered_css(layered_props, image_max_width, image_max_height) def _generate_html_template(self, markdown_content: str, title: str, css: str = None, template: str = None, 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, image_max_width: str = '12cm', image_max_height: str = '20cm', base64_references: dict = None) -> str: """Generate clean HTML template.""" # Add dogtag to markdown content if not disabled if not nodogtag: import datetime import getpass now = datetime.datetime.now() datetime_str = now.strftime("%Y-%m-%d %H:%M:%S") try: username = getpass.getuser() except: username = "user" # Create username link only for 'worsch', otherwise just show username if username == 'worsch': username_link = f'{username}' else: username_link = username dogtag = f'\n\n---\n*-- html from markdown by MarkiTect on {datetime_str} by {username_link}*' markdown_content_with_dogtag = markdown_content + dogtag else: markdown_content_with_dogtag = markdown_content dogtag = "" # Pass original markdown content to editor (without dogtag for editing) # But make dogtag available separately for protected display in editor js_markdown_content = json.dumps(markdown_content) js_markdown_content_with_dogtag = json.dumps(markdown_content_with_dogtag) js_dogtag_content = json.dumps(dogtag) js_base64_references = json.dumps(base64_references or {}) # Handle CSS styles css_content = "" if css: try: css_path = Path(css) if css_path.exists(): css_file_content = css_path.read_text(encoding='utf-8') css_content = f"" else: css_content = f'' except Exception: css_content = f'' # Generate template-specific CSS default_css = self._get_template_css(template, image_max_width, image_max_height) # Load clean editor JavaScript files editor_scripts = "" editor_config = "" body_classes = "" if edit_mode: body_classes = ' class="markitect-edit-mode"' # Configuration for clean editor version_str = f"{version_info['repo_name']} v{version_info['version']}{version_info['git_info']}" if version_info else "Markitect v0.5.0.dev" 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, 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;""" # Load clean editor architecture for both edit and insert modes if edit_mode or insert_mode: editor_scripts = self._get_clean_editor_scripts() else: editor_scripts = "" # Generate the complete HTML template html_template = f"""