2025-10-26 07:51:43 +01:00
"""
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
2025-10-28 03:50:21 +01:00
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 ' )
}
}
2025-10-26 07:51:43 +01:00
def render_file ( self , input_file : str , output_file : str , template : str = None , css : str = None ,
2025-11-04 09:11:50 +01:00
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 ] :
2025-10-26 07:51:43 +01:00
"""
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
2025-11-04 09:11:50 +01:00
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 )
2025-10-26 07:51:43 +01:00
# 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 ,
2025-10-28 23:55:21 +01:00
insert_mode = insert_mode ,
2025-10-26 07:51:43 +01:00
editor_theme = editor_theme ,
keyboard_shortcuts = keyboard_shortcuts ,
original_filename = original_filename ,
2025-10-28 03:50:21 +01:00
version_info = version_info ,
2025-11-04 09:11:50 +01:00
nodogtag = nodogtag ,
image_max_width = image_max_width ,
image_max_height = image_max_height ,
base64_references = base64_references
2025-10-26 07:51:43 +01:00
)
# 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 "
2025-11-04 09:11:50 +01:00
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 + = " <!-- Base64 Image References --> \n "
processed_content + = ' \n ' . join ( reference_definitions ) + ' \n '
return processed_content , base64_references
2025-10-26 07:51:43 +01:00
def _get_version_info ( self ) - > dict :
""" Get repository name and version information. """
2025-10-28 03:50:21 +01:00
from . __version__ import get_version_info
2025-10-26 07:51:43 +01:00
2025-10-28 03:50:21 +01:00
version_info = get_version_info ( )
2025-10-26 07:51:43 +01:00
2025-10-28 03:50:21 +01:00
# Transform to the format expected by the editor
return {
' repo_name ' : ' Markitect ' ,
' version ' : version_info [ ' full_version ' ] ,
' git_info ' : ' ' # Already included in full_version
}
2025-10-26 07:51:43 +01:00
2025-11-04 09:11:50 +01:00
def _get_template_css ( self , template : str = None , image_max_width : str = ' 12cm ' , image_max_height : str = ' 20cm ' ) - > str :
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
""" Generate layered theme CSS styles. """
# Import layered theme functions
from markitect . plugins . builtin . markdown_commands import (
parse_theme_string , combine_theme_properties , TEMPLATE_STYLES
)
2025-10-26 07:51:43 +01:00
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
# 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 )
2025-11-04 09:11:50 +01:00
return self . _generate_layered_css ( combined_props , image_max_width , image_max_height )
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
else :
# Legacy single theme or fallback
if not template or template not in TEMPLATE_STYLES :
2025-10-27 21:56:38 +01:00
# Use default layered themes or the specified theme
theme_list = parse_theme_string ( template or ' basic ' )
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
combined_props = combine_theme_properties ( theme_list )
2025-11-04 09:11:50 +01:00
return self . _generate_layered_css ( combined_props , image_max_width , image_max_height )
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
else :
# Legacy theme - convert to layered
theme_list = parse_theme_string ( template )
combined_props = combine_theme_properties ( theme_list )
2025-11-04 09:11:50 +01:00
return self . _generate_layered_css ( combined_props , image_max_width , image_max_height )
2025-10-26 07:51:43 +01:00
2025-11-04 09:11:50 +01:00
def _generate_layered_css ( self , properties : dict , image_max_width : str = ' 12cm ' , image_max_height : str = ' 20cm ' ) - > str :
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
""" Generate CSS from combined theme properties. """
2025-10-26 07:51:43 +01:00
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
# 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 ' ] } ;
2025-10-26 07:51:43 +01:00
margin : 0 auto ;
padding : 2 rem ;
line - height : 1.6 ;
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
color : { props [ ' body_color ' ] } ;
background - color : { props [ ' body_background ' ] } ;
} }
#markdown-content {{
2025-10-26 07:51:43 +01:00
min - height : 200 px ;
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
} } """
# Heading styles
2025-11-10 10:53:37 +01:00
heading_font_style = " "
if ' heading_font_family ' in props and props [ ' heading_font_family ' ] :
heading_font_style = f " font-family: { props [ ' heading_font_family ' ] } ; "
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
heading_css = " "
if props [ ' heading_style ' ] == ' underlined ' :
heading_css = f """
h1 , h2 , h3 , h4 , h5 , h6 { {
color : { props [ ' heading_color ' ] } ;
2025-11-10 10:53:37 +01:00
{ heading_font_style }
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
border - bottom : 1 px solid { props [ ' border_color ' ] } ;
padding - bottom : 0.3 em ;
} } """
elif props [ ' heading_style ' ] == ' centered ' :
heading_css = f """
h1 , h2 , h3 , h4 , h5 , h6 { {
color : { props [ ' heading_color ' ] } ;
2025-11-10 10:53:37 +01:00
{ heading_font_style }
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
margin - top : 2 rem ;
margin - bottom : 1 rem ;
} }
h1 { {
text - align : center ;
font - size : 2.2 em ;
border - bottom : 2 px solid { props [ ' heading_color ' ] } ;
padding - bottom : 0.5 rem ;
} } """
else : # simple
heading_css = f """
h1 , h2 , h3 , h4 , h5 , h6 { {
color : { props [ ' heading_color ' ] } ;
2025-11-10 10:53:37 +01:00
{ heading_font_style }
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
} } """
# Text alignment
text_css = " "
if props [ ' text_align ' ] == ' justify ' :
text_css = """
p {
text - align : justify ;
margin - bottom : 1.2 rem ;
} """
# Element styling
element_css = f """
pre { {
background - color : { props [ ' code_background ' ] } ;
color : { props [ ' code_color ' ] } ;
2025-10-26 07:51:43 +01:00
padding : 1 rem ;
border - radius : 6 px ;
overflow - x : auto ;
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
border : 1 px solid { props [ ' border_color ' ] } ;
} }
code { {
background - color : { props [ ' code_background ' ] } ;
color : { props [ ' code_color ' ] } ;
2025-10-26 07:51:43 +01:00
padding : 0.2 em 0.4 em ;
border - radius : 3 px ;
font - size : 0.9 em ;
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
} }
pre code { {
2025-10-26 07:51:43 +01:00
background : none ;
padding : 0 ;
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
} }
blockquote { {
border - left : 4 px solid { props [ ' blockquote_border ' ] } ;
2025-10-26 07:51:43 +01:00
margin : 0 ;
padding - left : 1 rem ;
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
color : { props [ ' blockquote_color ' ] } ;
} }
table { {
2025-10-27 11:59:16 +01:00
font - size : 0.85 em ;
border - collapse : collapse ;
margin : 1 rem 0 ;
width : 100 % ;
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
border : 1 px solid { props [ ' table_border ' ] } ;
} }
th , td { {
2025-10-27 11:59:16 +01:00
font - size : inherit ;
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
border : 1 px solid { props [ ' table_border ' ] } ;
2025-10-27 11:59:16 +01:00
padding : 0.5 rem ;
text - align : left ;
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
} }
th { {
background - color : { props [ ' table_header_bg ' ] } ;
2025-10-27 11:59:16 +01:00
font - weight : 600 ;
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
} } """
2025-10-27 21:56:38 +01:00
# 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)
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
accent_css = " "
2025-10-27 21:56:38 +01:00
if props . get ( ' accent_color ' ) and not props . get ( ' link_color ' ) :
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
accent_css = f """
a { {
color : { props [ ' accent_color ' ] } ;
} }
a : hover { {
opacity : 0.8 ;
} } """
2025-10-27 22:33:51 +01:00
# UI theme styling for editor interface elements
ui_css = " "
if props . get ( ' editor_panel_bg ' ) :
ui_css = f """
2025-10-27 23:00:42 +01:00
. markitect - edit - mode . ui - edit - floater - panel { {
2025-10-27 22:33:51 +01:00
background : { props [ ' editor_panel_bg ' ] } ;
border : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
box - shadow : 0 4 px 12 px { props . get ( ' editor_shadow ' , ' rgba(0,0,0,0.1) ' ) } ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
} }
2025-10-27 23:00:42 +01:00
. markitect - edit - mode . ui - edit - floater - header { {
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
} }
2025-10-28 03:50:21 +01:00
. 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 : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
box - shadow : 0 2 px 8 px { props . get ( ' editor_shadow ' , ' rgba(0,0,0,0.1) ' ) } ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
border - radius : 8 px ;
padding : 12 px ;
margin : 8 px 0 ;
} }
2025-10-27 23:00:42 +01:00
. markitect - edit - mode . ui - edit - button { {
2025-10-27 22:33:51 +01:00
background : { props . get ( ' editor_button_bg ' , ' #ffffff ' ) } ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
border : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
2025-10-28 03:50:21 +01:00
padding : 8 px 12 px ;
border - radius : 4 px ;
cursor : pointer ;
font - size : 12 px ;
min - width : 70 px ;
font - weight : 500 ;
transition : all 0.2 s ;
2025-10-27 22:33:51 +01:00
} }
2025-10-27 23:00:42 +01:00
. markitect - edit - mode . ui - edit - button : hover { {
2025-10-27 22:33:51 +01:00
background : { props . get ( ' editor_button_hover ' , ' #e9ecef ' ) } ;
} }
2025-10-27 23:00:42 +01:00
. markitect - edit - mode . ui - edit - button : active ,
. markitect - edit - mode . ui - edit - button . active { {
2025-10-27 22:33:51 +01:00
background : { props . get ( ' editor_button_active ' , ' #dee2e6 ' ) } ;
} }
2025-10-27 23:00:42 +01:00
. markitect - edit - mode . ui - edit - button - accept { {
2025-10-28 03:50:21 +01:00
background : { props . get ( ' editor_accept_bg ' , ' #4caf50 ' ) } ;
color : white ;
} }
. markitect - edit - mode . ui - edit - button - accept : hover { {
background : { props . get ( ' editor_accept_hover ' , ' #388e3c ' ) } ;
2025-10-27 23:00:42 +01:00
} }
. markitect - edit - mode . ui - edit - button - cancel { {
2025-10-28 03:50:21 +01:00
background : { props . get ( ' editor_cancel_bg ' , ' #f44336 ' ) } ;
color : white ;
} }
. markitect - edit - mode . ui - edit - button - cancel : hover { {
background : { props . get ( ' editor_cancel_hover ' , ' #d32f2f ' ) } ;
2025-10-27 23:00:42 +01:00
} }
. markitect - edit - mode . ui - edit - button - reset { {
2025-10-28 03:50:21 +01:00
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 ' ) } ;
2025-10-27 23:00:42 +01:00
} }
. markitect - edit - mode . ui - edit - section - frame { {
2025-10-28 03:50:21 +01:00
border : 2 px solid { props . get ( ' editor_focus_color ' , props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) ) } ;
box - shadow : 0 0 0 3 px { props . get ( ' editor_focus_color ' , props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) ) } 33 ;
2025-10-27 22:33:51 +01:00
} }
2025-10-27 23:00:42 +01:00
. markitect - edit - mode . ui - edit - textarea { {
2025-10-27 22:33:51 +01:00
border : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
2025-10-27 23:00:42 +01:00
background : { props . get ( ' editor_button_bg ' , ' #ffffff ' ) } ;
2025-10-27 22:33:51 +01:00
} }
2025-10-27 23:00:42 +01:00
. markitect - edit - mode . ui - edit - textarea : focus { {
2025-10-28 03:50:21 +01:00
border - color : { props . get ( ' editor_focus_color ' , props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) ) } ;
box - shadow : 0 0 0 2 px { 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.3 s , visibility 0.3 s ;
} }
. markitect - edit - mode . ui - edit - modal - overlay . active { {
opacity : 1 ;
visibility : visible ;
} }
. markitect - edit - mode . ui - edit - modal { {
background : { props [ ' editor_panel_bg ' ] } ;
border : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
box - shadow : 0 8 px 32 px { props . get ( ' editor_shadow ' , ' rgba(0,0,0,0.2) ' ) } ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
border - radius : 8 px ;
max - width : 600 px ;
max - height : 80 vh ;
width : 90 % ;
overflow : hidden ;
transform : scale ( 0.9 ) translateY ( - 20 px ) ;
transition : transform 0.3 s ;
} }
. markitect - edit - mode . ui - edit - modal - overlay . active . ui - edit - modal { {
transform : scale ( 1 ) translateY ( 0 ) ;
} }
. markitect - edit - mode . ui - edit - modal - header { {
padding : 20 px 24 px 16 px ;
border - bottom : 1 px 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 : 18 px ;
font - weight : 600 ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
} }
. markitect - edit - mode . ui - edit - modal - close { {
background : none ;
border : none ;
font - size : 24 px ;
cursor : pointer ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
padding : 4 px ;
border - radius : 4 px ;
width : 32 px ;
height : 32 px ;
display : flex ;
align - items : center ;
justify - content : center ;
transition : background - color 0.2 s ;
} }
. markitect - edit - mode . ui - edit - modal - close : hover { {
background : { props . get ( ' editor_button_hover ' , ' #e9ecef ' ) } ;
} }
. markitect - edit - mode . ui - edit - modal - body { {
padding : 20 px 24 px ;
overflow - y : auto ;
max - height : 60 vh ;
} }
. 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 : 14 px ;
} }
. markitect - edit - mode . ui - edit - modal - section { {
margin - bottom : 16 px ;
} }
. 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 : 8 px ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
} }
. markitect - edit - mode . ui - edit - modal - footer { {
padding : 16 px 24 px 20 px ;
border - top : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
text - align : right ;
} }
2025-10-28 22:36:15 +01:00
/ * Confirmation Dialog Styles * /
. markitect - edit - mode . ui - edit - confirmation - modal { {
max - width : 500 px ;
} }
. markitect - edit - mode . ui - edit - confirmation - content { {
font - size : 16 px ;
line - height : 1.5 ;
margin - bottom : 24 px ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
} }
. markitect - edit - mode . ui - edit - confirmation - warning { {
background : { props . get ( ' editor_warning_bg ' , ' #fff3cd ' ) } ;
border : 1 px solid { props . get ( ' editor_warning_border ' , ' #ffeaa7 ' ) } ;
color : { props . get ( ' editor_warning_text ' , ' #856404 ' ) } ;
padding : 12 px 16 px ;
border - radius : 6 px ;
margin : 16 px 0 ;
font - size : 14 px ;
} }
. markitect - edit - mode . ui - edit - confirmation - buttons { {
display : flex ;
gap : 12 px ;
justify - content : flex - end ;
} }
. markitect - edit - mode . ui - edit - button - confirm { {
background : { props . get ( ' editor_danger_button ' , ' #dc3545 ' ) } ;
color : white ;
border : none ;
padding : 10 px 20 px ;
border - radius : 6 px ;
font - size : 14 px ;
font - weight : 500 ;
cursor : pointer ;
transition : background - color 0.2 s , transform 0.1 s ;
} }
. markitect - edit - mode . ui - edit - button - confirm : hover { {
background : { props . get ( ' editor_danger_button_hover ' , ' #c82333 ' ) } ;
transform : translateY ( - 1 px ) ;
} }
. markitect - edit - mode . ui - edit - button - confirm : active { {
transform : translateY ( 0 ) ;
} }
. markitect - edit - mode . ui - edit - button - confirm : focus { {
outline : 2 px solid { props . get ( ' editor_focus_color ' , ' #007bff ' ) } ;
outline - offset : 2 px ;
} }
. markitect - edit - mode . ui - edit - button - cancel { {
background : { props . get ( ' editor_secondary_button ' , ' #6c757d ' ) } ;
color : white ;
border : none ;
padding : 10 px 20 px ;
border - radius : 6 px ;
font - size : 14 px ;
font - weight : 500 ;
cursor : pointer ;
transition : background - color 0.2 s , transform 0.1 s ;
} }
. markitect - edit - mode . ui - edit - button - cancel : hover { {
background : { props . get ( ' editor_secondary_button_hover ' , ' #545b62 ' ) } ;
transform : translateY ( - 1 px ) ;
} }
. markitect - edit - mode . ui - edit - button - cancel : active { {
transform : translateY ( 0 ) ;
} }
. markitect - edit - mode . ui - edit - button - cancel : focus { {
outline : 2 px solid { props . get ( ' editor_focus_color ' , ' #007bff ' ) } ;
outline - offset : 2 px ;
} }
/ * Document Scroll Indicators * /
. ui - scroll - indicator { {
position : fixed ;
left : 50 % ;
transform : translateX ( - 50 % ) ;
width : 60 px ;
height : 30 px ;
background : { props . get ( ' editor_panel_bg ' , ' #f8f9fa ' ) } ;
border : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
border - radius : 15 px ;
display : flex ;
align - items : center ;
justify - content : center ;
cursor : pointer ;
opacity : 0 ;
visibility : hidden ;
transition : opacity 0.3 s ease , visibility 0.3 s ease , transform 0.2 s ease , background - color 0.2 s ease ;
z - index : 1000 ;
box - shadow : 0 4 px 12 px { 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 : 20 px ;
} }
. ui - scroll - indicator - down { {
bottom : 20 px ;
} }
. ui - scroll - indicator : : before { {
content : ' ' ;
width : 0 ;
height : 0 ;
border - style : solid ;
transition : border - color 0.2 s ease ;
} }
. ui - scroll - indicator - up : : before { {
border - left : 8 px solid transparent ;
border - right : 8 px solid transparent ;
border - bottom : 12 px solid { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
} }
. ui - scroll - indicator - down : : before { {
border - left : 8 px solid transparent ;
border - right : 8 px solid transparent ;
border - top : 12 px 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 ' ) } ;
} }
2025-10-28 23:55:21 +01:00
/ * Insert Mode Specific Styles * /
. markitect - insert - mode . ui - edit - floater - panel { {
background : { props [ ' editor_panel_bg ' ] } ;
border : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
box - shadow : 0 4 px 12 px { 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 : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
box - shadow : 0 2 px 8 px { props . get ( ' editor_shadow ' , ' rgba(0,0,0,0.1) ' ) } ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
border - radius : 8 px ;
padding : 12 px ;
margin : 8 px 0 ;
} }
. markitect - insert - mode . ui - insert - protected - panel { {
border - left : 4 px solid #ff9800;
} }
. markitect - insert - mode . ui - insert - heading - display { {
font - family : ' SF Mono ' , Monaco , ' Cascadia Code ' , monospace ;
font - size : 14 px ;
font - weight : bold ;
padding : 8 px 12 px ;
background : { props . get ( ' editor_warning_bg ' , ' #fff3cd ' ) } ;
border : 1 px solid { props . get ( ' editor_warning_border ' , ' #ffeaa7 ' ) } ;
border - radius : 4 px ;
border - left : 4 px solid #007bff;
color : { props . get ( ' editor_warning_text ' , ' #856404 ' ) } ;
margin - bottom : 8 px ;
} }
. markitect - insert - mode . ui - insert - content - editor { {
border : 1 px 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 2 px { 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 : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
padding : 8 px 12 px ;
border - radius : 4 px ;
cursor : pointer ;
font - size : 12 px ;
min - width : 70 px ;
font - weight : 500 ;
transition : all 0.2 s ;
} }
. 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 : 2 px solid { props . get ( ' editor_focus_color ' , ' #007bff ' ) } ;
box - shadow : 0 0 0 3 px { 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.3 s , visibility 0.3 s ;
} }
. markitect - insert - mode . ui - edit - modal - overlay . active { {
opacity : 1 ;
visibility : visible ;
} }
. markitect - insert - mode . ui - edit - modal { {
background : { props [ ' editor_panel_bg ' ] } ;
border : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
box - shadow : 0 8 px 32 px { props . get ( ' editor_shadow ' , ' rgba(0,0,0,0.2) ' ) } ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
border - radius : 8 px ;
max - width : 600 px ;
max - height : 80 vh ;
width : 90 % ;
overflow : hidden ;
transform : scale ( 0.9 ) translateY ( - 20 px ) ;
transition : transform 0.3 s ;
} }
. markitect - insert - mode . ui - edit - modal - overlay . active . ui - edit - modal { {
transform : scale ( 1 ) translateY ( 0 ) ;
} }
. markitect - insert - mode . ui - edit - modal - header { {
padding : 20 px 24 px 16 px ;
border - bottom : 1 px 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 : 18 px ;
font - weight : 600 ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
} }
. markitect - insert - mode . ui - edit - modal - close { {
background : transparent ;
border : none ;
font - size : 24 px ;
cursor : pointer ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
padding : 0 ;
width : 32 px ;
height : 32 px ;
display : flex ;
align - items : center ;
justify - content : center ;
border - radius : 4 px ;
transition : background - color 0.2 s ;
} }
. markitect - insert - mode . ui - edit - modal - close : hover { {
background : { props . get ( ' editor_button_hover ' , ' #e9ecef ' ) } ;
} }
. markitect - insert - mode . ui - edit - modal - body { {
padding : 20 px 24 px ;
max - height : 400 px ;
overflow - y : auto ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
} }
. markitect - insert - mode . ui - edit - modal - section { {
margin - bottom : 8 px ;
color : { props . get ( ' editor_text_color ' , ' #212529 ' ) } ;
} }
. markitect - insert - mode . ui - edit - modal - footer { {
padding : 16 px 24 px 20 px ;
border - top : 1 px 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 : 500 px ;
} }
. markitect - insert - mode . ui - edit - confirmation - content { {
font - size : 16 px ;
line - height : 1.5 ;
margin - bottom : 24 px ;
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 : 1 px solid { props . get ( ' editor_warning_border ' , ' #ffeaa7 ' ) } ;
border - radius : 6 px ;
padding : 12 px 16 px ;
margin : 16 px 0 ;
font - size : 14 px ;
line - height : 1.4 ;
} }
. markitect - insert - mode . ui - edit - confirmation - buttons { {
display : flex ;
gap : 12 px ;
justify - content : flex - end ;
margin - top : 24 px ;
} }
. markitect - insert - mode . ui - edit - button - confirm { {
background : #dc3545;
color : white ;
border : 1 px solid #dc3545;
padding : 10 px 20 px ;
border - radius : 6 px ;
font - size : 14 px ;
font - weight : 500 ;
cursor : pointer ;
transition : all 0.2 s ;
} }
. 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 : 1 px solid { props . get ( ' editor_panel_border ' , ' #dee2e6 ' ) } ;
padding : 10 px 20 px ;
border - radius : 6 px ;
font - size : 14 px ;
font - weight : 500 ;
cursor : pointer ;
transition : all 0.2 s ;
} }
. markitect - insert - mode . ui - edit - button - cancel : hover { {
background : { props . get ( ' editor_button_hover ' , ' #e9ecef ' ) } ;
} }
2025-10-28 22:36:15 +01:00
"""
2025-10-27 22:33:51 +01:00
2025-11-04 09:11:50 +01:00
# 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 : 1 rem auto ;
} } """
return f " <style> { base_css } { heading_css } { text_css } { element_css } { link_css } { accent_css } { ui_css } { image_css } </style> "
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
2025-11-04 09:11:50 +01:00
def _get_legacy_template_css ( self , template : str , image_max_width : str = ' 12cm ' , image_max_height : str = ' 20cm ' ) - > str :
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
""" 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 : 2 rem ;
line - height : 1.6 ;
color : { style_config [ ' body_color ' ] } ;
} }
#markdown-content {{
min - height : 200 px ;
} } """
# 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 ' ] ,
}
2025-11-04 09:11:50 +01:00
return self . _generate_layered_css ( layered_props , image_max_width , image_max_height )
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
def _generate_html_template ( self , markdown_content : str , title : str , css : str = None , template : str = None ,
2025-11-04 09:11:50 +01:00
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 :
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
""" Generate clean HTML template. """
2025-10-28 03:50:21 +01:00
# Add dogtag to markdown content if not disabled
if not nodogtag :
import datetime
import getpass
now = datetime . datetime . now ( )
datetime_str = now . strftime ( " % Y- % m- %d % H: % M: % S " )
try :
username = getpass . getuser ( )
except :
username = " user "
# Create username link only for 'worsch', otherwise just show username
if username == ' worsch ' :
username_link = f ' <a href= " https://coulomb.social/open/worsch " target= " _blank " > { username } </a> '
else :
username_link = username
dogtag = f ' \n \n --- \n *-- html from markdown by <a href= " https://coulomb.social/open/MarkiTect " target= " _blank " >MarkiTect</a> on { datetime_str } by { username_link } * '
markdown_content_with_dogtag = markdown_content + dogtag
else :
markdown_content_with_dogtag = markdown_content
2025-11-04 09:11:50 +01:00
dogtag = " "
2025-10-28 03:50:21 +01:00
2025-11-04 09:11:50 +01:00
# 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 { } )
feat: implement sophisticated layered theme system for md-render
MAJOR FEATURES:
- **Layered Theme Architecture**: Combine themes across UI, document, and branding scopes
- **Advanced Theme Combinations**: Support complex themes like "dark,academic" or "light,github,corporate"
- **Legacy Compatibility**: Existing --template usage continues to work seamlessly
- **Enhanced CLI Validation**: Proper theme validation with helpful error messages
TECHNICAL IMPROVEMENTS:
- Replace DocumentManager with CleanDocumentManager throughout codebase
- Add ThemeType custom click parameter with comprehensive validation
- Implement parse_theme_string() and combine_theme_properties() functions
- Add _get_template_css() and _generate_layered_css() methods
- Support for UI themes (light/dark), document themes (basic/github/academic), and branding themes (corporate/startup)
THEME CAPABILITIES:
- **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
- **Intelligent property merging**: Later themes override earlier theme properties
QUALITY ASSURANCE:
- All template system tests passing (12/12)
- Fixed import errors and missing dependencies
- Updated test expectations for new validation messages
- Comprehensive validation prevents unknown theme usage
Breaking Change: --template parameter renamed to --theme with enhanced functionality
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 21:31:08 +01:00
# 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 " <style> \n { css_file_content } \n </style> "
else :
css_content = f ' <link rel= " stylesheet " href= " { css } " > '
except Exception :
css_content = f ' <link rel= " stylesheet " href= " { css } " > '
# Generate template-specific CSS
2025-11-04 09:11:50 +01:00
default_css = self . _get_template_css ( template , image_max_width , image_max_height )
2025-10-26 07:51:43 +01:00
# Load clean editor JavaScript files
editor_scripts = " "
editor_config = " "
body_classes = " "
if edit_mode :
body_classes = ' class= " markitect-edit-mode " '
# Configuration for clean editor
2025-10-28 03:50:21 +01:00
version_str = f " { version_info [ ' repo_name ' ] } v { version_info [ ' version ' ] } { version_info [ ' git_info ' ] } " if version_info else " Markitect v0.5.0.dev "
2025-10-26 07:51:43 +01:00
editor_config = f """
const MARKITECT_EDIT_MODE = true ;
const MARKITECT_EDITOR_CONFIG = { {
2025-10-28 23:55:21 +01:00
mode : ' edit ' ,
2025-10-26 07:51:43 +01:00
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 ; """
2025-10-28 23:55:21 +01:00
elif insert_mode :
body_classes = ' class= " markitect-insert-mode " '
2025-10-26 07:51:43 +01:00
2025-10-28 23:55:21 +01:00
# 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 :
2025-10-26 07:51:43 +01:00
editor_scripts = self . _get_clean_editor_scripts ( )
2025-11-04 09:11:50 +01:00
else :
editor_scripts = " "
2025-10-26 07:51:43 +01:00
# Generate the complete HTML template
html_template = f """ <!DOCTYPE html>
< html lang = " en " >
< head >
< meta charset = " utf-8 " >
< meta name = " viewport " content = " width=device-width, initial-scale=1.0 " >
< title > { title } < / title >
{ css_content }
{ default_css }
< script src = " https://cdn.jsdelivr.net/npm/marked/marked.min.js "
onload = " window.markitectMarkedLoaded = true "
onerror = " window.markitectMarkedError = true " > < / script >
< / head >
< body { body_classes } >
< div id = " markdown-content " > < / div >
< script >
const markdownContent = { js_markdown_content } ;
2025-11-04 09:11:50 +01:00
const markdownContentWithDogtag = { js_markdown_content_with_dogtag } ;
const dogtagContent = { js_dogtag_content } ;
window . markitectBase64References = { js_base64_references } ;
2025-10-26 07:51:43 +01:00
{ editor_config }
{ editor_scripts }
/ / Always render content first ( graceful degradation )
document . addEventListener ( ' DOMContentLoaded ' , function ( ) { {
console . log ( " Rendering content... " ) ;
2025-11-04 09:25:15 +01:00
/ / Check if modular components are being used
if ( typeof SectionManager != = ' undefined ' ) { {
console . log ( " ✓ Modular components detected - skipping direct content rendering " ) ;
console . log ( " ✓ Content will be rendered by modular architecture " ) ;
return ;
} }
2025-10-26 07:51:43 +01:00
const contentDiv = document . getElementById ( ' markdown-content ' ) ;
2025-11-04 09:25:15 +01:00
/ / Step 1 : Ensure content is always displayed ( fallback for non - modular mode )
2025-10-26 07:51:43 +01:00
if ( contentDiv ) { {
if ( typeof marked != = ' undefined ' ) { {
try { {
2025-11-04 09:11:50 +01:00
const html = marked . parse ( markdownContentWithDogtag ) ;
2025-10-28 03:50:21 +01:00
/ / Add target = " _blank " to all links
const htmlWithTargetBlank = html . replace ( / < a href = " ([^ " ] * ) " ([^>]*)>/g, ' <a href= " $ 1 " target= " _blank " $2> ' );
contentDiv . innerHTML = htmlWithTargetBlank ;
2025-10-26 07:51:43 +01:00
console . log ( " ✓ Content rendered successfully " ) ;
console . log ( ' ✓ Markdown rendered successfully ' ) ;
} } catch ( error ) { {
contentDiv . innerHTML = ' <p>Error rendering markdown: ' + error . message + ' </p> ' ;
console . error ( " Content rendered with errors " ) ;
console . error ( " Markdown parsing failed: " , error . message ) ;
} }
} } else { {
/ / Fallback : display raw markdown with basic formatting
const fallbackHtml = markdownContent
. replace ( / ^ # (.*$)/gim, '<h1>$1</h1>')
. replace ( / ^ ## (.*$)/gim, '<h2>$1</h2>')
. replace ( / ^ ### (.*$)/gim, '<h3>$1</h3>')
. replace ( / \\* \\* ( . * ? ) \\* \\* / g , ' <strong>$1</strong> ' )
. replace ( / \\* ( . * ? ) \\* / g , ' <em>$1</em> ' )
. replace ( / ^ - ( . * $ ) / gim , ' <li>$1</li> ' )
. replace ( / \\n \\n / g , ' <br><br> ' )
. replace ( / \\n / g , ' <br> ' ) ;
contentDiv . innerHTML = ' <div style= " white-space: pre-wrap; " > ' + fallbackHtml + ' </div> ' ;
console . warn ( " Content rendered with fallback parser " ) ;
console . warn ( " CDN library failed to load - using basic fallback rendering " ) ;
} }
} }
2025-10-28 23:55:21 +01:00
/ / 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 . . . ` ) ;
2025-10-26 07:51:43 +01:00
try { {
console . log ( " Creating clean editor instance... " ) ;
initializeCleanEditor ( ) ;
2025-10-28 23:55:21 +01:00
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 " ) ;
} }
2025-10-26 07:51:43 +01:00
} } catch ( error ) { {
2025-10-28 23:55:21 +01:00
console . error ( ` Clean $ { { mode } } mode failed to initialize : ` , error ) ;
2025-10-26 07:51:43 +01:00
} }
} }
2025-10-28 22:36:15 +01:00
/ / Step 3 : Initialize document scroll indicators ( always available )
try { {
initializeScrollIndicators ( ) ;
} } catch ( error ) { {
console . error ( " Scroll indicators failed to initialize: " , error ) ;
} }
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
/ / Step 4 : Define abstract Control class for UI controls
const Control = { {
/ / Abstract control properties
element : null ,
isExpanded : false ,
2025-11-11 00:29:34 +01:00
isHeaderOnly : false , / / New state for header - only mode
2025-11-10 23:10:33 +01:00
isDragging : false ,
2025-11-11 00:29:34 +01:00
isResizing : false , / / New state for resizing mode
2025-11-10 23:10:33 +01:00
dragOffset : { { x : 0 , y : 0 } } ,
2025-11-11 00:29:34 +01:00
resizeStartSize : { { width : 280 , height : ' auto ' } } ,
2025-11-10 23:10:33 +01:00
originalPosition : { { top : ' 80px ' , left : ' 20px ' } } ,
2025-11-11 00:29:34 +01:00
defaultSize : { { width : 280 , minWidth : 200 , minHeight : 150 } } ,
2025-11-10 23:10:33 +01:00
/ / Configuration properties ( to be overridden by subclasses )
config : { {
icon : ' ? ' ,
title : ' Control ' ,
className : ' control ' ,
defaultContent : ' Template only ' ,
ariaLabel : ' Control ' ,
position : ' w ' / / Default compass position : west ( middle - left )
} } ,
/ / Compass positioning system ( top - aligned for proper expansion )
compassPositions : { {
/ / North positions ( top )
' n ' : { { top : ' 20px ' , left : ' 50 % ' , transform : ' translateX(-50 % ) ' } } ,
' nne ' : { { top : ' 20px ' , left : ' 65 % ' , transform : ' translateX(-50 % ) ' } } ,
' ne ' : { { top : ' 20px ' , right : ' 20px ' } } ,
' ene ' : { { top : ' 80px ' , right : ' 20px ' } } , / / Top - aligned instead of center
/ / East positions ( right )
' e ' : { { top : ' 50vh ' , right : ' 20px ' , transform : ' translateY(-20px) ' } } , / / Anchor at icon level
' ese ' : { { top : ' calc(65vh - 20px) ' , right : ' 20px ' } } , / / Top - aligned
' se ' : { { bottom : ' 20px ' , right : ' 20px ' } } ,
' sse ' : { { bottom : ' 20px ' , right : ' 35 % ' , transform : ' translateX(50 % ) ' } } ,
/ / South positions ( bottom )
' s ' : { { bottom : ' 20px ' , left : ' 50 % ' , transform : ' translateX(-50 % ) ' } } ,
' ssw ' : { { bottom : ' 20px ' , left : ' 35 % ' , transform : ' translateX(-50 % ) ' } } ,
' sw ' : { { bottom : ' 20px ' , left : ' 20px ' } } ,
' wsw ' : { { bottom : ' 80px ' , left : ' 20px ' } } , / / Top - aligned instead of center
/ / West positions ( left ) - top - aligned for proper expansion
' w ' : { { top : ' 50vh ' , left : ' 20px ' , transform : ' translateY(-20px) ' } } , / / Anchor at icon level
' wnw ' : { { top : ' 80px ' , left : ' 20px ' } } , / / Top - aligned instead of center
' nw ' : { { top : ' 20px ' , left : ' 20px ' } } ,
' nnw ' : { { top : ' 20px ' , left : ' 35 % ' , transform : ' translateX(-50 % ) ' } }
} } ,
/ / Get expansion direction based on compass position
getExpansionDirection : function ( ) { {
const pos = this . config . position ;
const rightBorderPositions = [ ' ne ' , ' ene ' , ' e ' , ' ese ' , ' se ' ] ;
const bottomBorderPositions = [ ' sw ' , ' ssw ' , ' s ' , ' sse ' , ' se ' ] ;
return { {
header : rightBorderPositions . includes ( pos ) ? ' left ' : ' right ' ,
body : bottomBorderPositions . includes ( pos ) ? ' up ' : ' down '
} } ;
} } ,
/ / Calculate position styles based on compass direction
getPositionStyles : function ( ) { {
const compassPos = this . compassPositions [ this . config . position ] | | this . compassPositions [ ' w ' ] ;
return { {
position : ' fixed ' ,
top : compassPos . top | | ' auto ' ,
right : compassPos . right | | ' auto ' ,
bottom : compassPos . bottom | | ' auto ' ,
left : compassPos . left | | ' auto ' ,
transform : compassPos . transform | | ' none ' ,
zIndex : 1000
} } ;
} } ,
/ / Abstract methods ( to be implemented by subclasses )
buildContent : function ( ) { {
const content = this . element . querySelector ( ' .control-content ' ) ;
content . innerHTML = ` < p style = " padding: 1rem; color: #666; " > $ { { this . config . defaultContent } } < / p > ` ;
} } ,
/ / Concrete methods ( shared by all controls )
createControl : function ( ) { {
console . log ( ` 🎛 ️ Creating $ { { this . config . title } } control . . . ` ) ;
this . element = document . createElement ( ' div ' ) ;
this . element . className = this . config . className ;
this . element . innerHTML = `
< button class = " control-toggle " aria - label = " $ {{ this.config.ariaLabel}} " > $ { { this . config . icon } } < / button >
< div class = " control-panel " style = " display: none; " >
< div class = " control-header " >
< span class = " control-icon " > $ { { this . config . icon } } < / span >
2025-11-11 00:29:34 +01:00
< span class = " control-title " > $ { { this . config . title } } < / span >
2025-11-10 23:10:33 +01:00
< button class = " control-close " > ✕ < / button >
2025-11-10 19:39:46 +01:00
< / div >
2025-11-10 23:10:33 +01:00
< div class = " control-content " > Loading . . . < / div >
< / div >
` ;
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
/ / Position using compass direction
const positionStyles = this . getPositionStyles ( ) ;
this . element . style . cssText = `
position : $ { { positionStyles . position } } ;
top : $ { { positionStyles . top } } ;
right : $ { { positionStyles . right } } ;
bottom : $ { { positionStyles . bottom } } ;
left : $ { { positionStyles . left } } ;
transform : $ { { positionStyles . transform } } ;
z - index : $ { { positionStyles . zIndex } } ;
background : rgba ( 255 , 255 , 255 , 0.95 ) ;
border : 1 px solid #e1e5e9;
border - radius : 8 px ;
box - shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.15 ) ;
backdrop - filter : blur ( 8 px ) ;
width : 40 px ;
transition : all 0.3 s ease ;
font - family : - apple - system , BlinkMacSystemFont , ' Segoe UI ' , sans - serif ;
` ;
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
/ / Store original position for reset
this . originalPosition = { {
top : positionStyles . top ,
right : positionStyles . right ,
bottom : positionStyles . bottom ,
left : positionStyles . left ,
transform : positionStyles . transform
} } ;
/ / Style toggle button
const toggleBtn = this . element . querySelector ( ' .control-toggle ' ) ;
toggleBtn . style . cssText = `
width : 100 % ;
height : 40 px ;
border : none ;
background : transparent ;
cursor : pointer ;
font - size : 16 px ;
color : #666;
transition : color 0.2 s ease ;
` ;
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
/ / Handle click to build content on - demand
toggleBtn . addEventListener ( ' click ' , ( ) = > { {
if ( this . isExpanded ) { {
2025-11-10 19:39:46 +01:00
this . collapse ( ) ;
2025-11-10 23:10:33 +01:00
} } else { {
console . log ( ` 🎛 ️ $ { { this . config . title } } toggle clicked - building content . . . ` ) ;
this . buildContent ( ) ;
} }
} } ) ;
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
/ / Close button handler
const closeBtn = this . element . querySelector ( ' .control-close ' ) ;
closeBtn . addEventListener ( ' click ' , ( ) = > { {
this . collapse ( ) ;
} } ) ;
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
/ / Responsive behavior
window . addEventListener ( ' resize ' , ( ) = > { {
2025-11-10 19:39:46 +01:00
if ( window . innerWidth < = 768 ) { {
2025-11-10 23:10:33 +01:00
this . element . style . display = ' none ' ;
} } else { {
this . element . style . display = ' ' ;
2025-11-10 19:39:46 +01:00
} }
2025-11-10 23:10:33 +01:00
} } ) ;
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
document . body . appendChild ( this . element ) ;
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
/ / Hide on mobile
if ( window . innerWidth < = 768 ) { {
this . element . style . display = ' none ' ;
} }
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
console . log ( ` 🎛 ️ $ { { this . config . title } } control created ` ) ;
} } ,
styleHeader : function ( ) { {
const header = this . element . querySelector ( ' .control-header ' ) ;
/ / Style the header to show icon , title , and close button in one line
/ / Match the height of the collapsed icon state ( 40 px )
header . style . cssText = `
display : flex ;
align - items : center ;
justify - content : space - between ;
height : 40 px ;
padding : 0 1 rem ;
border - bottom : 1 px solid #eee;
margin - bottom : 0 ;
` ;
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
const icon = header . querySelector ( ' .control-icon ' ) ;
if ( icon ) { {
icon . style . cssText = `
font - size : 16 px ;
color : #666;
margin - right : 0.5 rem ;
cursor : grab ;
user - select : none ;
` ;
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
/ / Make icon draggable
this . setupDragHandlers ( icon ) ;
} }
2025-11-10 20:10:11 +01:00
2025-11-11 00:29:34 +01:00
const title = header . querySelector ( ' .control-title ' ) ;
2025-11-10 23:10:33 +01:00
if ( title ) { {
title . style . cssText = `
margin : 0 ;
font - size : 0.9 rem ;
font - weight : 600 ;
flex - grow : 1 ;
line - height : 1 ;
2025-11-11 00:29:34 +01:00
cursor : pointer ;
user - select : none ;
2025-11-10 23:10:33 +01:00
` ;
2025-11-11 00:29:34 +01:00
/ / Add click handler to toggle header - only mode
title . addEventListener ( ' click ' , ( ) = > { {
this . toggleHeaderOnly ( ) ;
} } ) ;
2025-11-10 23:10:33 +01:00
} }
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
const closeBtn = header . querySelector ( ' .control-close ' ) ;
if ( closeBtn ) { {
closeBtn . style . cssText = `
background : none ;
border : none ;
font - size : 14 px ;
cursor : pointer ;
2025-11-11 00:29:34 +01:00
color : #6c757d;
2025-11-10 23:10:33 +01:00
padding : 0 ;
width : 20 px ;
height : 20 px ;
2025-11-11 00:29:34 +01:00
display : none ;
2025-11-10 23:10:33 +01:00
align - items : center ;
justify - content : center ;
2025-11-11 00:29:34 +01:00
transition : color 0.2 s ease ;
2025-11-10 23:10:33 +01:00
` ;
} }
} } ,
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
styleContent : function ( ) { {
const content = this . element . querySelector ( ' .control-content ' ) ;
const expansion = this . getExpansionDirection ( ) ;
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
/ / Style the content area based on expansion direction
let contentStyles = `
padding : 0.5 rem ;
overflow - y : auto ;
` ;
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
if ( expansion . body == = ' up ' ) { {
/ / Body expands upward ( for bottom border positions )
contentStyles + = `
2025-11-10 20:10:11 +01:00
max - height : calc ( 80 vh - 40 px ) ;
` ;
2025-11-10 23:10:33 +01:00
content . parentElement . style . flexDirection = ' column-reverse ' ;
} } else { {
/ / Body expands downward ( default )
contentStyles + = `
max - height : calc ( 80 vh - 40 px ) ;
` ;
content . parentElement . style . flexDirection = ' column ' ;
} }
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
content . style . cssText = contentStyles ;
} } ,
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
expand : function ( ) { {
this . isExpanded = true ;
const panel = this . element . querySelector ( ' .control-panel ' ) ;
const toggleBtn = this . element . querySelector ( ' .control-toggle ' ) ;
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
/ / Get expansion direction based on compass position
const expansion = this . getExpansionDirection ( ) ;
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
/ / Apply expansion styling based on direction
if ( expansion . header == = ' left ' ) { {
/ / Header expands to the left ( for right border positions )
this . element . style . width = ' 280px ' ;
this . element . style . transformOrigin = ' top right ' ;
} } else { {
/ / Header expands to the right ( default )
this . element . style . width = ' 280px ' ;
this . element . style . transformOrigin = ' top left ' ;
} }
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
panel . style . display = ' block ' ;
toggleBtn . style . display = ' none ' ;
this . styleHeader ( ) ;
this . styleContent ( ) ;
2025-11-11 00:29:34 +01:00
this . addResizeHandle ( ) ;
2025-11-10 23:10:33 +01:00
} } ,
collapse : function ( ) { {
this . isExpanded = false ;
2025-11-11 00:29:34 +01:00
this . isHeaderOnly = false ; / / Reset header - only state
2025-11-10 23:10:33 +01:00
const panel = this . element . querySelector ( ' .control-panel ' ) ;
const toggleBtn = this . element . querySelector ( ' .control-toggle ' ) ;
panel . style . display = ' none ' ;
2025-11-11 00:29:34 +01:00
/ / Reset size to default
2025-11-10 23:10:33 +01:00
this . element . style . width = ' 40px ' ;
2025-11-11 00:29:34 +01:00
this . element . style . height = ' auto ' ;
/ / Remove resize handle
this . removeResizeHandle ( ) ;
2025-11-10 23:10:33 +01:00
toggleBtn . style . display = ' block ' ;
/ / Reset position to original compass location
this . element . style . top = this . originalPosition . top ;
this . element . style . right = this . originalPosition . right ;
this . element . style . bottom = this . originalPosition . bottom ;
this . element . style . left = this . originalPosition . left ;
this . element . style . transform = this . originalPosition . transform ;
} } ,
2025-11-11 00:29:34 +01:00
toggleHeaderOnly : function ( ) { {
if ( ! this . isExpanded ) { {
/ / If collapsed , first expand normally
this . buildContent ( ) ;
return ;
} }
const content = this . element . querySelector ( ' .control-content ' ) ;
if ( this . isHeaderOnly ) { {
/ / Show content area ( go to full expanded mode )
this . isHeaderOnly = false ;
content . style . display = ' block ' ;
console . log ( ` 🎛 ️ $ { { this . config . title } } expanded to full view ` ) ;
} } else { {
/ / Hide content area ( go to header - only mode )
this . isHeaderOnly = true ;
content . style . display = ' none ' ;
console . log ( ` 🎛 ️ $ { { this . config . title } } collapsed to header only ` ) ;
} }
} } ,
2025-11-10 23:10:33 +01:00
setupDragHandlers : function ( dragElement ) { {
dragElement . addEventListener ( ' mousedown ' , ( e ) = > { {
this . isDragging = true ;
const rect = this . element . getBoundingClientRect ( ) ;
const iconRect = dragElement . getBoundingClientRect ( ) ;
/ / Calculate offset relative to the icon position , not the element
this . dragOffset . x = e . clientX - rect . left ;
this . dragOffset . y = iconRect . top - rect . top + ( iconRect . height / 2 ) ; / / Keep mouse at icon center
dragElement . style . cursor = ' grabbing ' ;
e . preventDefault ( ) ;
} } ) ;
document . addEventListener ( ' mousemove ' , ( e ) = > { {
if ( ! this . isDragging | | ! this . isExpanded ) return ;
const newX = e . clientX - this . dragOffset . x ;
const newY = e . clientY - this . dragOffset . y ;
/ / Keep within viewport bounds
const maxX = window . innerWidth - this . element . offsetWidth ;
const maxY = window . innerHeight - this . element . offsetHeight ;
const boundedX = Math . max ( 0 , Math . min ( newX , maxX ) ) ;
const boundedY = Math . max ( 0 , Math . min ( newY , maxY ) ) ;
this . element . style . left = boundedX + ' px ' ;
this . element . style . top = boundedY + ' px ' ;
} } ) ;
document . addEventListener ( ' mouseup ' , ( ) = > { {
if ( this . isDragging ) { {
this . isDragging = false ;
dragElement . style . cursor = ' grab ' ;
} }
} } ) ;
2025-11-11 00:29:34 +01:00
} } ,
/ / Add resize handle to expanded control
addResizeHandle : function ( ) { {
/ / Remove existing resize handle if any
this . removeResizeHandle ( ) ;
const resizeHandle = document . createElement ( ' div ' ) ;
resizeHandle . className = ' control-resize-handle ' ;
/ / Create small circle for resize handle
resizeHandle . innerHTML = ' ' ;
resizeHandle . style . cssText = `
position : absolute ;
bottom : 2 px ;
right : 2 px ;
width : 8 px ;
height : 8 px ;
cursor : nw - resize ;
display : none ;
user - select : none ;
z - index : 1001 ;
background : #6c757d;
border - radius : 50 % ;
opacity : 0.6 ;
transition : opacity 0.2 s ease ;
` ;
this . element . appendChild ( resizeHandle ) ;
this . setupResizeHandlers ( resizeHandle ) ;
this . setupHoverBehavior ( ) ;
} } ,
/ / Setup hover behavior for resize handle and close button
setupHoverBehavior : function ( ) { {
const resizeHandle = this . element . querySelector ( ' .control-resize-handle ' ) ;
const closeBtn = this . element . querySelector ( ' .control-close ' ) ;
if ( resizeHandle & & closeBtn ) { {
/ / Show / hide on control hover
this . element . addEventListener ( ' mouseenter ' , ( ) = > { {
resizeHandle . style . display = ' flex ' ;
closeBtn . style . display = ' block ' ;
} } ) ;
this . element . addEventListener ( ' mouseleave ' , ( ) = > { {
resizeHandle . style . display = ' none ' ;
closeBtn . style . display = ' none ' ;
} } ) ;
} }
} } ,
/ / Remove resize handle
removeResizeHandle : function ( ) { {
const existingHandle = this . element . querySelector ( ' .control-resize-handle ' ) ;
if ( existingHandle ) { {
existingHandle . remove ( ) ;
} }
} } ,
/ / Set up resize event handlers
setupResizeHandlers : function ( resizeHandle ) { {
resizeHandle . addEventListener ( ' mousedown ' , ( e ) = > { {
this . isResizing = true ;
const rect = this . element . getBoundingClientRect ( ) ;
this . resizeStartSize = { {
width : rect . width ,
height : rect . height ,
startX : e . clientX ,
startY : e . clientY
} } ;
resizeHandle . style . cursor = ' nw-resize ' ;
resizeHandle . style . color = ' #28a745 ' ;
e . preventDefault ( ) ;
e . stopPropagation ( ) ; / / Prevent triggering drag
} } ) ;
document . addEventListener ( ' mousemove ' , ( e ) = > { {
if ( ! this . isResizing | | ! this . isExpanded ) return ;
const deltaX = e . clientX - this . resizeStartSize . startX ;
const deltaY = e . clientY - this . resizeStartSize . startY ;
const newWidth = Math . max ( this . defaultSize . minWidth , this . resizeStartSize . width + deltaX ) ;
const newHeight = Math . max ( this . defaultSize . minHeight , this . resizeStartSize . height + deltaY ) ;
/ / Check viewport bounds
const maxWidth = window . innerWidth - this . element . offsetLeft ;
const maxHeight = window . innerHeight - this . element . offsetTop ;
const boundedWidth = Math . min ( newWidth , maxWidth - 20 ) ;
const boundedHeight = Math . min ( newHeight , maxHeight - 20 ) ;
this . element . style . width = boundedWidth + ' px ' ;
this . element . style . height = boundedHeight + ' px ' ;
/ / Ensure content areas resize properly
this . updateContentSize ( ) ;
} } ) ;
document . addEventListener ( ' mouseup ' , ( ) = > { {
if ( this . isResizing ) { {
this . isResizing = false ;
resizeHandle . style . cursor = ' nw-resize ' ;
resizeHandle . style . color = ' #6c757d ' ;
} }
} } ) ;
} } ,
/ / Update content area sizes during resize
updateContentSize : function ( ) { {
const content = this . element . querySelector ( ' .control-content ' ) ;
if ( content ) { {
/ / Adjust content height to fit the resized control
const headerHeight = 40 ; / / Header is 40 px
const padding = 16 ; / / Account for padding
const controlHeight = this . element . offsetHeight ;
const availableHeight = controlHeight - headerHeight - padding ;
content . style . maxHeight = Math . max ( 100 , availableHeight ) + ' px ' ;
} }
2025-11-10 23:10:33 +01:00
} }
} } ;
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
/ / Step 5 : Initialize ContentsControl ( new implementation based on Control class )
try { {
const contentsControl = Object . create ( Control ) ;
/ / Configure for contents navigation
contentsControl . config = { {
icon : ' ☰ ' ,
title : ' Contents ' ,
className : ' contents-control ' ,
defaultContent : ' No headings found ' ,
ariaLabel : ' Document Navigation ' ,
position : ' wnw ' / / West - north - west positioning
} } ;
2025-11-10 20:10:11 +01:00
2025-11-10 23:10:33 +01:00
/ / Override buildContent method for navigation functionality
contentsControl . buildContent = function ( ) { {
const content = this . element . querySelector ( ' .control-content ' ) ;
/ / Build navigation content from current DOM
const allHeadings = document . querySelectorAll ( ' h1, h2, h3 ' ) ;
/ / Filter out headings that contain " Contents " or similar navigation - related text
const headings = Array . from ( allHeadings ) . filter ( heading = > { {
const text = heading . textContent . trim ( ) . toLowerCase ( ) ;
return ! text . includes ( ' contents ' ) & & ! text . includes ( ' table of contents ' ) & & ! text . includes ( ' navigation ' ) ;
} } ) ;
console . log ( " 📋 Found headings for navigation: " , headings . length ) ;
if ( headings . length == = 0 ) { {
content . innerHTML = ' <p style= " padding: 1rem; color: #666; " >No headings found</p> ' ;
} } else { {
let navHtml = ' ' ;
headings . forEach ( ( heading , index ) = > { {
if ( ! heading . id ) { {
heading . id = ` heading - $ { { index + 1 } } ` ;
2025-11-10 20:10:11 +01:00
} }
2025-11-10 23:10:33 +01:00
const level = parseInt ( heading . tagName . substring ( 1 ) ) ;
const indent = ( level - 1 ) * 1 ;
navHtml + = `
< a href = " #$ {{ heading.id}} "
style = " display: block; padding: 0.5rem; margin-left: $ {{ indent}}rem;
text - decoration : none ; color : #333; font-size: 0.9rem;
border - radius : 4 px ; cursor : pointer ; "
onmouseover = " this.style.backgroundColor= ' #f5f5f5 ' "
onmouseout = " this.style.backgroundColor= ' ' "
onclick = " event.preventDefault(); document.getElementById( ' $ {{ heading.id}} ' ).scrollIntoView( {{ behavior: ' smooth ' }}); if (window.innerWidth <= 768) setTimeout(() => contentsControl.collapse(), 500); " >
$ { { heading . textContent . trim ( ) } }
< / a >
` ;
2025-11-10 20:10:11 +01:00
} } ) ;
2025-11-10 23:10:33 +01:00
content . innerHTML = navHtml ;
2025-11-10 19:39:46 +01:00
} }
2025-11-10 23:10:33 +01:00
/ / Show panel
this . expand ( ) ;
2025-11-10 19:39:46 +01:00
} } ;
2025-11-10 23:10:33 +01:00
/ / Initialize the ContentsControl
contentsControl . createControl ( ) ;
2025-11-10 19:39:46 +01:00
/ / Make globally available for mobile collapse
2025-11-10 23:10:33 +01:00
window . contentsControl = contentsControl ;
2025-11-10 19:39:46 +01:00
} } catch ( error ) { {
2025-11-10 23:10:33 +01:00
console . error ( " ContentsControl failed to initialize: " , error ) ;
2025-11-10 19:39:46 +01:00
} }
2025-11-10 23:10:33 +01:00
2025-10-26 07:51:43 +01:00
} } ) ;
/ / Handle CDN loading errors
window . addEventListener ( ' load ' , function ( ) { {
if ( window . markitectMarkedError ) { {
console . error ( " CDN library failed to load - network or firewall blocking marked.js " ) ;
} }
} } ) ;
< / script >
< / body >
< / html > """
return html_template
def _get_clean_editor_scripts ( self ) - > str :
2025-11-04 09:11:50 +01:00
""" Load the modular editor JavaScript components from external files. """
from pathlib import Path
# Define the modular components to load in order
components = [
' js/core/section-manager.js ' ,
' js/components/debug-panel.js ' ,
' js/components/document-controls.js ' ,
' js/components/dom-renderer.js '
]
base_path = Path ( __file__ ) . parent / ' static '
combined_script = [ ]
# Load each component
for component_path in components :
script_path = base_path / component_path
if script_path . exists ( ) :
try :
content = script_path . read_text ( encoding = ' utf-8 ' )
combined_script . append ( f " // === { component_path } === " )
combined_script . append ( content )
combined_script . append ( " " ) # Add blank line between components
except Exception as e :
print ( f " Warning: Could not read { component_path } : { e } " )
combined_script . append ( f " console.error( ' Failed to load { component_path } ' ); " )
else :
print ( f " Warning: { component_path } not found at { script_path } " )
combined_script . append ( f " console.error( ' { component_path } file not found ' ); " )
# Add initialization script to wire up the components
initialization_script = """
/ / == = Component Initialization == =
document . addEventListener ( ' DOMContentLoaded ' , function ( ) {
/ / Create container for the markdown content
const container = document . getElementById ( ' markdown-content ' ) | | document . body ;
/ / Initialize components
const sectionManager = new SectionManager ( ) ;
const domRenderer = new DOMRenderer ( sectionManager , container ) ;
const debugPanel = new DebugPanel ( ) ;
const documentControls = new DocumentControls ( ) ;
/ / Create document controls
documentControls . create ( ) ;
2025-11-10 23:10:33 +01:00
/ / Define abstract Control class for UI controls ( same as viewing mode )
const Control = {
/ / Abstract control properties
element : null ,
2025-11-10 19:39:46 +01:00
isExpanded : false ,
2025-11-11 00:29:34 +01:00
isHeaderOnly : false , / / New state for header - only mode
2025-11-10 20:10:11 +01:00
isDragging : false ,
2025-11-11 00:29:34 +01:00
isResizing : false , / / New state for resizing mode
2025-11-10 20:10:11 +01:00
dragOffset : { x : 0 , y : 0 } ,
2025-11-11 00:29:34 +01:00
resizeStartSize : { width : 280 , height : ' auto ' } ,
2025-11-10 20:10:11 +01:00
originalPosition : { top : ' 80px ' , left : ' 20px ' } ,
2025-11-11 00:29:34 +01:00
defaultSize : { width : 280 , minWidth : 200 , minHeight : 150 } ,
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
/ / Configuration properties ( to be overridden by subclasses )
config : {
icon : ' ? ' ,
title : ' Control ' ,
className : ' control ' ,
defaultContent : ' Template only ' ,
ariaLabel : ' Control ' ,
position : ' w ' / / Default compass position : west ( middle - left )
} ,
/ / Compass positioning system ( top - aligned for proper expansion )
compassPositions : {
/ / North positions ( top )
' n ' : { top : ' 20px ' , left : ' 50 % ' , transform : ' translateX(-50 % ) ' } ,
' nne ' : { top : ' 20px ' , left : ' 65 % ' , transform : ' translateX(-50 % ) ' } ,
' ne ' : { top : ' 20px ' , right : ' 20px ' } ,
' ene ' : { top : ' 80px ' , right : ' 20px ' } , / / Top - aligned instead of center
/ / East positions ( right )
' e ' : { top : ' 50vh ' , right : ' 20px ' , transform : ' translateY(-20px) ' } , / / Anchor at icon level
' ese ' : { top : ' calc(65vh - 20px) ' , right : ' 20px ' } , / / Top - aligned
' se ' : { bottom : ' 20px ' , right : ' 20px ' } ,
' sse ' : { bottom : ' 20px ' , right : ' 35 % ' , transform : ' translateX(50 % ) ' } ,
/ / South positions ( bottom )
' s ' : { bottom : ' 20px ' , left : ' 50 % ' , transform : ' translateX(-50 % ) ' } ,
' ssw ' : { bottom : ' 20px ' , left : ' 35 % ' , transform : ' translateX(-50 % ) ' } ,
' sw ' : { bottom : ' 20px ' , left : ' 20px ' } ,
' wsw ' : { bottom : ' 80px ' , left : ' 20px ' } , / / Top - aligned instead of center
/ / West positions ( left ) - top - aligned for proper expansion
' w ' : { top : ' 50vh ' , left : ' 20px ' , transform : ' translateY(-20px) ' } , / / Anchor at icon level
' wnw ' : { top : ' 80px ' , left : ' 20px ' } , / / Top - aligned instead of center
' nw ' : { top : ' 20px ' , left : ' 20px ' } ,
' nnw ' : { top : ' 20px ' , left : ' 35 % ' , transform : ' translateX(-50 % ) ' }
} ,
/ / Get expansion direction based on compass position
getExpansionDirection : function ( ) {
const pos = this . config . position ;
const rightBorderPositions = [ ' ne ' , ' ene ' , ' e ' , ' ese ' , ' se ' ] ;
const bottomBorderPositions = [ ' sw ' , ' ssw ' , ' s ' , ' sse ' , ' se ' ] ;
return {
header : rightBorderPositions . includes ( pos ) ? ' left ' : ' right ' ,
body : bottomBorderPositions . includes ( pos ) ? ' up ' : ' down '
} ;
} ,
/ / Calculate position styles based on compass direction
getPositionStyles : function ( ) {
const compassPos = this . compassPositions [ this . config . position ] | | this . compassPositions [ ' w ' ] ;
return {
position : ' fixed ' ,
top : compassPos . top | | ' auto ' ,
right : compassPos . right | | ' auto ' ,
bottom : compassPos . bottom | | ' auto ' ,
left : compassPos . left | | ' auto ' ,
transform : compassPos . transform | | ' none ' ,
zIndex : 1001
} ;
} ,
/ / Abstract methods ( to be implemented by subclasses )
buildContent : function ( ) {
const content = this . element . querySelector ( ' .control-content ' ) ;
content . innerHTML = ` < p style = " padding: 1rem; color: #666; " > $ { this . config . defaultContent } < / p > ` ;
} ,
/ / Concrete methods ( shared by all controls )
2025-11-10 19:39:46 +01:00
createControl : function ( ) {
2025-11-10 23:10:33 +01:00
console . log ( ` 🎛 ️ Creating $ { this . config . title } control . . . ` ) ;
this . element = document . createElement ( ' div ' ) ;
this . element . className = this . config . className ;
this . element . innerHTML = `
< button class = " control-toggle " aria - label = " $ {this.config.ariaLabel} " > $ { this . config . icon } < / button >
< div class = " control-panel " style = " display: none; " >
< div class = " control-header " >
< span class = " control-icon " > $ { this . config . icon } < / span >
2025-11-11 00:29:34 +01:00
< span class = " control-title " > $ { this . config . title } < / span >
2025-11-10 23:10:33 +01:00
< button class = " control-close " > ✕ < / button >
2025-11-10 19:39:46 +01:00
< / div >
2025-11-10 23:10:33 +01:00
< div class = " control-content " > Loading . . . < / div >
2025-11-10 19:39:46 +01:00
< / div >
` ;
2025-11-10 23:10:33 +01:00
/ / Position using compass direction
const positionStyles = this . getPositionStyles ( ) ;
this . element . style . cssText = `
position : $ { positionStyles . position } ;
top : $ { positionStyles . top } ;
right : $ { positionStyles . right } ;
bottom : $ { positionStyles . bottom } ;
left : $ { positionStyles . left } ;
transform : $ { positionStyles . transform } ;
z - index : $ { positionStyles . zIndex } ;
2025-11-10 19:39:46 +01:00
background : rgba ( 255 , 255 , 255 , 0.95 ) ;
border : 1 px solid #e1e5e9;
border - radius : 8 px ;
box - shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.15 ) ;
backdrop - filter : blur ( 8 px ) ;
width : 40 px ;
2025-11-10 23:10:33 +01:00
transition : all 0.3 s ease ;
2025-11-10 19:39:46 +01:00
font - family : - apple - system , BlinkMacSystemFont , ' Segoe UI ' , sans - serif ;
` ;
2025-11-10 23:10:33 +01:00
/ / Store original position for reset
this . originalPosition = {
top : positionStyles . top ,
right : positionStyles . right ,
bottom : positionStyles . bottom ,
left : positionStyles . left ,
transform : positionStyles . transform
} ;
2025-11-10 19:39:46 +01:00
/ / Style toggle button
2025-11-10 23:10:33 +01:00
const toggleBtn = this . element . querySelector ( ' .control-toggle ' ) ;
2025-11-10 19:39:46 +01:00
toggleBtn . style . cssText = `
width : 100 % ;
height : 40 px ;
border : none ;
background : transparent ;
cursor : pointer ;
font - size : 16 px ;
color : #666;
transition : color 0.2 s ease ;
` ;
2025-11-10 23:10:33 +01:00
/ / Handle click to build content on - demand
2025-11-10 19:39:46 +01:00
toggleBtn . addEventListener ( ' click ' , ( ) = > {
2025-11-10 20:10:11 +01:00
if ( this . isExpanded ) {
this . collapse ( ) ;
} else {
2025-11-10 23:10:33 +01:00
console . log ( ` 🎛 ️ $ { this . config . title } toggle clicked - building content . . . ` ) ;
this . buildContent ( ) ;
2025-11-10 20:10:11 +01:00
}
2025-11-10 19:39:46 +01:00
} ) ;
/ / Close button handler
2025-11-10 23:10:33 +01:00
const closeBtn = this . element . querySelector ( ' .control-close ' ) ;
2025-11-10 19:39:46 +01:00
closeBtn . addEventListener ( ' click ' , ( ) = > {
this . collapse ( ) ;
} ) ;
2025-11-10 23:10:33 +01:00
document . body . appendChild ( this . element ) ;
console . log ( ` 🎛 ️ $ { this . config . title } control created ` ) ;
2025-11-10 19:39:46 +01:00
} ,
2025-11-10 23:10:33 +01:00
styleHeader : function ( ) {
const header = this . element . querySelector ( ' .control-header ' ) ;
2025-11-10 20:10:11 +01:00
/ / Style the header to show icon , title , and close button in one line
/ / Match the height of the collapsed icon state ( 40 px )
header . style . cssText = `
display : flex ;
align - items : center ;
justify - content : space - between ;
height : 40 px ;
padding : 0 1 rem ;
border - bottom : 1 px solid #eee;
margin - bottom : 0 ;
` ;
2025-11-10 23:10:33 +01:00
const icon = header . querySelector ( ' .control-icon ' ) ;
2025-11-10 20:10:11 +01:00
if ( icon ) {
icon . style . cssText = `
font - size : 16 px ;
color : #666;
margin - right : 0.5 rem ;
cursor : grab ;
user - select : none ;
` ;
/ / Make icon draggable
this . setupDragHandlers ( icon ) ;
}
2025-11-11 00:29:34 +01:00
const title = header . querySelector ( ' .control-title ' ) ;
2025-11-10 20:10:11 +01:00
if ( title ) {
title . style . cssText = `
margin : 0 ;
font - size : 0.9 rem ;
font - weight : 600 ;
flex - grow : 1 ;
line - height : 1 ;
2025-11-11 00:29:34 +01:00
cursor : pointer ;
user - select : none ;
2025-11-10 20:10:11 +01:00
` ;
2025-11-11 00:29:34 +01:00
/ / Add click handler to toggle header - only mode
title . addEventListener ( ' click ' , ( ) = > {
this . toggleHeaderOnly ( ) ;
} ) ;
2025-11-10 20:10:11 +01:00
}
2025-11-10 23:10:33 +01:00
const closeBtn = header . querySelector ( ' .control-close ' ) ;
2025-11-10 20:10:11 +01:00
if ( closeBtn ) {
closeBtn . style . cssText = `
background : none ;
border : none ;
font - size : 14 px ;
cursor : pointer ;
2025-11-11 00:29:34 +01:00
color : #6c757d;
2025-11-10 20:10:11 +01:00
padding : 0 ;
width : 20 px ;
height : 20 px ;
2025-11-11 00:29:34 +01:00
display : none ;
2025-11-10 20:10:11 +01:00
align - items : center ;
justify - content : center ;
2025-11-11 00:29:34 +01:00
transition : color 0.2 s ease ;
2025-11-10 20:10:11 +01:00
` ;
}
2025-11-10 23:10:33 +01:00
} ,
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
styleContent : function ( ) {
const content = this . element . querySelector ( ' .control-content ' ) ;
const expansion = this . getExpansionDirection ( ) ;
2025-11-10 19:39:46 +01:00
2025-11-10 23:10:33 +01:00
/ / Style the content area based on expansion direction
let contentStyles = `
2025-11-10 20:10:11 +01:00
padding : 0.5 rem ;
overflow - y : auto ;
` ;
2025-11-10 23:10:33 +01:00
if ( expansion . body == = ' up ' ) {
/ / Body expands upward ( for bottom border positions )
contentStyles + = `
max - height : calc ( 80 vh - 40 px ) ;
` ;
content . parentElement . style . flexDirection = ' column-reverse ' ;
} else {
/ / Body expands downward ( default )
contentStyles + = `
max - height : calc ( 80 vh - 40 px ) ;
` ;
content . parentElement . style . flexDirection = ' column ' ;
}
content . style . cssText = contentStyles ;
2025-11-10 19:39:46 +01:00
} ,
expand : function ( ) {
this . isExpanded = true ;
2025-11-10 23:10:33 +01:00
const panel = this . element . querySelector ( ' .control-panel ' ) ;
const toggleBtn = this . element . querySelector ( ' .control-toggle ' ) ;
/ / Get expansion direction based on compass position
const expansion = this . getExpansionDirection ( ) ;
/ / Apply expansion styling based on direction
if ( expansion . header == = ' left ' ) {
/ / Header expands to the left ( for right border positions )
this . element . style . width = ' 300px ' ;
this . element . style . transformOrigin = ' top right ' ;
} else {
/ / Header expands to the right ( default )
this . element . style . width = ' 300px ' ;
this . element . style . transformOrigin = ' top left ' ;
}
2025-11-10 19:39:46 +01:00
panel . style . display = ' block ' ;
2025-11-10 20:10:11 +01:00
toggleBtn . style . display = ' none ' ;
2025-11-10 23:10:33 +01:00
this . styleHeader ( ) ;
this . styleContent ( ) ;
2025-11-11 00:29:34 +01:00
this . addResizeHandle ( ) ;
2025-11-10 19:39:46 +01:00
} ,
collapse : function ( ) {
this . isExpanded = false ;
2025-11-11 00:29:34 +01:00
this . isHeaderOnly = false ; / / Reset header - only state
2025-11-10 23:10:33 +01:00
const panel = this . element . querySelector ( ' .control-panel ' ) ;
const toggleBtn = this . element . querySelector ( ' .control-toggle ' ) ;
2025-11-10 19:39:46 +01:00
panel . style . display = ' none ' ;
2025-11-11 00:29:34 +01:00
/ / Reset size to default
2025-11-10 23:10:33 +01:00
this . element . style . width = ' 40px ' ;
2025-11-11 00:29:34 +01:00
this . element . style . height = ' auto ' ;
/ / Remove resize handle
this . removeResizeHandle ( ) ;
2025-11-10 20:10:11 +01:00
toggleBtn . style . display = ' block ' ;
2025-11-10 23:10:33 +01:00
/ / Reset position to original compass location
this . element . style . top = this . originalPosition . top ;
this . element . style . right = this . originalPosition . right ;
this . element . style . bottom = this . originalPosition . bottom ;
this . element . style . left = this . originalPosition . left ;
this . element . style . transform = this . originalPosition . transform ;
2025-11-10 20:10:11 +01:00
} ,
2025-11-11 00:29:34 +01:00
toggleHeaderOnly : function ( ) {
if ( ! this . isExpanded ) {
/ / If collapsed , first expand normally
this . buildContent ( ) ;
return ;
}
const content = this . element . querySelector ( ' .control-content ' ) ;
if ( this . isHeaderOnly ) {
/ / Show content area ( go to full expanded mode )
this . isHeaderOnly = false ;
content . style . display = ' block ' ;
console . log ( ` 🎛 ️ $ { this . config . title } expanded to full view ` ) ;
} else {
/ / Hide content area ( go to header - only mode )
this . isHeaderOnly = true ;
content . style . display = ' none ' ;
console . log ( ` 🎛 ️ $ { this . config . title } collapsed to header only ` ) ;
}
} ,
2025-11-10 20:10:11 +01:00
setupDragHandlers : function ( dragElement ) {
dragElement . addEventListener ( ' mousedown ' , ( e ) = > {
this . isDragging = true ;
2025-11-10 23:10:33 +01:00
const rect = this . element . getBoundingClientRect ( ) ;
const iconRect = dragElement . getBoundingClientRect ( ) ;
/ / Calculate offset relative to the icon position , not the element
2025-11-10 20:10:11 +01:00
this . dragOffset . x = e . clientX - rect . left ;
2025-11-10 23:10:33 +01:00
this . dragOffset . y = iconRect . top - rect . top + ( iconRect . height / 2 ) ; / / Keep mouse at icon center
2025-11-10 20:10:11 +01:00
dragElement . style . cursor = ' grabbing ' ;
e . preventDefault ( ) ;
} ) ;
document . addEventListener ( ' mousemove ' , ( e ) = > {
if ( ! this . isDragging | | ! this . isExpanded ) return ;
const newX = e . clientX - this . dragOffset . x ;
const newY = e . clientY - this . dragOffset . y ;
/ / Keep within viewport bounds
2025-11-10 23:10:33 +01:00
const maxX = window . innerWidth - this . element . offsetWidth ;
const maxY = window . innerHeight - this . element . offsetHeight ;
2025-11-10 20:10:11 +01:00
const boundedX = Math . max ( 0 , Math . min ( newX , maxX ) ) ;
const boundedY = Math . max ( 0 , Math . min ( newY , maxY ) ) ;
2025-11-10 23:10:33 +01:00
this . element . style . left = boundedX + ' px ' ;
this . element . style . top = boundedY + ' px ' ;
2025-11-10 20:10:11 +01:00
} ) ;
document . addEventListener ( ' mouseup ' , ( ) = > {
if ( this . isDragging ) {
this . isDragging = false ;
dragElement . style . cursor = ' grab ' ;
}
} ) ;
2025-11-11 00:29:34 +01:00
} ,
/ / Add resize handle to expanded control ( same as viewing mode )
addResizeHandle : function ( ) {
/ / Remove existing resize handle if any
this . removeResizeHandle ( ) ;
const resizeHandle = document . createElement ( ' div ' ) ;
resizeHandle . className = ' control-resize-handle ' ;
/ / Create small circle for resize handle
resizeHandle . innerHTML = ' ' ;
resizeHandle . style . cssText = `
position : absolute ;
bottom : 2 px ;
right : 2 px ;
width : 8 px ;
height : 8 px ;
cursor : nw - resize ;
display : none ;
user - select : none ;
z - index : 1001 ;
background : #6c757d;
border - radius : 50 % ;
opacity : 0.6 ;
transition : opacity 0.2 s ease ;
` ;
this . element . appendChild ( resizeHandle ) ;
this . setupResizeHandlers ( resizeHandle ) ;
this . setupHoverBehavior ( ) ;
} ,
/ / Remove resize handle
removeResizeHandle : function ( ) {
const existingHandle = this . element . querySelector ( ' .control-resize-handle ' ) ;
if ( existingHandle ) {
existingHandle . remove ( ) ;
}
} ,
/ / Set up resize event handlers
setupResizeHandlers : function ( resizeHandle ) {
resizeHandle . addEventListener ( ' mousedown ' , ( e ) = > {
this . isResizing = true ;
const rect = this . element . getBoundingClientRect ( ) ;
this . resizeStartSize = {
width : rect . width ,
height : rect . height ,
startX : e . clientX ,
startY : e . clientY
} ;
resizeHandle . style . cursor = ' nw-resize ' ;
resizeHandle . style . background = ' rgba(40, 167, 69, 0.9) ' ;
e . preventDefault ( ) ;
e . stopPropagation ( ) ; / / Prevent triggering drag
} ) ;
document . addEventListener ( ' mousemove ' , ( e ) = > {
if ( ! this . isResizing | | ! this . isExpanded ) return ;
const deltaX = e . clientX - this . resizeStartSize . startX ;
const deltaY = e . clientY - this . resizeStartSize . startY ;
const newWidth = Math . max ( this . defaultSize . minWidth , this . resizeStartSize . width + deltaX ) ;
const newHeight = Math . max ( this . defaultSize . minHeight , this . resizeStartSize . height + deltaY ) ;
/ / Check viewport bounds
const maxWidth = window . innerWidth - this . element . offsetLeft ;
const maxHeight = window . innerHeight - this . element . offsetTop ;
const boundedWidth = Math . min ( newWidth , maxWidth - 20 ) ;
const boundedHeight = Math . min ( newHeight , maxHeight - 20 ) ;
this . element . style . width = boundedWidth + ' px ' ;
this . element . style . height = boundedHeight + ' px ' ;
/ / Ensure content areas resize properly
this . updateContentSize ( ) ;
} ) ;
document . addEventListener ( ' mouseup ' , ( ) = > {
if ( this . isResizing ) {
this . isResizing = false ;
resizeHandle . style . cursor = ' nw-resize ' ;
resizeHandle . style . color = ' #6c757d ' ;
}
} ) ;
} ,
/ / Update content area sizes during resize
updateContentSize : function ( ) {
const content = this . element . querySelector ( ' .control-content ' ) ;
if ( content ) {
/ / Adjust content height to fit the resized control
const headerHeight = 40 ; / / Header is 40 px
const padding = 16 ; / / Account for padding
const controlHeight = this . element . offsetHeight ;
const availableHeight = controlHeight - headerHeight - padding ;
content . style . maxHeight = Math . max ( 100 , availableHeight ) + ' px ' ;
}
} ,
/ / Setup hover behavior for resize handle and close button
setupHoverBehavior : function ( ) {
const resizeHandle = this . element . querySelector ( ' .control-resize-handle ' ) ;
const closeBtn = this . element . querySelector ( ' .control-close ' ) ;
if ( resizeHandle & & closeBtn ) {
/ / Show / hide on control hover
this . element . addEventListener ( ' mouseenter ' , ( ) = > {
resizeHandle . style . display = ' flex ' ;
closeBtn . style . display = ' block ' ;
} ) ;
this . element . addEventListener ( ' mouseleave ' , ( ) = > {
resizeHandle . style . display = ' none ' ;
closeBtn . style . display = ' none ' ;
} ) ;
}
2025-11-10 19:39:46 +01:00
}
} ;
2025-11-10 23:10:33 +01:00
/ / Create ContentsControl for edit mode ( new implementation based on Control class )
try {
const contentsControl = Object . create ( Control ) ;
/ / Configure for contents navigation in edit mode
contentsControl . config = {
icon : ' ☰ ' ,
title : ' Contents ' ,
className : ' contents-control edit-mode ' ,
defaultContent : ' No headings found ' ,
ariaLabel : ' Document Navigation ' ,
position : ' wnw ' / / West - north - west positioning
} ;
/ / Override buildContent method for navigation functionality
contentsControl . buildContent = function ( ) {
const content = this . element . querySelector ( ' .control-content ' ) ;
/ / Build navigation content from current DOM
const allHeadings = document . querySelectorAll ( ' h1, h2, h3 ' ) ;
/ / Filter out headings that contain " Contents " or similar navigation - related text
const headings = Array . from ( allHeadings ) . filter ( heading = > {
const text = heading . textContent . trim ( ) . toLowerCase ( ) ;
return ! text . includes ( ' contents ' ) & & ! text . includes ( ' table of contents ' ) & & ! text . includes ( ' navigation ' ) ;
} ) ;
console . log ( " 📋 Found headings for navigation: " , headings . length ) ;
if ( headings . length == = 0 ) {
content . innerHTML = ' <p style= " padding: 1rem; color: #666; " >No headings found</p> ' ;
} else {
let navHtml = ' ' ;
headings . forEach ( ( heading , index ) = > {
if ( ! heading . id ) {
heading . id = ` heading - $ { index + 1 } ` ;
}
const level = parseInt ( heading . tagName . substring ( 1 ) ) ;
const indent = ( level - 1 ) * 1 ;
navHtml + = `
< a href = " #$ {heading.id} "
style = " display: block; padding: 0.5rem; margin-left: $ {indent} rem;
text - decoration : none ; color : #333; font-size: 0.9rem;
border - radius : 4 px ; cursor : pointer ; "
onmouseover = " this.style.backgroundColor= ' #f5f5f5 ' "
onmouseout = " this.style.backgroundColor= ' ' "
onclick = " event.preventDefault(); document.getElementById( ' $ {heading.id} ' ).scrollIntoView( { behavior: ' smooth ' }); if (window.innerWidth <= 768) setTimeout(() => contentsControl.collapse(), 500); " >
$ { heading . textContent . trim ( ) }
< / a >
` ;
} ) ;
content . innerHTML = navHtml ;
}
/ / Show panel
this . expand ( ) ;
} ;
/ / Initialize the ContentsControl
contentsControl . createControl ( ) ;
/ / Make globally available for mobile collapse
window . contentsControl = contentsControl ;
} catch ( error ) {
console . error ( " ContentsControl failed to initialize: " , error ) ;
}
2025-11-11 00:29:34 +01:00
/ / Step 7 : Initialize Independent Debug System
try {
/ / Create independent debug system using IndexedDB for persistence
window . MarkitectDebugSystem = {
db : null ,
messages : [ ] ,
maxMessages : 1000 ,
isEnabled : true ,
subscribers : [ ] ,
/ / Selection and filtering system
selectionCriteria : {
includeDocumentEvents : true ,
includeSystemEvents : false ,
includeControlEvents : true ,
includeEditingEvents : true ,
includeNavigationEvents : false ,
includedHeadings : new Set ( ) , / / Track which document headings to monitor
excludedSources : new Set ( [ ' ContentsControl ' , ' DocumentNavigator ' ] )
} ,
/ / Initialize IndexedDB for persistence
async init ( ) {
return new Promise ( ( resolve , reject ) = > {
const request = indexedDB . open ( ' MarkitectDebugDB ' , 1 ) ;
request . onerror = ( ) = > reject ( request . error ) ;
request . onsuccess = ( ) = > {
this . db = request . result ;
this . loadMessages ( ) . then ( resolve ) ;
} ;
request . onupgradeneeded = ( e ) = > {
const db = e . target . result ;
if ( ! db . objectStoreNames . contains ( ' messages ' ) ) {
const store = db . createObjectStore ( ' messages ' , { keyPath : ' id ' , autoIncrement : true } ) ;
store . createIndex ( ' timestamp ' , ' timestamp ' , { unique : false } ) ;
store . createIndex ( ' category ' , ' category ' , { unique : false } ) ;
}
} ;
} ) ;
} ,
/ / Add a debug message with selection filtering
async addMessage ( message , category = ' INFO ' , source = ' System ' , context = { } ) {
/ / Check if this message should be included based on selection criteria
if ( ! this . shouldIncludeMessage ( message , category , source , context ) ) {
return null ;
}
const messageObj = {
message : String ( message ) ,
category : category . toUpperCase ( ) ,
source : source ,
context : context ,
timestamp : new Date ( ) . toISOString ( ) ,
displayTime : new Date ( ) . toLocaleTimeString ( )
} ;
/ / Add to memory
this . messages . push ( messageObj ) ;
/ / Keep only last maxMessages in memory
if ( this . messages . length > this . maxMessages ) {
this . messages = this . messages . slice ( - this . maxMessages ) ;
}
/ / Persist to IndexedDB
if ( this . db ) {
try {
const transaction = this . db . transaction ( [ ' messages ' ] , ' readwrite ' ) ;
const store = transaction . objectStore ( ' messages ' ) ;
await store . add ( messageObj ) ;
} catch ( e ) {
console . warn ( ' Failed to persist debug message: ' , e ) ;
}
}
/ / Notify subscribers
this . subscribers . forEach ( callback = > {
try { callback ( messageObj ) ; } catch ( e ) { console . error ( ' Debug subscriber error: ' , e ) ; }
} ) ;
return messageObj ;
} ,
/ / Selection filtering logic
shouldIncludeMessage ( message , category , source , context ) {
if ( ! this . isEnabled ) return false ;
/ / Check excluded sources
if ( this . selectionCriteria . excludedSources . has ( source ) ) {
return false ;
}
/ / Category - based filtering
const categoryChecks = {
' DOCUMENT ' : ( ) = > this . selectionCriteria . includeDocumentEvents ,
' SYSTEM ' : ( ) = > this . selectionCriteria . includeSystemEvents ,
' CONTROL ' : ( ) = > this . selectionCriteria . includeControlEvents ,
' EDITING ' : ( ) = > this . selectionCriteria . includeEditingEvents ,
' NAVIGATION ' : ( ) = > this . selectionCriteria . includeNavigationEvents
} ;
/ / Check if we have a specific category filter
if ( context . eventType & & categoryChecks [ context . eventType ] ) {
return categoryChecks [ context . eventType ] ( ) ;
}
/ / Document heading specific filtering
if ( context . headingId ) {
return this . selectionCriteria . includedHeadings . has ( context . headingId ) | |
this . selectionCriteria . includedHeadings . size == = 0 ; / / Include all if none specifically selected
}
/ / Default : include based on general settings
return category == = ' ERROR ' | | / / Always include errors
this . selectionCriteria . includeSystemEvents ;
} ,
/ / Document structure awareness
scanDocumentStructure ( ) {
const headings = document . querySelectorAll ( ' h1, h2, h3, h4, h5, h6 ' ) ;
const documentStructure = {
headings : [ ] ,
totalSections : 0
} ;
headings . forEach ( ( heading , index ) = > {
/ / Skip control headings ( like Contents , Debug , etc . )
const text = heading . textContent . trim ( ) . toLowerCase ( ) ;
if ( text . includes ( ' contents ' ) | | text . includes ( ' debug ' ) | |
text . includes ( ' control ' ) | | text . includes ( ' navigation ' ) ) {
return ;
}
if ( ! heading . id ) {
heading . id = ` document - heading - $ { index + 1 } ` ;
}
documentStructure . headings . push ( {
id : heading . id ,
text : heading . textContent . trim ( ) ,
level : parseInt ( heading . tagName . substring ( 1 ) ) ,
element : heading
} ) ;
} ) ;
documentStructure . totalSections = documentStructure . headings . length ;
this . documentStructure = documentStructure ;
/ / Auto - include all document headings for monitoring
this . selectionCriteria . includedHeadings . clear ( ) ;
documentStructure . headings . forEach ( heading = > {
this . selectionCriteria . includedHeadings . add ( heading . id ) ;
} ) ;
this . addMessage ( ` Document structure scanned : $ { documentStructure . totalSections } sections found ` ,
' INFO ' , ' DocumentScanner ' , { eventType : ' DOCUMENT ' , headings : documentStructure . headings } ) ;
return documentStructure ;
} ,
/ / Selection management methods
includeHeading ( headingId ) {
this . selectionCriteria . includedHeadings . add ( headingId ) ;
} ,
excludeHeading ( headingId ) {
this . selectionCriteria . includedHeadings . delete ( headingId ) ;
} ,
includeSource ( source ) {
this . selectionCriteria . excludedSources . delete ( source ) ;
} ,
excludeSource ( source ) {
this . selectionCriteria . excludedSources . add ( source ) ;
} ,
/ / Get selection criteria for UI
getSelectionCriteria ( ) {
return {
. . . this . selectionCriteria ,
includedHeadings : Array . from ( this . selectionCriteria . includedHeadings ) ,
excludedSources : Array . from ( this . selectionCriteria . excludedSources )
} ;
} ,
/ / Load messages from IndexedDB
async loadMessages ( ) {
if ( ! this . db ) return ;
try {
const transaction = this . db . transaction ( [ ' messages ' ] , ' readonly ' ) ;
const store = transaction . objectStore ( ' messages ' ) ;
const request = store . getAll ( ) ;
request . onsuccess = ( ) = > {
this . messages = request . result . slice ( - this . maxMessages ) ;
console . log ( ` 📊 Loaded $ { this . messages . length } debug messages from IndexedDB ` ) ;
} ;
} catch ( e ) {
console . warn ( ' Failed to load debug messages: ' , e ) ;
}
} ,
/ / Clear all messages
async clearMessages ( ) {
this . messages = [ ] ;
if ( this . db ) {
try {
const transaction = this . db . transaction ( [ ' messages ' ] , ' readwrite ' ) ;
const store = transaction . objectStore ( ' messages ' ) ;
await store . clear ( ) ;
} catch ( e ) {
console . warn ( ' Failed to clear debug messages from DB: ' , e ) ;
}
}
this . subscribers . forEach ( callback = > {
try { callback ( { type : ' clear ' } ) ; } catch ( e ) { console . error ( ' Debug subscriber error: ' , e ) ; }
} ) ;
} ,
/ / Subscribe to debug updates
subscribe ( callback ) {
this . subscribers . push ( callback ) ;
return ( ) = > {
const index = this . subscribers . indexOf ( callback ) ;
if ( index > - 1 ) this . subscribers . splice ( index , 1 ) ;
} ;
} ,
/ / Get messages ( with filtering )
getMessages ( category = null , limit = null ) {
let filtered = this . messages ;
if ( category ) {
filtered = filtered . filter ( msg = > msg . category == = category . toUpperCase ( ) ) ;
}
if ( limit ) {
filtered = filtered . slice ( - limit ) ;
}
return filtered ;
} ,
/ / Get recent messages
getRecentMessages ( count = 50 ) {
return this . messages . slice ( - count ) ;
} ,
/ / Export messages as JSON
exportMessages ( ) {
return JSON . stringify ( this . messages , null , 2 ) ;
}
} ;
/ / Initialize the debug system
window . MarkitectDebugSystem . init ( ) . then ( ( ) = > {
console . log ( ' 📊 Markitect Debug System initialized ' ) ;
/ / Add initial message
window . MarkitectDebugSystem . addMessage ( ' Markitect Debug System initialized ' , ' INFO ' , ' DebugSystem ' , { eventType : ' SYSTEM ' } ) ;
} ) . catch ( error = > {
console . warn ( ' 📊 Debug System initialization failed, using memory only: ' , error ) ;
window . MarkitectDebugSystem . addMessage ( ' Debug System initialized (memory only) ' , ' WARNING ' , ' DebugSystem ' , { eventType : ' SYSTEM ' } ) ;
} ) ;
} catch ( error ) {
console . error ( " Debug System initialization failed: " , error ) ;
}
/ / Step 8 : Initialize DebugControl ( new implementation based on Control class )
try {
const debugControl = Object . create ( Control ) ;
/ / Configure for debug functionality
debugControl . config = {
icon : ' 🪲 ' ,
title : ' Debug ' ,
className : ' debug-control ' ,
defaultContent : ' Debug panel controls ' ,
ariaLabel : ' Debug Control ' ,
position : ' ese ' / / East - south - east positioning
} ;
/ / Override buildContent method for debug functionality
debugControl . buildContent = function ( ) {
console . log ( " 🪲 Building debug control content... " ) ;
try {
const content = this . element . querySelector ( ' .control-content ' ) ;
if ( ! content ) {
console . error ( " 🪲 Debug control content element not found " ) ;
return ;
}
/ / Build debug control panel with selection and filtering
content . innerHTML = `
< div style = " padding: 0.5rem; " >
< ! - - Debug Control Header - - >
< div style = " display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;
padding - bottom : 0.5 rem ; border - bottom : 1 px solid #eee;">
< h4 style = " margin: 0; font-size: 0.9rem; font-weight: 600; " > Debug Messages < / h4 >
< div >
< button onclick = " window.MarkitectDebugSystem?.scanDocumentStructure() "
style = " padding: 0.25rem 0.5rem; background: #6c757d; color: white;
border : none ; border - radius : 3 px ; cursor : pointer ; font - size : 0.7 rem ; margin - right : 0.25 rem ; " >
📊 Scan
< / button >
< button onclick = " window.MarkitectDebugSystem?.clearMessages() "
style = " padding: 0.25rem 0.5rem; background: #dc3545; color: white;
border : none ; border - radius : 3 px ; cursor : pointer ; font - size : 0.7 rem ; " >
Clear
< / button >
< / div >
< / div >
< ! - - Selection Controls - - >
< div style = " margin-bottom: 0.5rem; padding: 0.5rem; background: #f8f9fa; border-radius: 4px; font-size: 0.8rem; " >
< div style = " font-weight: bold; margin-bottom: 0.25rem; " > Event Types : < / div >
< div style = " display: grid; grid-template-columns: 1fr 1fr; gap: 0.25rem; " >
< label style = " display: flex; align-items: center; cursor: pointer; " >
< input type = " checkbox " id = " docEvents " checked onchange = " window.MarkitectDebugSystem.selectionCriteria.includeDocumentEvents = this.checked " style = " margin-right: 0.25rem; " >
< span style = " font-size: 0.75rem; " > Document < / span >
< / label >
< label style = " display: flex; align-items: center; cursor: pointer; " >
< input type = " checkbox " id = " editEvents " checked onchange = " window.MarkitectDebugSystem.selectionCriteria.includeEditingEvents = this.checked " style = " margin-right: 0.25rem; " >
< span style = " font-size: 0.75rem; " > Editing < / span >
< / label >
< label style = " display: flex; align-items: center; cursor: pointer; " >
< input type = " checkbox " id = " ctrlEvents " checked onchange = " window.MarkitectDebugSystem.selectionCriteria.includeControlEvents = this.checked " style = " margin-right: 0.25rem; " >
< span style = " font-size: 0.75rem; " > Controls < / span >
< / label >
< label style = " display: flex; align-items: center; cursor: pointer; " >
< input type = " checkbox " id = " sysEvents " onchange = " window.MarkitectDebugSystem.selectionCriteria.includeSystemEvents = this.checked " style = " margin-right: 0.25rem; " >
< span style = " font-size: 0.75rem; " > System < / span >
< / label >
< / div >
< / div >
< ! - - Debug Switch - - >
< div style = " display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;
padding : 0.5 rem ; background : #e9ecef; border-radius: 4px;">
< span style = " font-size: 0.9rem; font-weight: bold; " > Debug System : < / span >
< label style = " display: flex; align-items: center; cursor: pointer; " >
< input type = " checkbox " id = " debugToggle " checked
onchange = " window.MarkitectDebugSystem.isEnabled = this.checked; document.getElementById( ' debugStatus ' ).textContent = this.checked ? ' ON ' : ' OFF ' ; document.getElementById( ' debugStatus ' ).style.color = this.checked ? ' #28a745 ' : ' #dc3545 ' ; "
style = " margin-right: 0.5rem; " >
< span id = " debugStatus " style = " color: #28a745; font-weight: bold; " > ON < / span >
< / label >
< / div >
< ! - - Debug Messages Container - - >
< div id = " debugMessagesContainer " style = " max-height: 180px; overflow-y: auto;
border : 1 px solid #ddd; border-radius: 4px;
background : #f8f9fa; font-family: monospace; font-size: 0.8rem;">
< div style = " padding: 0.5rem; color: #666; text-align: center; " >
Click 📊 Scan to analyze document structure
< / div >
< / div >
< ! - - Export and Tools - - >
< div style = " margin-top: 0.5rem; display: flex; gap: 0.25rem; " >
< button onclick = " navigator.clipboard.writeText(window.MarkitectDebugSystem.exportMessages()).then(() => window.MarkitectDebugSystem.addMessage( ' Debug messages exported to clipboard ' , ' SUCCESS ' , ' DebugControl ' )) "
style = " flex: 1; padding: 0.4rem; background: #17a2b8; color: white;
border : none ; border - radius : 3 px ; cursor : pointer ; font - size : 0.8 rem ; " >
📋 Export
< / button >
< button onclick = " console.table(window.MarkitectDebugSystem.getSelectionCriteria()) "
style = " flex: 1; padding: 0.4rem; background: #6f42c1; color: white;
border : none ; border - radius : 3 px ; cursor : pointer ; font - size : 0.8 rem ; " >
🔍 Criteria
< / button >
< / div >
< / div >
` ;
console . log ( " 🪲 Debug control HTML content built successfully " ) ;
/ / Note : Debug switch state is set directly in HTML ( checked attribute )
/ / No need to call updateDebugStatus ( ) since checkbox manages its own state
/ / Set up periodic update of debug messages
try {
this . setupMessageUpdates ( ) ;
} catch ( e ) {
console . log ( " 🪲 Message updates setup failed: " , e ) ;
}
/ / Show panel and call expand
console . log ( " 🪲 Calling expand... " ) ;
this . expand ( ) ;
console . log ( " 🪲 Expand called successfully " ) ;
} catch ( error ) {
console . error ( " 🪲 Error in buildContent: " , error ) ;
/ / Fallback simple content
const content = this . element . querySelector ( ' .control-content ' ) ;
if ( content ) {
content . innerHTML = ` < div style = " padding: 1rem; color: #666; " > Debug control error : $ { error . message } < / div > ` ;
this . expand ( ) ;
}
}
} ;
/ / Add method to update debug switch status ( updated for new debug system )
debugControl . updateDebugStatus = function ( ) {
const checkbox = this . element . querySelector ( ' #debugToggle ' ) ;
const statusText = this . element . querySelector ( ' #debugStatus ' ) ;
if ( checkbox & & statusText ) {
try {
if ( window . MarkitectDebugSystem ) {
/ / Use the new debug system ' s enabled state
const isEnabled = window . MarkitectDebugSystem . isEnabled ;
checkbox . checked = isEnabled ;
statusText . textContent = isEnabled ? ' ON ' : ' OFF ' ;
statusText . style . color = isEnabled ? ' #28a745 ' : ' #dc3545 ' ;
} else {
/ / Fallback when debug system is not available
checkbox . checked = false ;
statusText . textContent = ' UNAVAILABLE ' ;
statusText . style . color = ' #dc3545 ' ;
}
} catch ( e ) {
console . log ( " 🪲 Error updating debug status: " , e ) ;
checkbox . checked = false ;
statusText . textContent = ' ERROR ' ;
statusText . style . color = ' #dc3545 ' ;
}
}
} ;
/ / Add method to setup message updates
debugControl . setupMessageUpdates = function ( ) {
try {
/ / Update messages every 500 ms when debug control is open
this . messageUpdateInterval = setInterval ( ( ) = > {
if ( this . isExpanded & & ! this . isHeaderOnly ) {
this . updateMessages ( ) ;
/ / Note : updateDebugStatus ( ) removed - checkbox manages its own state
}
} , 500 ) ;
console . log ( " 🪲 Message update interval set up successfully " ) ;
} catch ( e ) {
console . log ( " 🪲 Failed to set up message updates: " , e ) ;
}
} ;
/ / Add method to update debug messages display using new system
debugControl . updateMessages = function ( ) {
const container = this . element . querySelector ( ' #debugMessagesContainer ' ) ;
if ( ! container ) return ;
try {
if ( window . MarkitectDebugSystem ) {
const messages = window . MarkitectDebugSystem . getRecentMessages ( 50 ) ;
if ( messages & & messages . length > 0 ) {
/ / Show messages in reverse order ( newest first )
const reversedMessages = [ . . . messages ] . reverse ( ) ;
container . innerHTML = `
< div style = " padding: 0.25rem 0.5rem; background: #e9ecef; font-size: 0.7rem; color: #6c757d; " >
$ { messages . length } messages ( newest first ) |
< span onclick = " window.MarkitectDebugSystem.addMessage( ' Test message from debug control ' , ' INFO ' , ' DebugControl ' , { eventType: ' CONTROL ' }) "
style = " cursor: pointer; text-decoration: underline; " > Add Test < / span >
< / div >
$ { reversedMessages . map ( msg = > `
< div style = " padding: 0.25rem 0.5rem; border-bottom: 1px solid #e9ecef;
color : $ { this . getMessageColor ( msg . category ) } ; " >
< div style = " display: flex; justify-content: space-between; align-items: flex-start; " >
< div style = " flex: 1; " >
< small style = " color: #6c757d; " > [ $ { msg . displayTime } ] < / small >
< span style = " font-weight: bold; margin: 0 0.25rem 0 0.5rem; " > $ { msg . category } : < / span >
$ { msg . message }
< / div >
$ { msg . source ? ` < small style = " color: #868e96; font-size: 0.7rem; margin-left: 0.5rem; " > $ { msg . source } < / small > ` : ' ' }
< / div >
$ { msg . context & & msg . context . headingId ? `
< div style = " font-size: 0.7rem; color: #6c757d; padding-left: 1rem; " >
📍 Section : $ { msg . context . headingId }
< / div >
` : ' ' }
< / div >
` ) . join ( ' ' ) }
` ;
} else {
container . innerHTML = `
< div style = " padding: 0.5rem; color: #666; text-align: center; " >
No debug messages yet < br >
< button onclick = " window.MarkitectDebugSystem.addMessage( ' First debug message ' , ' INFO ' ) "
style = " margin-top: 0.5rem; padding: 0.25rem 0.5rem; background: #6c757d; color: white; border: none; border-radius: 3px; cursor: pointer; " >
Add Test Message
< / button >
< / div >
` ;
}
} else {
container . innerHTML = `
< div style = " padding: 0.5rem; color: #dc3545; text-align: center; " >
Debug system not initialized
< / div >
` ;
}
} catch ( e ) {
console . log ( " 🪲 Error updating messages: " , e ) ;
container . innerHTML = `
< div style = " padding: 0.5rem; color: #dc3545; text-align: center; " >
Error loading messages : $ { e . message }
< / div >
` ;
}
} ;
/ / Add method to get message colors by type
debugControl . getMessageColor = function ( type ) {
const colors = {
' ERROR ' : ' #dc3545 ' ,
' WARNING ' : ' #fd7e14 ' ,
' SUCCESS ' : ' #28a745 ' ,
' INFO ' : ' #17a2b8 ' ,
' DEBUG ' : ' #6f42c1 '
} ;
return colors [ type ] | | ' #495057 ' ;
} ;
/ / Override collapse to clean up intervals
const originalCollapse = debugControl . collapse ;
debugControl . collapse = function ( ) {
if ( this . messageUpdateInterval ) {
clearInterval ( this . messageUpdateInterval ) ;
this . messageUpdateInterval = null ;
}
originalCollapse . call ( this ) ;
} ;
/ / Create and show the debug control
console . log ( " 🪲 Creating debug control... " ) ;
debugControl . createControl ( ) ;
console . log ( " 🪲 Debug control created, element: " , debugControl . element ) ;
/ / Make debug control globally accessible
window . debugControl = debugControl ;
console . log ( " 🪲 Debug control setup complete and globally accessible " ) ;
} catch ( error ) {
console . error ( " DebugControl failed to initialize: " , error ) ;
}
2025-11-10 19:39:46 +01:00
2025-11-04 09:11:50 +01:00
/ / Wire up event handlers
documentControls . setEventHandlers ( {
' save-document ' : ( ) = > {
console . log ( ' Save document clicked ' ) ;
2025-11-04 21:59:22 +01:00
try {
/ / Get current markdown content from section manager
const currentMarkdown = sectionManager . getDocumentMarkdown ( ) ;
/ / Create filename with timestamp suffix following the established convention
const now = new Date ( ) ;
const timestamp = now . toISOString ( ) . slice ( 0 , 19 ) . replace ( / : / g , ' - ' ) . replace ( ' T ' , ' - ' ) ;
/ / Extract original filename from config or use default
const originalFilename = window . editorConfig ? . originalFilename | | ' document ' ;
const editedFilename = ` $ { originalFilename } - edited - $ { timestamp } . md ` ;
/ / Create and download the file
const blob = new Blob ( [ currentMarkdown ] , { type : ' text/markdown ' } ) ;
const url = URL . createObjectURL ( blob ) ;
const a = document . createElement ( ' a ' ) ;
a . href = url ;
a . download = editedFilename ;
document . body . appendChild ( a ) ;
a . click ( ) ;
document . body . removeChild ( a ) ;
URL . revokeObjectURL ( url ) ;
2025-11-11 00:29:34 +01:00
/ / Log success to debug system
window . MarkitectDebugSystem ? . addMessage ( ` Document saved as : $ { editedFilename } ` , ' SUCCESS ' , ' DocumentSaver ' , { eventType : ' DOCUMENT ' } ) ;
2025-11-04 21:59:22 +01:00
console . log ( ` Document successfully saved as : $ { editedFilename } ` ) ;
} catch ( error ) {
2025-11-11 00:29:34 +01:00
window . MarkitectDebugSystem ? . addMessage ( ` Save failed : $ { error . message } ` , ' ERROR ' , ' DocumentSaver ' , { eventType : ' DOCUMENT ' } ) ;
2025-11-04 21:59:22 +01:00
console . error ( ' Save error: ' , error ) ;
}
2025-11-04 09:11:50 +01:00
} ,
' reset-all ' : ( ) = > {
console . log ( ' Reset all clicked ' ) ;
2025-11-04 09:40:01 +01:00
/ / Hide any open editors
domRenderer . hideCurrentEditor ( ) ;
/ / Reset all sections to original state
const allSections = Array . from ( sectionManager . sections . values ( ) ) ;
allSections . forEach ( section = > {
section . resetToOriginal ( ) ;
} ) ;
/ / Re - render all sections
domRenderer . renderAllSections ( allSections ) ;
2025-11-11 00:29:34 +01:00
window . MarkitectDebugSystem ? . addMessage ( ` Reset all sections to original state ` , ' INFO ' , ' SectionManager ' , { eventType : ' EDITING ' } ) ;
2025-10-28 22:36:15 +01:00
}
2025-11-04 09:11:50 +01:00
} ) ;
2025-10-28 22:36:15 +01:00
2025-11-11 00:29:34 +01:00
/ / Set up debug system integration with section - aware logging
2025-11-04 09:11:50 +01:00
sectionManager . on ( ' sections-created ' , ( data ) = > {
2025-11-11 00:29:34 +01:00
window . MarkitectDebugSystem ? . addMessage ( ` Created $ { data . count } sections ` , ' INFO ' , ' SectionManager ' , { eventType : ' DOCUMENT ' } ) ;
2025-11-04 09:11:50 +01:00
} ) ;
2025-10-28 22:36:15 +01:00
2025-11-04 09:11:50 +01:00
sectionManager . on ( ' edit-started ' , ( data ) = > {
2025-11-11 00:29:34 +01:00
window . MarkitectDebugSystem ? . addMessage ( ` Edit started for section : $ { data . sectionId } ` , ' DEBUG ' , ' SectionManager ' , { eventType : ' EDITING ' , sectionId : data . sectionId } ) ;
2025-11-04 09:11:50 +01:00
} ) ;
2025-10-28 22:36:15 +01:00
2025-11-04 09:11:50 +01:00
sectionManager . on ( ' changes-accepted ' , ( data ) = > {
2025-11-11 00:29:34 +01:00
window . MarkitectDebugSystem ? . addMessage ( ` Changes accepted for section : $ { data . sectionId } ` , ' SUCCESS ' , ' SectionManager ' , { eventType : ' EDITING ' , sectionId : data . sectionId } ) ;
2025-11-04 09:40:01 +01:00
/ / Re - render the section to show updated content
const section = sectionManager . sections . get ( data . sectionId ) ;
if ( section ) {
const sectionElement = domRenderer . findSectionElement ( data . sectionId ) ;
if ( sectionElement ) {
const newElement = domRenderer . renderSection ( section ) ;
sectionElement . parentNode . replaceChild ( newElement , sectionElement ) ;
2025-11-11 00:29:34 +01:00
window . MarkitectDebugSystem ? . addMessage ( ` DOM updated for section : $ { data . sectionId } ` , ' INFO ' , ' DOMRenderer ' , { eventType : ' EDITING ' , sectionId : data . sectionId } ) ;
2025-11-04 09:40:01 +01:00
}
}
2025-10-28 22:36:15 +01:00
} ) ;
2025-11-04 09:11:50 +01:00
sectionManager . on ( ' changes-cancelled ' , ( data ) = > {
2025-11-11 00:29:34 +01:00
window . MarkitectDebugSystem ? . addMessage ( ` Changes cancelled for section : $ { data . sectionId } ` , ' WARNING ' , ' SectionManager ' , { eventType : ' EDITING ' , sectionId : data . sectionId } ) ;
2025-10-28 22:36:15 +01:00
} ) ;
2025-11-04 09:25:15 +01:00
/ / Initialize with markdown content
const markdownToRender = markdownContent | | ' ' ;
if ( markdownToRender . trim ( ) ) {
const sections = sectionManager . createSectionsFromMarkdown ( markdownToRender ) ;
2025-11-04 09:11:50 +01:00
domRenderer . renderAllSections ( sections ) ;
2025-11-11 00:29:34 +01:00
window . MarkitectDebugSystem ? . addMessage ( ` Initialized with $ { sections . length } sections ` , ' INFO ' , ' DocumentRenderer ' , { eventType : ' DOCUMENT ' } ) ;
2025-11-04 09:25:15 +01:00
} else {
2025-11-11 00:29:34 +01:00
window . MarkitectDebugSystem ? . addMessage ( ' No markdown content to initialize ' , ' WARNING ' , ' DocumentRenderer ' , { eventType : ' DOCUMENT ' } ) ;
2025-11-04 09:11:50 +01:00
}
2025-10-28 22:36:15 +01:00
2025-11-04 09:11:50 +01:00
/ / Make components globally available for debugging
window . markitectComponents = {
sectionManager ,
domRenderer ,
debugPanel ,
documentControls
} ;
2025-10-28 22:36:15 +01:00
2025-11-04 09:11:50 +01:00
console . log ( ' Markitect modular editor initialized successfully ' ) ;
} ) ;
"""
2025-10-28 22:36:15 +01:00
2025-11-04 09:11:50 +01:00
combined_script . append ( initialization_script )
return ' \n ' . join ( combined_script )