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-10-28 23:55:21 +01:00
edit_mode : bool = False , insert_mode : bool = False , editor_theme : str = ' github ' , keyboard_shortcuts : bool = True , nodogtag : bool = False ) - > 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
markdown_content = input_path . read_text ( encoding = ' utf-8 ' )
# 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 ,
nodogtag = nodogtag
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 "
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
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 _get_template_css ( self , template : str = None ) - > str :
""" Generate layered theme CSS styles. """
# Import layered theme functions
from markitect . plugins . builtin . markdown_commands import (
parse_theme_string , combine_theme_properties , TEMPLATE_STYLES
)
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 )
return self . _generate_layered_css ( combined_props )
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 )
return self . _generate_layered_css ( combined_props )
else :
# Legacy theme - convert to layered
theme_list = parse_theme_string ( template )
combined_props = combine_theme_properties ( theme_list )
return self . _generate_layered_css ( combined_props )
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
def _generate_layered_css ( self , properties : dict ) - > str :
""" 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
heading_css = " "
if props [ ' heading_style ' ] == ' underlined ' :
heading_css = f """
h1 , h2 , h3 , h4 , h5 , h6 { {
color : { props [ ' heading_color ' ] } ;
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 ' ] } ;
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 ' ] } ;
} } """
# 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
return f " <style> { base_css } { heading_css } { text_css } { element_css } { link_css } { accent_css } { ui_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
def _get_legacy_template_css ( self , template : str ) - > str :
""" Legacy CSS generation - kept for backward compatibility. """
# Import template styles
from markitect . plugins . builtin . markdown_commands import TEMPLATE_STYLES
# Use basic as default if no template specified
if not template or template not in TEMPLATE_STYLES :
template = ' basic '
style_config = TEMPLATE_STYLES [ template ]
# Base CSS that's common to all templates
base_css = f """
body { {
font - family : { style_config [ ' font_family ' ] } ;
max - width : { style_config [ ' max_width ' ] } ;
margin : 0 auto ;
padding : 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 ' ] ,
}
return self . _generate_layered_css ( layered_props )
def _generate_html_template ( self , markdown_content : str , title : str , css : str = None , template : str = None ,
2025-10-28 23:55:21 +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 ) - > 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
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
# Escape the markdown content for JavaScript
2025-10-28 03:50:21 +01:00
js_markdown_content = json . dumps ( markdown_content_with_dogtag )
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
default_css = self . _get_template_css ( template )
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 ( )
# 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 } ;
{ editor_config }
{ editor_scripts }
/ / Always render content first ( graceful degradation )
document . addEventListener ( ' DOMContentLoaded ' , function ( ) { {
console . log ( " Rendering content... " ) ;
const contentDiv = document . getElementById ( ' markdown-content ' ) ;
/ / Step 1 : Ensure content is always displayed
if ( contentDiv ) { {
if ( typeof marked != = ' undefined ' ) { {
try { {
2025-10-28 03:50:21 +01:00
const html = marked . parse ( markdownContent ) ;
/ / 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-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 :
""" Get the complete clean editor JavaScript code. """
return """
/ / Clean Editor Architecture
/ * *
* Test - Driven Section Editor Implementation
*
* A clean , object - oriented approach to handling section editing
* that can be tested independently of the DOM .
* /
/ / Enums for clear state management
const EditState = Object . freeze ( {
ORIGINAL : ' original ' ,
EDITING : ' editing ' ,
MODIFIED : ' modified ' ,
SAVED : ' saved '
} ) ;
const SectionType = Object . freeze ( {
HEADING : ' heading ' ,
PARAGRAPH : ' paragraph ' ,
LIST : ' list ' ,
CODE : ' code ' ,
BLOCKQUOTE : ' blockquote '
} ) ;
/ * *
* Section class - Core business logic for a single editable section
* /
class Section {
constructor ( id , originalMarkdown , sectionType = SectionType . PARAGRAPH ) {
this . id = id ;
this . originalMarkdown = originalMarkdown ;
this . currentMarkdown = originalMarkdown ;
this . editingMarkdown = null ;
this . pendingMarkdown = null ;
this . sectionType = sectionType ;
2025-10-28 23:55:21 +01:00
this . headingLevel = Section . detectHeadingLevel ( originalMarkdown ) ;
2025-10-26 07:51:43 +01:00
this . state = EditState . ORIGINAL ;
this . domElement = null ;
this . lastSaved = null ;
this . created = new Date ( ) ;
}
startEdit ( ) {
if ( this . state == = EditState . EDITING ) {
throw new Error ( ` Section $ { this . id } is already being edited ` ) ;
}
this . editingMarkdown = this . pendingMarkdown | | this . currentMarkdown ;
this . state = EditState . EDITING ;
return this . editingMarkdown ;
}
updateContent ( markdown ) {
if ( this . state != = EditState . EDITING ) {
throw new Error ( ` Section $ { this . id } is not in editing state ` ) ;
}
this . editingMarkdown = markdown ;
}
acceptChanges ( ) {
if ( this . state != = EditState . EDITING ) {
throw new Error ( ` Section $ { this . id } is not in editing state ` ) ;
}
this . currentMarkdown = this . editingMarkdown ;
this . editingMarkdown = null ;
this . pendingMarkdown = null ;
this . state = EditState . SAVED ;
this . lastSaved = new Date ( ) ;
return this . currentMarkdown ;
}
cancelChanges ( ) {
if ( this . state != = EditState . EDITING ) {
throw new Error ( ` Section $ { this . id } is not in editing state ` ) ;
}
this . editingMarkdown = null ;
if ( this . pendingMarkdown != = null ) {
this . state = EditState . MODIFIED ;
return this . pendingMarkdown ;
} else if ( this . lastSaved != = null ) {
this . state = EditState . SAVED ;
return this . currentMarkdown ;
} else {
this . state = this . hasChanges ( ) ? EditState . MODIFIED : EditState . ORIGINAL ;
return this . currentMarkdown ;
}
}
resetToOriginal ( ) {
this . currentMarkdown = this . originalMarkdown ;
this . editingMarkdown = null ;
this . pendingMarkdown = null ;
this . lastSaved = null ;
this . state = EditState . ORIGINAL ;
return this . originalMarkdown ;
}
stopEditing ( ) {
if ( this . state != = EditState . EDITING ) {
return this . state ;
}
/ / If we have editing changes that differ from current content , preserve them as pending
if ( this . editingMarkdown & & this . editingMarkdown != = this . currentMarkdown ) {
this . pendingMarkdown = this . editingMarkdown ;
this . state = EditState . MODIFIED ; / / Has pending changes
} else {
/ / No changes made during this edit session
this . pendingMarkdown = null ;
if ( this . lastSaved != = null ) {
this . state = EditState . SAVED ;
} else {
this . state = this . hasChanges ( ) ? EditState . MODIFIED : EditState . ORIGINAL ;
}
}
this . editingMarkdown = null ;
return this . state ;
}
hasChanges ( ) {
return this . currentMarkdown != = this . originalMarkdown ;
}
isEditing ( ) {
return this . state == = EditState . EDITING ;
}
getStatus ( ) {
return {
id : this . id ,
state : this . state ,
hasChanges : this . hasChanges ( ) ,
isEditing : this . isEditing ( ) ,
contentLength : this . currentMarkdown . length ,
lastSaved : this . lastSaved ,
sectionType : this . sectionType
} ;
}
static generateId ( content , position ) {
const str = content . substring ( 0 , 100 ) + position . toString ( ) ;
let hash = 0 ;
for ( let i = 0 ; i < str . length ; i + + ) {
const char = str . charCodeAt ( i ) ;
hash = ( ( hash << 5 ) - hash ) + char ;
hash = hash & hash ;
}
return ` section_ $ { Math . abs ( hash ) } _ $ { position } ` ;
}
static detectType ( markdown ) {
const trimmed = markdown . trim ( ) ;
if ( trimmed . startsWith ( ' # ' ) ) return SectionType . HEADING ;
if ( trimmed . startsWith ( ' ``` ' ) ) return SectionType . CODE ;
if ( trimmed . startsWith ( ' > ' ) ) return SectionType . BLOCKQUOTE ;
if ( trimmed . startsWith ( ' - ' ) | | trimmed . startsWith ( ' * ' ) | | / ^ \\d + \\. / . test ( trimmed ) ) {
return SectionType . LIST ;
}
return SectionType . PARAGRAPH ;
}
2025-10-28 23:55:21 +01:00
static detectHeadingLevel ( markdown ) {
const trimmed = markdown . trim ( ) ;
const match = trimmed . match ( / ^ ( #{1,6})\s/);
return match ? match [ 1 ] . length : null ;
}
isHeading ( ) {
return this . sectionType == = SectionType . HEADING ;
}
isProtectedHeading ( ) {
if ( ! this . isHeading ( ) ) return false ;
/ / Check if we ' re in insert mode and if this heading level is protected
const config = window . editorConfig | | { } ;
const restrictedLevels = config . restrictedHeadingLevels | | [ ] ;
return config . mode == = ' insert ' & & restrictedLevels . includes ( this . headingLevel ) ;
}
getHeadingText ( ) {
if ( ! this . isHeading ( ) ) return null ;
/ / Extract first line for heading text
const firstLine = this . originalMarkdown . trim ( ) . split ( ' \\ n ' ) [ 0 ] ;
const match = firstLine . match ( / ^ ( #{1,6})\s+(.+)$/);
return match ? match [ 2 ] : null ;
}
getHeadingContent ( ) {
if ( ! this . isHeading ( ) ) return this . currentMarkdown ;
const lines = this . currentMarkdown . split ( ' \\ n ' ) ;
/ / Return content after the heading line
return lines . slice ( 1 ) . join ( ' \\ n ' ) ;
}
2025-10-26 07:51:43 +01:00
}
/ * *
* SectionManager class - Manages the collection of sections
* /
class SectionManager {
constructor ( ) {
this . sections = new Map ( ) ;
/ / Note : Removed single editingSection tracking to allow multiple concurrent edits
this . listeners = new Map ( ) ;
}
on ( event , callback ) {
if ( ! this . listeners . has ( event ) ) {
this . listeners . set ( event , [ ] ) ;
}
this . listeners . get ( event ) . push ( callback ) ;
}
emit ( event , data ) {
if ( this . listeners . has ( event ) ) {
this . listeners . get ( event ) . forEach ( callback = > callback ( data ) ) ;
}
}
createSectionsFromMarkdown ( markdownContent ) {
const lines = markdownContent . split ( ' \\ n ' ) ;
const sections = [ ] ;
let currentSection = ' ' ;
let position = 0 ;
for ( let i = 0 ; i < lines . length ; i + + ) {
const line = lines [ i ] ;
2025-10-28 23:55:21 +01:00
const isHeading = / ^ #{1,6}\s/.test(line);
2025-10-26 07:51:43 +01:00
const isNewParagraph = line . trim ( ) & & i > 0 & & ! lines [ i - 1 ] . trim ( ) ;
const isNewSection = isHeading | | isNewParagraph ;
if ( isNewSection & & currentSection . trim ( ) ) {
const sectionId = Section . generateId ( currentSection , position ) ;
const sectionType = Section . detectType ( currentSection ) ;
const section = new Section ( sectionId , currentSection . trim ( ) , sectionType ) ;
sections . push ( section ) ;
this . sections . set ( sectionId , section ) ;
position + + ;
currentSection = line ;
} else {
if ( currentSection ) currentSection + = ' \\ n ' ;
currentSection + = line ;
}
}
if ( currentSection . trim ( ) ) {
const sectionId = Section . generateId ( currentSection , position ) ;
const sectionType = Section . detectType ( currentSection ) ;
const section = new Section ( sectionId , currentSection . trim ( ) , sectionType ) ;
sections . push ( section ) ;
this . sections . set ( sectionId , section ) ;
}
this . emit ( ' sections-created ' , { sections , count : sections . length } ) ;
return sections ;
}
startEditing ( sectionId ) {
const section = this . sections . get ( sectionId ) ;
if ( ! section ) {
throw new Error ( ` Section $ { sectionId } not found ` ) ;
}
/ / Check if section is already being edited
if ( section . isEditing ( ) ) {
console . log ( ' Section already in editing state: ' , sectionId ) ;
return section . editingMarkdown ;
}
const content = section . startEdit ( ) ;
/ / Note : No longer tracking single editingSection - allowing multiple
this . emit ( ' edit-started ' , { sectionId , content , section : section . getStatus ( ) } ) ;
return content ;
}
updateContent ( sectionId , markdown ) {
const section = this . sections . get ( sectionId ) ;
if ( ! section ) {
throw new Error ( ` Section $ { sectionId } not found ` ) ;
}
section . updateContent ( markdown ) ;
this . emit ( ' content-updated ' , { sectionId , markdown , section : section . getStatus ( ) } ) ;
}
acceptChanges ( sectionId ) {
const section = this . sections . get ( sectionId ) ;
if ( ! section ) {
throw new Error ( ` Section $ { sectionId } not found ` ) ;
}
2025-10-28 23:55:21 +01:00
/ / For protected headings in insert mode , validate that heading hasn ' t changed
if ( section . isProtectedHeading ( ) ) {
const originalHeadingLine = section . originalMarkdown . split ( ' \\ n ' ) [ 0 ] ;
const newHeadingLine = section . editingMarkdown . split ( ' \\ n ' ) [ 0 ] ;
if ( originalHeadingLine != = newHeadingLine ) {
throw new Error ( ` Cannot modify protected heading in insert mode . Heading level $ { section . headingLevel } is read - only . ` ) ;
}
}
2025-10-26 07:51:43 +01:00
/ / Check if the edited content contains new headings that would create splits
const newContent = section . editingMarkdown ;
const originalContent = section . originalMarkdown ;
const shouldSplit = this . checkForSectionSplits ( newContent , originalContent ) ;
if ( shouldSplit ) {
/ / Handle section splitting
this . handleSectionSplit ( sectionId , newContent ) ;
} else {
/ / Normal accept without splitting
const content = section . acceptChanges ( ) ;
/ / Note : No longer tracking single editingSection
this . emit ( ' changes-accepted ' , { sectionId , content , section : section . getStatus ( ) } ) ;
}
return section . currentMarkdown ;
}
checkForSectionSplits ( content , originalContent ) {
if ( ! content ) return false ;
/ / Split by lines and check for headings
const lines = content . split ( ' \\ n ' ) ;
const originalLines = originalContent ? originalContent . split ( ' \\ n ' ) : [ ] ;
let newHeadingCount = 0 ;
let originalHeadingCount = 0 ;
/ / Count headings in new content
for ( const line of lines ) {
2025-10-28 23:55:21 +01:00
if ( / ^ #{1,6}\s/.test(line.trim())) {
2025-10-26 07:51:43 +01:00
newHeadingCount + + ;
}
}
/ / Count headings in original content
for ( const line of originalLines ) {
2025-10-28 23:55:21 +01:00
if ( / ^ #{1,6}\s/.test(line.trim())) {
2025-10-26 07:51:43 +01:00
originalHeadingCount + + ;
}
}
/ / Split if :
/ / 1. We have multiple headings now , OR
/ / 2. We added headings where there were none before , OR
/ / 3. We have more headings than we started with
return newHeadingCount > 1 | |
( originalHeadingCount == = 0 & & newHeadingCount > 0 ) | |
newHeadingCount > originalHeadingCount ;
}
handleSectionSplit ( originalSectionId , content ) {
console . log ( ' Splitting section: ' , originalSectionId ) ;
const originalSection = this . sections . get ( originalSectionId ) ;
if ( ! originalSection ) return ;
/ / Accept the current changes first
originalSection . acceptChanges ( ) ;
/ / Split the content into new sections
const newSections = this . createSectionsFromContent ( content , originalSectionId ) ;
/ / Get all sections as an ordered array to maintain document order
const allSectionsArray = Array . from ( this . sections . values ( ) ) ;
const originalIndex = allSectionsArray . findIndex ( s = > s . id == = originalSectionId ) ;
/ / Clear the sections map and rebuild it with proper order
this . sections . clear ( ) ;
/ / Add sections before the original
for ( let i = 0 ; i < originalIndex ; i + + ) {
const section = allSectionsArray [ i ] ;
this . sections . set ( section . id , section ) ;
}
/ / Add the new split sections
newSections . forEach ( section = > {
this . sections . set ( section . id , section ) ;
} ) ;
/ / Add sections after the original
for ( let i = originalIndex + 1 ; i < allSectionsArray . length ; i + + ) {
const section = allSectionsArray [ i ] ;
this . sections . set ( section . id , section ) ;
}
/ / Note : No longer tracking single editingSection
/ / Emit event to trigger UI re - render
this . emit ( ' section-split ' , {
originalSectionId ,
newSections : newSections . map ( s = > s . getStatus ( ) ) ,
allSections : Array . from ( this . sections . values ( ) )
} ) ;
}
createSectionsFromContent ( content , baseSectionId ) {
const lines = content . split ( ' \\ n ' ) ;
const sections = [ ] ;
let currentSection = ' ' ;
let position = 0 ;
for ( let i = 0 ; i < lines . length ; i + + ) {
const line = lines [ i ] ;
2025-10-28 23:55:21 +01:00
const isHeading = / ^ #{1,6}\s/.test(line.trim());
2025-10-26 07:51:43 +01:00
if ( isHeading ) {
/ / When we encounter a heading , complete any previous section
if ( currentSection . trim ( ) ) {
const sectionId = ` $ { baseSectionId } _split_ $ { position } ` ;
const sectionType = Section . detectType ( currentSection ) ;
const section = new Section ( sectionId , currentSection . trim ( ) , sectionType ) ;
sections . push ( section ) ;
position + + ;
}
/ / Start new section with this heading
currentSection = line ;
} else {
/ / Add content to current section
if ( currentSection ) currentSection + = ' \\ n ' ;
currentSection + = line ;
}
}
/ / Add the final section if it has content
if ( currentSection . trim ( ) ) {
const sectionId = ` $ { baseSectionId } _split_ $ { position } ` ;
const sectionType = Section . detectType ( currentSection ) ;
const section = new Section ( sectionId , currentSection . trim ( ) , sectionType ) ;
sections . push ( section ) ;
}
return sections ;
}
cancelChanges ( sectionId ) {
const section = this . sections . get ( sectionId ) ;
if ( ! section ) {
throw new Error ( ` Section $ { sectionId } not found ` ) ;
}
const content = section . cancelChanges ( ) ;
/ / Note : No longer tracking single editingSection
this . emit ( ' changes-cancelled ' , { sectionId , content , section : section . getStatus ( ) } ) ;
return content ;
}
resetToOriginal ( sectionId ) {
const section = this . sections . get ( sectionId ) ;
if ( ! section ) {
throw new Error ( ` Section $ { sectionId } not found ` ) ;
}
const content = section . resetToOriginal ( ) ;
this . emit ( ' section-reset ' , { sectionId , content , section : section . getStatus ( ) } ) ;
return content ;
}
stopEditing ( sectionId ) {
const section = this . sections . get ( sectionId ) ;
if ( ! section ) {
throw new Error ( ` Section $ { sectionId } not found ` ) ;
}
const newState = section . stopEditing ( ) ;
/ / Note : No longer tracking single editingSection
this . emit ( ' edit-stopped ' , { sectionId , newState , section : section . getStatus ( ) } ) ;
return newState ;
}
getAllSections ( ) {
return Array . from ( this . sections . values ( ) ) ;
}
getDocumentMarkdown ( ) {
return this . getAllSections ( )
. map ( section = > section . currentMarkdown )
. join ( ' \\ n \\ n ' ) ;
}
}
/ * *
* DOM Renderer - Handles DOM interactions
* /
class DOMRenderer {
constructor ( sectionManager , containerElement ) {
this . sectionManager = sectionManager ;
this . container = containerElement ;
/ / Note : Removed single currentSection tracking to allow multiple concurrent edits
this . editingSections = new Set ( ) ; / / Track multiple editing sections
this . handleSectionClick = this . handleSectionClick . bind ( this ) ;
this . handleAccept = this . handleAccept . bind ( this ) ;
this . handleCancel = this . handleCancel . bind ( this ) ;
this . handleReset = this . handleReset . bind ( this ) ;
this . handleKeydown = this . handleKeydown . bind ( this ) ;
this . setupEventListeners ( ) ;
}
setupEventListeners ( ) {
this . sectionManager . on ( ' sections-created ' , ( data ) = > {
this . renderAllSections ( data . sections ) ;
} ) ;
this . sectionManager . on ( ' edit-started ' , ( data ) = > {
this . showEditor ( data . sectionId , data . content ) ;
} ) ;
this . sectionManager . on ( ' edit-stopped ' , ( data ) = > {
this . hideEditor ( data . sectionId ) ;
/ / Don ' t update content - let pending changes remain
} ) ;
this . sectionManager . on ( ' changes-accepted ' , ( data ) = > {
this . hideEditor ( data . sectionId ) ;
this . updateSectionContent ( data . sectionId , data . content ) ;
} ) ;
this . sectionManager . on ( ' changes-cancelled ' , ( data ) = > {
this . hideEditor ( data . sectionId ) ;
this . updateSectionContent ( data . sectionId , data . content ) ;
} ) ;
this . sectionManager . on ( ' section-reset ' , ( data ) = > {
this . updateTextareaContent ( data . content , data . sectionId ) ;
} ) ;
this . sectionManager . on ( ' section-split ' , ( data ) = > {
console . log ( ' Handling section split in UI ' ) ;
this . handleSectionSplit ( data ) ;
} ) ;
}
renderAllSections ( sections ) {
this . container . innerHTML = ' ' ;
sections . forEach ( section = > {
const element = this . createSectionElement ( section ) ;
section . domElement = element ;
this . container . appendChild ( element ) ;
} ) ;
this . container . addEventListener ( ' click ' , this . handleSectionClick ) ;
}
createSectionElement ( section ) {
const element = document . createElement ( ' div ' ) ;
element . setAttribute ( ' data-section-id ' , section . id ) ;
if ( typeof marked != = ' undefined ' ) {
2025-10-28 03:50:21 +01:00
const html = marked . parse ( section . currentMarkdown ) ;
/ / Add target = " _blank " to all links
const htmlWithTargetBlank = html . replace ( / < a href = " ([^ " ] * ) " ([^>]*)>/g, ' <a href= " $ 1 " target= " _blank " $2> ' );
element . innerHTML = htmlWithTargetBlank ;
2025-10-26 07:51:43 +01:00
} else {
element . innerHTML = ` < p > $ { section . currentMarkdown } < / p > ` ;
}
/ / Setup styling and event handlers
this . setupSectionElement ( element ) ;
return element ;
}
handleSectionClick ( event ) {
2025-10-28 03:50:21 +01:00
/ / Don ' t handle clicks on form elements, buttons, or links
if ( event . target . closest ( ' textarea, button, input, a ' ) ) {
2025-10-26 07:51:43 +01:00
return ;
}
2025-10-27 23:00:42 +01:00
const sectionElement = event . target . closest ( ' .ui-edit-section ' ) ;
2025-10-26 07:51:43 +01:00
if ( ! sectionElement ) return ;
const sectionId = sectionElement . getAttribute ( ' data-section-id ' ) ;
if ( ! sectionId ) return ;
/ / Check if this section is already being edited
const section = this . sectionManager . sections . get ( sectionId ) ;
if ( section & & section . isEditing ( ) ) {
console . log ( ' Section already being edited: ' , sectionId ) ;
return ;
}
try {
console . log ( ' Starting edit for section: ' , sectionId ) ;
this . sectionManager . startEditing ( sectionId ) ;
} catch ( error ) {
console . error ( ' Failed to start editing: ' , error ) ;
}
}
showEditor ( sectionId , content ) {
const element = this . findSectionElement ( sectionId ) ;
if ( ! element ) return ;
this . hideCurrentEditor ( ) ;
2025-10-28 23:55:21 +01:00
const section = this . sectionManager . sections . get ( sectionId ) ;
const isProtectedHeading = section & & section . isProtectedHeading ( ) ;
2025-10-26 07:51:43 +01:00
const editorContainer = document . createElement ( ' div ' ) ;
2025-10-28 23:55:21 +01:00
editorContainer . className = isProtectedHeading ? ' ui-edit-inline-panel ui-insert-protected-panel ' : ' ui-edit-inline-panel ' ;
2025-10-26 07:51:43 +01:00
editorContainer . style . cssText = `
2025-10-28 23:55:21 +01:00
display : flex ;
flex - direction : column ;
gap : 8 px ;
width : 100 % ;
` ;
/ / If this is a protected heading , show the heading display
if ( isProtectedHeading ) {
const headingDisplay = document . createElement ( ' div ' ) ;
headingDisplay . className = ' ui-insert-heading-display ' ;
const headingText = section . getHeadingText ( ) ;
const headingLevel = section . headingLevel ;
const headingMarkdown = ' # ' . repeat ( headingLevel ) + ' ' + headingText ;
headingDisplay . textContent = headingMarkdown ;
headingDisplay . style . cssText = `
font - family : ' SF Mono ' , Monaco , ' Cascadia Code ' , monospace ;
font - size : 14 px ;
font - weight : bold ;
padding : 8 px 12 px ;
background : rgba ( 0 , 0 , 0 , 0.05 ) ;
border - radius : 4 px ;
border - left : 4 px solid #007bff;
color : #333;
` ;
editorContainer . appendChild ( headingDisplay ) ;
}
/ / Create content editing area
const editingArea = document . createElement ( ' div ' ) ;
editingArea . style . cssText = `
2025-10-26 07:51:43 +01:00
display : flex ;
gap : 12 px ;
align - items : flex - start ;
` ;
const textarea = document . createElement ( ' textarea ' ) ;
2025-10-28 23:55:21 +01:00
textarea . className = isProtectedHeading ? ' ui-edit-textarea ui-insert-content-editor ' : ' ui-edit-textarea ui-edit-textarea-main ' ;
/ / For protected headings , only show content after the heading
const textareaContent = isProtectedHeading ? section . getHeadingContent ( ) : content ;
textarea . value = textareaContent ;
2025-10-26 07:51:43 +01:00
textarea . style . cssText = `
flex : 1 ;
min - height : 100 px ;
font - family : ' SF Mono ' , Monaco , ' Cascadia Code ' , monospace ;
border - radius : 6 px ;
padding : 12 px ;
font - size : 14 px ;
line - height : 1.5 ;
resize : vertical ;
` ;
textarea . addEventListener ( ' input ' , ( ) = > {
2025-10-28 23:55:21 +01:00
if ( isProtectedHeading ) {
/ / Reconstruct full content with protected heading
const headingLine = section . originalMarkdown . split ( ' \\ n ' ) [ 0 ] ;
const fullContent = headingLine + ' \\ n ' + textarea . value ;
this . sectionManager . updateContent ( sectionId , fullContent ) ;
} else {
this . sectionManager . updateContent ( sectionId , textarea . value ) ;
}
2025-10-26 07:51:43 +01:00
} ) ;
textarea . addEventListener ( ' keydown ' , this . handleKeydown ) ;
const controls = document . createElement ( ' div ' ) ;
controls . style . cssText = `
display : flex ;
flex - direction : column ;
gap : 6 px ;
` ;
2025-10-28 03:50:21 +01:00
const createButton = ( text , className , handler ) = > {
2025-10-26 07:51:43 +01:00
const btn = document . createElement ( ' button ' ) ;
btn . textContent = text ;
2025-10-28 03:50:21 +01:00
btn . className = className ;
2025-10-26 07:51:43 +01:00
btn . addEventListener ( ' click ' , handler ) ;
return btn ;
} ;
2025-10-28 03:50:21 +01:00
controls . appendChild ( createButton ( ' ✓ Accept ' , ' ui-edit-button ui-edit-button-accept ' , ( ) = > this . handleAccept ( sectionId ) ) ) ;
controls . appendChild ( createButton ( ' ✗ Cancel ' , ' ui-edit-button ui-edit-button-cancel ' , ( ) = > this . handleCancel ( sectionId ) ) ) ;
controls . appendChild ( createButton ( ' 🔄 Reset ' , ' ui-edit-button ui-edit-button-reset ' , ( ) = > this . handleReset ( sectionId ) ) ) ;
2025-10-26 07:51:43 +01:00
2025-10-28 23:55:21 +01:00
editingArea . appendChild ( textarea ) ;
editingArea . appendChild ( controls ) ;
editorContainer . appendChild ( editingArea ) ;
2025-10-26 07:51:43 +01:00
element . innerHTML = ' ' ;
element . appendChild ( editorContainer ) ;
textarea . focus ( ) ;
/ / Track this section as being edited
this . editingSections . add ( sectionId ) ;
}
hideCurrentEditor ( ) {
/ / This method is no longer needed since we support multiple editors
/ / Individual editors are hidden via hideEditor ( sectionId )
}
hideEditor ( sectionId ) {
/ / Remove from editing sections set
this . editingSections . delete ( sectionId ) ;
/ / Force re - render the section to ensure it displays correctly
const section = this . sectionManager . sections . get ( sectionId ) ;
if ( section ) {
this . updateSectionContent ( sectionId , section . currentMarkdown ) ;
}
}
updateSectionContent ( sectionId , content ) {
const element = this . findSectionElement ( sectionId ) ;
if ( ! element ) return ;
if ( typeof marked != = ' undefined ' ) {
2025-10-28 03:50:21 +01:00
const html = marked . parse ( content ) ;
/ / Add target = " _blank " to all links
const htmlWithTargetBlank = html . replace ( / < a href = " ([^ " ] * ) " ([^>]*)>/g, ' <a href= " $ 1 " target= " _blank " $2> ' );
element . innerHTML = htmlWithTargetBlank ;
2025-10-26 07:51:43 +01:00
} else {
element . innerHTML = ` < p > $ { content } < / p > ` ;
}
/ / Restore the section styling and click behavior
this . setupSectionElement ( element ) ;
}
setupSectionElement ( element ) {
2025-10-28 03:50:21 +01:00
element . className = ' ui-edit-section ' ;
2025-10-26 07:51:43 +01:00
element . style . cssText = `
margin : 16 px 0 ;
padding : 12 px ;
border - radius : 6 px ;
cursor : pointer ;
transition : all 0.2 s ease ;
border : 2 px solid transparent ;
` ;
/ / Remove any existing event listeners to avoid duplicates
element . removeEventListener ( ' mouseenter ' , element . _mouseenterHandler ) ;
element . removeEventListener ( ' mouseleave ' , element . _mouseleaveHandler ) ;
/ / Create new handlers and store references
element . _mouseenterHandler = ( ) = > {
2025-10-28 03:50:21 +01:00
element . style . backgroundColor = ' rgba(0, 0, 0, 0.02) ' ;
element . style . borderColor = ' rgba(0, 0, 0, 0.1) ' ;
2025-10-26 07:51:43 +01:00
} ;
element . _mouseleaveHandler = ( ) = > {
element . style . backgroundColor = ' ' ;
element . style . borderColor = ' transparent ' ;
} ;
element . addEventListener ( ' mouseenter ' , element . _mouseenterHandler ) ;
element . addEventListener ( ' mouseleave ' , element . _mouseleaveHandler ) ;
}
updateTextareaContent ( content , sectionId ) {
/ / Find the specific textarea for this section
const element = this . findSectionElement ( sectionId ) ;
if ( element ) {
const textarea = element . querySelector ( ' textarea ' ) ;
if ( textarea ) {
textarea . value = content ;
}
}
}
handleSectionSplit ( data ) {
/ / Clear the editor state for the original section
this . editingSections . delete ( data . originalSectionId ) ;
/ / Find the original section element and its position
const originalElement = this . findSectionElement ( data . originalSectionId ) ;
if ( ! originalElement ) {
console . error ( ' Original section element not found ' ) ;
return ;
}
/ / Get the position of the original element
const originalPosition = Array . from ( this . container . children ) . indexOf ( originalElement ) ;
/ / Create new section elements for the split sections
const newElements = [ ] ;
data . newSections . forEach ( sectionData = > {
const section = this . sectionManager . sections . get ( sectionData . id ) ;
if ( section ) {
const element = this . createSectionElement ( section ) ;
section . domElement = element ;
newElements . push ( element ) ;
}
} ) ;
/ / Remove the original element
originalElement . remove ( ) ;
/ / Insert new elements at the original position
if ( originalPosition < this . container . children . length ) {
const nextElement = this . container . children [ originalPosition ] ;
newElements . forEach ( element = > {
this . container . insertBefore ( element , nextElement ) ;
} ) ;
} else {
/ / If original was at the end , just append
newElements . forEach ( element = > {
this . container . appendChild ( element ) ;
} ) ;
}
/ / Show success message
console . log ( ` Section split into $ { data . newSections . length } sections ` ) ;
/ / Notify the main editor about the split
if ( window . markitectCleanEditor ) {
window . markitectCleanEditor . showMessage (
` ✂ ️ Section split into $ { data . newSections . length } sections ! ` ,
' success '
) ;
}
}
findSectionElement ( sectionId ) {
return this . container . querySelector ( ` [ data - section - id = " $ {sectionId} " ] ` ) ;
}
handleAccept ( sectionId ) {
try {
console . log ( ' Accepting changes for section: ' , sectionId ) ;
this . sectionManager . acceptChanges ( sectionId ) ;
console . log ( ' Changes accepted successfully ' ) ;
} catch ( error ) {
console . error ( ' Failed to accept changes: ' , error ) ;
}
}
handleCancel ( sectionId ) {
try {
console . log ( ' Canceling changes for section: ' , sectionId ) ;
this . sectionManager . cancelChanges ( sectionId ) ;
console . log ( ' Changes canceled successfully ' ) ;
} catch ( error ) {
console . error ( ' Failed to cancel changes: ' , error ) ;
}
}
handleReset ( sectionId ) {
try {
this . sectionManager . resetToOriginal ( sectionId ) ;
} catch ( error ) {
console . error ( ' Failed to reset section: ' , error ) ;
}
}
handleKeydown ( event ) {
if ( ! this . currentSection ) return ;
if ( event . ctrlKey | | event . metaKey ) {
switch ( event . key ) {
case ' Enter ' :
event . preventDefault ( ) ;
this . handleAccept ( this . currentSection ) ;
break ;
case ' Escape ' :
event . preventDefault ( ) ;
this . handleCancel ( this . currentSection ) ;
break ;
}
}
if ( event . key == = ' Escape ' ) {
event . preventDefault ( ) ;
this . handleCancel ( this . currentSection ) ;
}
}
}
/ * *
* Main Editor Integration
* /
class MarkitectCleanEditor {
constructor ( markdownContent , containerElement , options = { } ) {
this . options = {
theme : ' github ' ,
keyboardShortcuts : true ,
autosave : false ,
. . . options
} ;
this . sectionManager = new SectionManager ( ) ;
this . domRenderer = new DOMRenderer ( this . sectionManager , containerElement ) ;
this . originalMarkdown = markdownContent ;
this . initialize ( ) ;
}
initialize ( ) {
try {
const sections = this . sectionManager . createSectionsFromMarkdown ( this . originalMarkdown ) ;
console . log ( ` ✓ Initialized clean editor with $ { sections . length } sections ` ) ;
/ / Add global control panel
this . addGlobalControls ( ) ;
return true ;
} catch ( error ) {
console . error ( ' Failed to initialize clean editor: ' , error ) ;
return false ;
}
}
addGlobalControls ( ) {
/ / Create floating control panel
const panel = document . createElement ( ' div ' ) ;
2025-10-27 23:00:42 +01:00
panel . id = ' ui-edit-floater ' ;
panel . className = ' ui-edit-floater-panel ' ;
2025-10-26 07:51:43 +01:00
panel . innerHTML = `
2025-10-27 23:00:42 +01:00
< div class = " ui-edit-floater-header " >
2025-10-26 07:51:43 +01:00
< h3 > 📝 Editor < / h3 >
2025-10-27 23:00:42 +01:00
< div class = " ui-edit-floater-status " id = " editor-status " > Ready < / div >
2025-10-26 07:51:43 +01:00
< / div >
2025-10-27 23:00:42 +01:00
< div class = " ui-edit-floater-actions " >
< button id = " save-document " class = " ui-edit-button ui-edit-button-accept " > 💾 Save Document < / button >
< button id = " reset-all " class = " ui-edit-button ui-edit-button-reset " > 🔄 Reset All < / button >
< button id = " show-status " class = " ui-edit-button ui-edit-button-secondary " > 📊 Show Status < / button >
2025-10-26 07:51:43 +01:00
< / div >
` ;
/ / Style the panel
panel . style . cssText = `
position : fixed ;
top : 20 px ;
right : 20 px ;
border - radius : 8 px ;
padding : 16 px ;
z - index : 1000 ;
font - family : system - ui , - apple - system , sans - serif ;
min - width : 200 px ;
max - width : 250 px ;
` ;
2025-10-28 03:50:21 +01:00
/ / Add internal styling for structural layout ( theme colors come from CSS )
2025-10-26 07:51:43 +01:00
const style = document . createElement ( ' style ' ) ;
style . textContent = `
2025-10-28 03:50:21 +01:00
. ui - edit - floater - header h3 {
2025-10-26 07:51:43 +01:00
margin : 0 0 8 px 0 ;
font - size : 16 px ;
}
2025-10-28 03:50:21 +01:00
. ui - edit - floater - status {
2025-10-26 07:51:43 +01:00
font - size : 12 px ;
margin - bottom : 12 px ;
}
2025-10-28 03:50:21 +01:00
. ui - edit - button {
2025-10-26 07:51:43 +01:00
display : block ;
width : 100 % ;
margin : 6 px 0 ;
padding : 10 px 12 px ;
border - radius : 4 px ;
cursor : pointer ;
font - size : 14 px ;
font - weight : 500 ;
transition : all 0.2 s ;
2025-10-28 03:50:21 +01:00
border : 1 px solid transparent ;
2025-10-26 07:51:43 +01:00
}
` ;
document . head . appendChild ( style ) ;
document . body . appendChild ( panel ) ;
/ / Add event listeners
document . getElementById ( ' save-document ' ) . addEventListener ( ' click ' , ( ) = > this . saveDocument ( ) ) ;
document . getElementById ( ' reset-all ' ) . addEventListener ( ' click ' , ( ) = > this . resetAllSections ( ) ) ;
document . getElementById ( ' show-status ' ) . addEventListener ( ' click ' , ( ) = > this . showStatus ( ) ) ;
/ / Update status periodically
this . statusInterval = setInterval ( ( ) = > this . updateGlobalStatus ( ) , 2000 ) ;
}
updateGlobalStatus ( ) {
const statusEl = document . getElementById ( ' editor-status ' ) ;
if ( ! statusEl ) return ;
const sections = this . sectionManager . getAllSections ( ) ;
const modified = sections . filter ( s = > s . hasChanges ( ) ) . length ;
const editing = sections . filter ( s = > s . isEditing ( ) ) . length ;
if ( editing > 0 ) {
statusEl . textContent = ` Editing $ { editing } section $ { editing != = 1 ? ' s ' : ' ' } ` ;
2025-10-28 03:50:21 +01:00
statusEl . className = ' ui-edit-floater-status editing ' ;
2025-10-26 07:51:43 +01:00
} else if ( modified > 0 ) {
statusEl . textContent = ` $ { modified } section $ { modified != = 1 ? ' s ' : ' ' } modified ` ;
2025-10-28 03:50:21 +01:00
statusEl . style . color = ' ' ;
2025-10-26 07:51:43 +01:00
} else {
2025-10-28 03:50:21 +01:00
statusEl . textContent = ' All sections saved ✓ ' ;
statusEl . style . color = ' ' ;
2025-10-26 07:51:43 +01:00
}
}
saveDocument ( ) {
const markdown = this . getDocumentMarkdown ( ) ;
const blob = new Blob ( [ markdown ] , { type : ' text/markdown ' } ) ;
const url = URL . createObjectURL ( blob ) ;
/ / Generate intelligent filename
const filename = this . generateSaveFilename ( ) ;
const a = document . createElement ( ' a ' ) ;
a . href = url ;
a . download = filename ;
a . style . display = ' none ' ;
document . body . appendChild ( a ) ;
a . click ( ) ;
document . body . removeChild ( a ) ;
URL . revokeObjectURL ( url ) ;
console . log ( ' 📄 Document saved as: ' , filename ) ;
this . showMessage ( ` Document saved as : $ { filename } ` , ' success ' ) ;
}
generateSaveFilename ( ) {
/ / Try to get original filename from config
let baseName = ' document ' ;
/ / Method 1 : Use original filename from config if available
if ( typeof MARKITECT_EDITOR_CONFIG != = ' undefined ' & & MARKITECT_EDITOR_CONFIG . originalFilename ) {
baseName = MARKITECT_EDITOR_CONFIG . originalFilename ;
}
/ / Method 2 : Try to extract from page title
if ( baseName == = ' document ' ) {
const title = document . title ;
if ( title & & title != = ' Markdown Document ' & & ! title . includes ( ' Debug ' ) & & ! title . includes ( ' Test ' ) ) {
baseName = title . toLowerCase ( )
. replace ( / [ ^ a - z0 - 9 \s - ] / g , ' ' ) / / Remove special chars
. replace ( / \s + / g , ' - ' ) / / Replace spaces with dashes
. replace ( / - + / g , ' - ' ) / / Collapse multiple dashes
. replace ( / ^ - | - $ / g , ' ' ) ; / / Remove leading / trailing dashes
}
}
/ / Method 3 : Try to extract from URL pathname
if ( baseName == = ' document ' ) {
const urlPath = window . location . pathname ;
const match = urlPath . match ( / \/ ( [ ^ \/ ] + ) \. html ? $ / ) ;
if ( match ) {
const urlBaseName = match [ 1 ] ;
if ( ! urlBaseName . includes ( ' debug ' ) & & ! urlBaseName . includes ( ' test ' ) ) {
baseName = urlBaseName . replace ( / _ / g , ' - ' ) ;
}
}
}
/ / Method 4 : Try to extract from first heading
if ( baseName == = ' document ' ) {
const firstHeading = this . sectionManager . getAllSections ( )
. find ( section = > section . sectionType == = ' heading ' ) ;
if ( firstHeading ) {
baseName = firstHeading . originalMarkdown
. replace ( / ^ #+\s*/, '') // Remove markdown heading syntax
. toLowerCase ( )
. replace ( / [ ^ a - z0 - 9 \s - ] / g , ' ' )
. replace ( / \s + / g , ' - ' )
. replace ( / - + / g , ' - ' )
. replace ( / ^ - | - $ / g , ' ' )
. substring ( 0 , 30 ) ; / / Limit length
}
}
/ / Generate timestamp
const now = new Date ( ) ;
const timestamp = now . toISOString ( )
. replace ( / T / , ' - ' )
. replace ( / : / g , ' - ' )
. replace ( / \. \d { 3 } Z $ / , ' ' ) ;
/ / Check if there are modifications
const hasModifications = this . sectionManager . getAllSections ( )
. some ( section = > section . hasChanges ( ) ) ;
if ( hasModifications ) {
return ` $ { baseName } - edited - $ { timestamp } . md ` ;
} else {
return ` $ { baseName } - $ { timestamp } . md ` ;
}
}
2025-10-28 22:36:15 +01:00
/ * *
* Show custom confirmation dialog with theme - consistent styling
* @param { string } message - The confirmation message
* @param { string } confirmText - Text for confirm button ( default : " Confirm " )
* @param { string } cancelText - Text for cancel button ( default : " Cancel " )
* @param { string } warningText - Optional warning text to highlight consequences
* @returns { Promise < boolean > } - True if confirmed , false if cancelled
* /
showConfirmation ( message , confirmText = " Confirm " , cancelText = " Cancel " , warningText = null ) {
return new Promise ( ( resolve ) = > {
/ / Remove any existing modal
const existingModal = document . querySelector ( ' .ui-edit-modal-overlay ' ) ;
if ( existingModal ) {
existingModal . remove ( ) ;
}
/ / Create modal overlay
const overlay = document . createElement ( ' div ' ) ;
overlay . className = ' ui-edit-modal-overlay ' ;
/ / Create modal content
const modal = document . createElement ( ' div ' ) ;
modal . className = ' ui-edit-modal ui-edit-confirmation-modal ' ;
/ / Create header
const header = document . createElement ( ' div ' ) ;
header . className = ' ui-edit-modal-header ' ;
const title = document . createElement ( ' h3 ' ) ;
title . className = ' ui-edit-modal-title ' ;
title . textContent = ' Confirm Action ' ;
const closeBtn = document . createElement ( ' button ' ) ;
closeBtn . className = ' ui-edit-modal-close ' ;
closeBtn . innerHTML = ' × ' ;
closeBtn . setAttribute ( ' aria-label ' , ' Close ' ) ;
header . appendChild ( title ) ;
header . appendChild ( closeBtn ) ;
/ / Create body
const body = document . createElement ( ' div ' ) ;
body . className = ' ui-edit-modal-body ' ;
const content = document . createElement ( ' div ' ) ;
content . className = ' ui-edit-confirmation-content ' ;
content . textContent = message ;
body . appendChild ( content ) ;
/ / Add warning section if provided
if ( warningText ) {
const warning = document . createElement ( ' div ' ) ;
warning . className = ' ui-edit-confirmation-warning ' ;
warning . textContent = warningText ;
body . appendChild ( warning ) ;
}
/ / Create footer with action buttons
const footer = document . createElement ( ' div ' ) ;
footer . className = ' ui-edit-modal-footer ' ;
const buttonContainer = document . createElement ( ' div ' ) ;
buttonContainer . className = ' ui-edit-confirmation-buttons ' ;
const cancelBtn = document . createElement ( ' button ' ) ;
cancelBtn . className = ' ui-edit-button-cancel ' ;
cancelBtn . textContent = cancelText ;
const confirmBtn = document . createElement ( ' button ' ) ;
confirmBtn . className = ' ui-edit-button-confirm ' ;
confirmBtn . textContent = confirmText ;
buttonContainer . appendChild ( cancelBtn ) ;
buttonContainer . appendChild ( confirmBtn ) ;
footer . appendChild ( buttonContainer ) ;
/ / Assemble modal
modal . appendChild ( header ) ;
modal . appendChild ( body ) ;
modal . appendChild ( footer ) ;
overlay . appendChild ( modal ) ;
document . body . appendChild ( overlay ) ;
/ / Function to close modal and resolve
const closeModal = ( result ) = > {
overlay . remove ( ) ;
resolve ( result ) ;
} ;
/ / Event listeners
closeBtn . addEventListener ( ' click ' , ( ) = > closeModal ( false ) ) ;
cancelBtn . addEventListener ( ' click ' , ( ) = > closeModal ( false ) ) ;
confirmBtn . addEventListener ( ' click ' , ( ) = > closeModal ( true ) ) ;
/ / Close on overlay click
overlay . addEventListener ( ' click ' , ( e ) = > {
if ( e . target == = overlay ) {
closeModal ( false ) ;
}
} ) ;
/ / Keyboard support
const handleKeyDown = ( e ) = > {
if ( e . key == = ' Escape ' ) {
closeModal ( false ) ;
} else if ( e . key == = ' Enter ' ) {
closeModal ( true ) ;
}
} ;
document . addEventListener ( ' keydown ' , handleKeyDown ) ;
/ / Clean up event listener when modal is closed
const originalResolve = resolve ;
resolve = ( result ) = > {
document . removeEventListener ( ' keydown ' , handleKeyDown ) ;
originalResolve ( result ) ;
} ;
/ / Show modal with animation
setTimeout ( ( ) = > {
overlay . classList . add ( ' active ' ) ;
/ / Focus the confirm button for accessibility
confirmBtn . focus ( ) ;
} , 10 ) ;
} ) ;
}
async resetAllSections ( ) {
const confirmed = await this . showConfirmation (
' Reset all content to original markdown? ' ,
' Reset Document ' ,
' Keep Changes ' ,
' This will permanently lose all edits and remove any split sections. This action cannot be undone. '
) ;
if ( confirmed ) {
2025-10-26 07:51:43 +01:00
/ / Clear the section manager completely
this . sectionManager . sections . clear ( ) ;
/ / Note : No longer tracking single editingSection
/ / Recreate sections from original markdown
const sections = this . sectionManager . createSectionsFromMarkdown ( this . originalMarkdown ) ;
console . log ( ' 🔄 All sections reset to original structure ' ) ;
this . showMessage ( ' Document reset to original structure ' , ' info ' ) ;
}
}
showStatus ( ) {
const sections = this . sectionManager . getAllSections ( ) ;
const total = sections . length ;
const modified = sections . filter ( s = > s . hasChanges ( ) ) . length ;
const editing = sections . filter ( s = > s . isEditing ( ) ) . length ;
/ / Get the actual save filename that will be used
const saveFilename = this . generateSaveFilename ( ) ;
2025-10-28 03:50:21 +01:00
/ / Create structured content for the modal
const modalContent = {
title : ` 📊 $ { window . editorConfig . repoName } Status ` ,
sections : [
{
title : ' Application Information ' ,
content : ` $ { window . editorConfig . version } `
} ,
{
title : ' File Information ' ,
content : ` Save file : $ { saveFilename }
2025-10-26 07:51:43 +01:00
Source : $ { window . editorConfig . originalFilename }
2025-10-28 03:50:21 +01:00
URL : $ { window . location . protocol } / / $ { window . location . host } $ { window . location . pathname } `
} ,
{
title : ' Document Status ' ,
content : ` • Total sections : $ { total }
2025-10-26 07:51:43 +01:00
• Modified sections : $ { modified }
• Currently editing : $ { editing }
2025-10-28 03:50:21 +01:00
• Unsaved changes : $ { modified > 0 | | editing > 0 ? ' Yes ' : ' No ' } `
} ,
{
title : ' Section Behavior ' ,
content : ` • Each section is a logical unit ( heading + content until next heading )
2025-10-26 07:51:43 +01:00
• Content with line breaks stays in one section
• To split content : Create new headings ( # ## ###)
2025-10-28 03:50:21 +01:00
• Sections don ' t auto-split on line breaks`
} ,
{
title : ' Editing Controls ' ,
content : ` • Click any section to edit its content
2025-10-26 07:51:43 +01:00
• Accept ( ✓ ) - Save changes to that section
• Cancel ( ✗ ) - Discard changes , return to previous state
• Reset ( 🔄 ) - Restore original content for that section
• Save Document - Download all current content
2025-10-28 03:50:21 +01:00
• Reset All - Restore entire document to original state `
}
]
} ;
this . showModal ( modalContent ) ;
}
showModal ( content ) {
/ / Remove any existing modal
const existingModal = document . querySelector ( ' .ui-edit-modal-overlay ' ) ;
if ( existingModal ) {
existingModal . remove ( ) ;
}
/ / Create modal overlay
const overlay = document . createElement ( ' div ' ) ;
overlay . className = ' ui-edit-modal-overlay ' ;
/ / Create modal content
const modal = document . createElement ( ' div ' ) ;
modal . className = ' ui-edit-modal ' ;
/ / Create header
const header = document . createElement ( ' div ' ) ;
header . className = ' ui-edit-modal-header ' ;
const title = document . createElement ( ' h3 ' ) ;
title . className = ' ui-edit-modal-title ' ;
title . textContent = content . title ;
const closeBtn = document . createElement ( ' button ' ) ;
closeBtn . className = ' ui-edit-modal-close ' ;
closeBtn . innerHTML = ' × ' ;
closeBtn . setAttribute ( ' aria-label ' , ' Close ' ) ;
header . appendChild ( title ) ;
header . appendChild ( closeBtn ) ;
/ / Create body
const body = document . createElement ( ' div ' ) ;
body . className = ' ui-edit-modal-body ' ;
/ / Add sections
content . sections . forEach ( section = > {
const sectionDiv = document . createElement ( ' div ' ) ;
sectionDiv . className = ' ui-edit-modal-section ' ;
const sectionTitle = document . createElement ( ' div ' ) ;
sectionTitle . className = ' ui-edit-modal-section-title ' ;
sectionTitle . textContent = section . title ;
2025-10-26 07:51:43 +01:00
2025-10-28 03:50:21 +01:00
const sectionContent = document . createElement ( ' div ' ) ;
sectionContent . className = ' ui-edit-modal-content ' ;
sectionContent . textContent = section . content ;
sectionDiv . appendChild ( sectionTitle ) ;
sectionDiv . appendChild ( sectionContent ) ;
body . appendChild ( sectionDiv ) ;
} ) ;
/ / Create footer with close button
const footer = document . createElement ( ' div ' ) ;
footer . className = ' ui-edit-modal-footer ' ;
const footerCloseBtn = document . createElement ( ' button ' ) ;
footerCloseBtn . className = ' ui-edit-button ui-edit-button-accept ' ;
footerCloseBtn . textContent = ' Close ' ;
footer . appendChild ( footerCloseBtn ) ;
/ / Assemble modal
modal . appendChild ( header ) ;
modal . appendChild ( body ) ;
modal . appendChild ( footer ) ;
overlay . appendChild ( modal ) ;
/ / Add to page
document . body . appendChild ( overlay ) ;
/ / Close handlers
const closeModal = ( ) = > {
overlay . classList . remove ( ' active ' ) ;
setTimeout ( ( ) = > {
if ( overlay . parentNode ) {
overlay . parentNode . removeChild ( overlay ) ;
}
} , 300 ) ;
} ;
closeBtn . addEventListener ( ' click ' , closeModal ) ;
footerCloseBtn . addEventListener ( ' click ' , closeModal ) ;
/ / Close on overlay click ( but not modal content )
overlay . addEventListener ( ' click ' , ( e ) = > {
if ( e . target == = overlay ) {
closeModal ( ) ;
}
} ) ;
/ / Close on Escape key
const handleKeydown = ( e ) = > {
if ( e . key == = ' Escape ' ) {
closeModal ( ) ;
document . removeEventListener ( ' keydown ' , handleKeydown ) ;
}
} ;
document . addEventListener ( ' keydown ' , handleKeydown ) ;
/ / Show modal with animation
requestAnimationFrame ( ( ) = > {
overlay . classList . add ( ' active ' ) ;
} ) ;
/ / Focus management
setTimeout ( ( ) = > {
closeBtn . focus ( ) ;
} , 100 ) ;
2025-10-26 07:51:43 +01:00
}
showMessage ( message , type = ' info ' ) {
const messageDiv = document . createElement ( ' div ' ) ;
messageDiv . textContent = message ;
const colors = {
' success ' : ' #28a745 ' ,
' error ' : ' #dc3545 ' ,
' info ' : ' #007acc '
} ;
messageDiv . style . cssText = `
position : fixed ;
top : 20 px ;
left : 50 % ;
transform : translateX ( - 50 % ) ;
background : $ { colors [ type ] | | colors . info } ;
color : white ;
padding : 12 px 20 px ;
border - radius : 4 px ;
box - shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.2 ) ;
z - index : 10001 ;
font - size : 14 px ;
max - width : 400 px ;
text - align : center ;
` ;
document . body . appendChild ( messageDiv ) ;
setTimeout ( ( ) = > {
if ( messageDiv . parentNode ) {
messageDiv . parentNode . removeChild ( messageDiv ) ;
}
} , 3000 ) ;
}
getDocumentMarkdown ( ) {
return this . sectionManager . getDocumentMarkdown ( ) ;
}
}
/ / Initialize the clean editor system
let markitectCleanEditor ;
function initializeCleanEditor ( ) {
const container = document . getElementById ( ' markdown-content ' ) ;
if ( ! container ) {
console . error ( ' Markdown content container not found ' ) ;
return ;
}
if ( typeof window . MarkitectEditor == = ' undefined ' ) {
console . error ( ' MarkitectEditor not found ' ) ;
return ;
}
markitectCleanEditor = new window . MarkitectEditor . MarkitectCleanEditor ( markdownContent , container ) ;
window . markitectCleanEditor = markitectCleanEditor ; / / Make globally available
console . log ( ' ✅ Clean section editor initialized successfully ' ) ;
}
2025-10-28 22:36:15 +01:00
/ / Document scroll indicators
function initializeScrollIndicators ( ) {
/ / Create scroll indicators
const scrollUpIndicator = document . createElement ( ' div ' ) ;
scrollUpIndicator . className = ' ui-scroll-indicator ui-scroll-indicator-up ' ;
scrollUpIndicator . setAttribute ( ' aria-label ' , ' Scroll to top ' ) ;
scrollUpIndicator . setAttribute ( ' title ' , ' Scroll up ' ) ;
const scrollDownIndicator = document . createElement ( ' div ' ) ;
scrollDownIndicator . className = ' ui-scroll-indicator ui-scroll-indicator-down ' ;
scrollDownIndicator . setAttribute ( ' aria-label ' , ' Scroll to bottom ' ) ;
scrollDownIndicator . setAttribute ( ' title ' , ' Scroll down ' ) ;
document . body . appendChild ( scrollUpIndicator ) ;
document . body . appendChild ( scrollDownIndicator ) ;
let scrollIndicatorTimeout = null ;
/ / Function to show / hide indicators based on scroll position and mouse position
function updateScrollIndicators ( mouseY = null ) {
const scrollTop = window . pageYOffset | | document . documentElement . scrollTop ;
const windowHeight = window . innerHeight ;
const documentHeight = document . documentElement . scrollHeight ;
/ / Determine if scrolling is possible in each direction
const canScrollUp = scrollTop > 0 ;
const canScrollDown = scrollTop < documentHeight - windowHeight ;
/ / Only show indicators if there ' s any scroll possibility or if document is short
let showUp = false ;
let showDown = false ;
/ / Show indicators on mouseover near top / bottom of viewport
if ( mouseY != = null ) {
const topZone = 100 ; / / pixels from top
const bottomZone = windowHeight - 100 ; / / pixels from bottom
if ( mouseY < = topZone ) {
showUp = true ;
}
if ( mouseY > = bottomZone ) {
showDown = true ;
}
}
/ / Update indicator visibility and state
if ( showUp ) {
scrollUpIndicator . classList . add ( ' active ' ) ;
if ( canScrollUp ) {
scrollUpIndicator . classList . remove ( ' disabled ' ) ;
} else {
scrollUpIndicator . classList . add ( ' disabled ' ) ;
}
} else {
scrollUpIndicator . classList . remove ( ' active ' ) ;
}
if ( showDown ) {
scrollDownIndicator . classList . add ( ' active ' ) ;
if ( canScrollDown ) {
scrollDownIndicator . classList . remove ( ' disabled ' ) ;
} else {
scrollDownIndicator . classList . add ( ' disabled ' ) ;
}
} else {
scrollDownIndicator . classList . remove ( ' active ' ) ;
}
/ / Auto - hide after a delay when mouse moves away
if ( scrollIndicatorTimeout ) {
clearTimeout ( scrollIndicatorTimeout ) ;
}
scrollIndicatorTimeout = setTimeout ( ( ) = > {
scrollUpIndicator . classList . remove ( ' active ' ) ;
scrollDownIndicator . classList . remove ( ' active ' ) ;
} , 2000 ) ;
}
/ / Mouse move handler
function handleMouseMove ( e ) {
updateScrollIndicators ( e . clientY ) ;
}
/ / Smooth scroll function
function smoothScroll ( targetY , duration = 500 ) {
const startY = window . pageYOffset ;
const difference = targetY - startY ;
const startTime = performance . now ( ) ;
function step ( currentTime ) {
const elapsed = currentTime - startTime ;
const progress = Math . min ( elapsed / duration , 1 ) ;
/ / Easing function ( ease - out )
const easeOut = 1 - Math . pow ( 1 - progress , 3 ) ;
window . scrollTo ( 0 , startY + difference * easeOut ) ;
if ( progress < 1 ) {
requestAnimationFrame ( step ) ;
}
}
requestAnimationFrame ( step ) ;
}
/ / Click handlers for smooth scrolling
scrollUpIndicator . addEventListener ( ' click ' , ( ) = > {
const currentScroll = window . pageYOffset ;
const targetScroll = Math . max ( 0 , currentScroll - window . innerHeight * 0.8 ) ;
smoothScroll ( targetScroll ) ;
} ) ;
scrollDownIndicator . addEventListener ( ' click ' , ( ) = > {
const currentScroll = window . pageYOffset ;
const maxScroll = document . documentElement . scrollHeight - window . innerHeight ;
const targetScroll = Math . min ( maxScroll , currentScroll + window . innerHeight * 0.8 ) ;
smoothScroll ( targetScroll ) ;
} ) ;
/ / Event listeners
document . addEventListener ( ' mousemove ' , handleMouseMove ) ;
document . addEventListener ( ' scroll ' , ( ) = > updateScrollIndicators ( ) ) ;
/ / Initial check
updateScrollIndicators ( ) ;
console . log ( ' ✅ Document scroll indicators initialized ' ) ;
}
2025-10-26 07:51:43 +01:00
/ / Export for testing and usage
if ( typeof module != = ' undefined ' & & module . exports ) {
module . exports = { Section , SectionManager , DOMRenderer , MarkitectCleanEditor } ;
} else {
window . MarkitectEditor = { Section , SectionManager , DOMRenderer , MarkitectCleanEditor } ;
}
2025-10-28 03:50:21 +01:00
"""