feat: complete clean editor implementation with comprehensive UI framework
Major architectural improvements and feature enhancements: ## Core Features Added - ✨ Custom status modal system replacing browser alerts with theme-consistent branding - ✨ HTML generation dogtag with timestamp and username linking - ✨ All document links now open in new tabs without triggering edit mode - ✨ Comprehensive UI framework documentation (UserInterfaceFramework.md) ## Architecture Improvements - 🔧 Complete cleanup of document_manager.py - removed 2000+ lines of legacy code - 🔧 Clean wrapper implementation maintaining backward compatibility - 🔧 Enhanced database integration with proper front matter parsing - 🔧 Improved AST processing and cache file generation ## UI/UX Enhancements - 🎨 Theme-aware modal dialogs with proper CSS styling and accessibility - 🎨 Consistent CSS class naming conventions across all UI components - 🎨 Enhanced link behavior for better document navigation - 🎨 Professional status information display ## Developer Experience - 📝 Comprehensive UI component documentation for future development - 🧪 Updated test suite to work with clean implementation - 🧪 Fixed multiple test compatibility issues - 🧪 Enhanced error handling and validation ## Technical Details - Added store_document method to CleanDocumentManager - Enhanced ingest_file method with proper title extraction - Implemented theme-consistent modal overlay patterns - Added --nodogtag CLI option for clean output when needed - Fixed CSS escape sequences and JavaScript syntax issues This release establishes a solid foundation for the clean editor architecture while maintaining full backward compatibility with existing functionality. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,8 +16,47 @@ class CleanDocumentManager:
|
||||
def __init__(self, db_manager=None):
|
||||
self.db_manager = db_manager
|
||||
|
||||
def store_document(self, file_path: str, content: str, ast: list = None, front_matter: dict = None):
|
||||
"""Store a document in the database."""
|
||||
if self.db_manager:
|
||||
from pathlib import Path
|
||||
filename = Path(file_path).name
|
||||
return self.db_manager.store_markdown_file(filename, content)
|
||||
|
||||
def get_file(self, file_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Retrieve a markdown file from the database.
|
||||
|
||||
Args:
|
||||
file_path: Path to the markdown file to retrieve
|
||||
|
||||
Returns:
|
||||
Dictionary containing file content and metadata
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If file is not found in database
|
||||
"""
|
||||
if not self.db_manager:
|
||||
raise ValueError("Database manager not initialized")
|
||||
|
||||
# Get file from database
|
||||
file_data = self.db_manager.get_markdown_file(file_path)
|
||||
|
||||
if file_data is None:
|
||||
raise FileNotFoundError(f"File '{file_path}' not found in database")
|
||||
|
||||
return {
|
||||
'content': file_data.get('content', ''),
|
||||
'metadata': {
|
||||
'filename': file_data.get('filename', file_path),
|
||||
'front_matter': file_data.get('front_matter'),
|
||||
'size': len(file_data.get('content', '')),
|
||||
'modified': file_data.get('modified')
|
||||
}
|
||||
}
|
||||
|
||||
def render_file(self, input_file: str, output_file: str, template: str = None, css: str = None,
|
||||
edit_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True) -> Dict[str, Any]:
|
||||
edit_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True, nodogtag: bool = False) -> Dict[str, Any]:
|
||||
"""
|
||||
Render a markdown file to HTML with optional clean editing capabilities.
|
||||
"""
|
||||
@@ -49,7 +88,8 @@ class CleanDocumentManager:
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts,
|
||||
original_filename=original_filename,
|
||||
version_info=version_info
|
||||
version_info=version_info,
|
||||
nodogtag=nodogtag
|
||||
)
|
||||
|
||||
# Write HTML file
|
||||
@@ -73,51 +113,17 @@ class CleanDocumentManager:
|
||||
|
||||
def _get_version_info(self) -> dict:
|
||||
"""Get repository name and version information."""
|
||||
version_info = {
|
||||
from .__version__ import get_version_info
|
||||
|
||||
version_info = get_version_info()
|
||||
|
||||
# Transform to the format expected by the editor
|
||||
return {
|
||||
'repo_name': 'Markitect',
|
||||
'version': '0.3.0',
|
||||
'git_info': ''
|
||||
'version': version_info['full_version'],
|
||||
'git_info': '' # Already included in full_version
|
||||
}
|
||||
|
||||
try:
|
||||
# Try to get version from package metadata
|
||||
from importlib.metadata import version as get_version
|
||||
version_info['version'] = get_version('markitect')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Try to get git information
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# Get git commit hash and status
|
||||
try:
|
||||
git_hash = subprocess.check_output(
|
||||
['git', 'rev-parse', '--short', 'HEAD'],
|
||||
cwd=Path(__file__).parent,
|
||||
stderr=subprocess.DEVNULL
|
||||
).decode().strip()
|
||||
|
||||
# Check if there are uncommitted changes
|
||||
try:
|
||||
subprocess.check_output(
|
||||
['git', 'diff-index', '--quiet', 'HEAD', '--'],
|
||||
cwd=Path(__file__).parent,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
git_status = ''
|
||||
except subprocess.CalledProcessError:
|
||||
git_status = '-modified'
|
||||
|
||||
version_info['git_info'] = f" (git:{git_hash}{git_status})"
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return version_info
|
||||
|
||||
def _get_template_css(self, template: str = None) -> str:
|
||||
"""Generate layered theme CSS styles."""
|
||||
# Import layered theme functions
|
||||
@@ -310,10 +316,29 @@ class CleanDocumentManager:
|
||||
.markitect-edit-mode .ui-edit-floater-header {{
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-floater-header h3 {{
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-inline-panel {{
|
||||
background: {props['editor_panel_bg']};
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
box-shadow: 0 2px 8px {props.get('editor_shadow', 'rgba(0,0,0,0.1)')};
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin: 8px 0;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button {{
|
||||
background: {props.get('editor_button_bg', '#ffffff')};
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
min-width: 70px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button:hover {{
|
||||
background: {props.get('editor_button_hover', '#e9ecef')};
|
||||
@@ -323,17 +348,36 @@ class CleanDocumentManager:
|
||||
background: {props.get('editor_button_active', '#dee2e6')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-accept {{
|
||||
background: {props.get('editor_button_bg', '#4caf50')};
|
||||
background: {props.get('editor_accept_bg', '#4caf50')};
|
||||
color: white;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-accept:hover {{
|
||||
background: {props.get('editor_accept_hover', '#388e3c')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-cancel {{
|
||||
background: {props.get('editor_button_bg', '#f44336')};
|
||||
background: {props.get('editor_cancel_bg', '#f44336')};
|
||||
color: white;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-cancel:hover {{
|
||||
background: {props.get('editor_cancel_hover', '#d32f2f')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-reset {{
|
||||
background: {props.get('editor_button_bg', '#ff9800')};
|
||||
background: {props.get('editor_reset_bg', '#ff9800')};
|
||||
color: white;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-reset:hover {{
|
||||
background: {props.get('editor_reset_hover', '#f57c00')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-secondary {{
|
||||
background: {props.get('editor_secondary_bg', '#6c757d')};
|
||||
color: white;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-secondary:hover {{
|
||||
background: {props.get('editor_secondary_hover', '#545b62')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-section-frame {{
|
||||
border: 2px solid {props.get('editor_focus_color', '#0066cc')};
|
||||
box-shadow: 0 0 0 3px {props.get('editor_focus_color', '#0066cc')}33;
|
||||
border: 2px solid {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))};
|
||||
box-shadow: 0 0 0 3px {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))}33;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-textarea {{
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
@@ -341,8 +385,103 @@ class CleanDocumentManager:
|
||||
background: {props.get('editor_button_bg', '#ffffff')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-textarea:focus {{
|
||||
border-color: {props.get('editor_focus_color', '#0066cc')};
|
||||
box-shadow: 0 0 0 2px {props.get('editor_focus_color', '#0066cc')}33;
|
||||
border-color: {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))};
|
||||
box-shadow: 0 0 0 2px {props.get('editor_focus_color', props.get('editor_panel_border', '#dee2e6'))}33;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-overlay {{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-overlay.active {{
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal {{
|
||||
background: {props['editor_panel_bg']};
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
box-shadow: 0 8px 32px {props.get('editor_shadow', 'rgba(0,0,0,0.2)')};
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
border-radius: 8px;
|
||||
max-width: 600px;
|
||||
max-height: 80vh;
|
||||
width: 90%;
|
||||
overflow: hidden;
|
||||
transform: scale(0.9) translateY(-20px);
|
||||
transition: transform 0.3s;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-overlay.active .ui-edit-modal {{
|
||||
transform: scale(1) translateY(0);
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-header {{
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-title {{
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-close {{
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-close:hover {{
|
||||
background: {props.get('editor_button_hover', '#e9ecef')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-body {{
|
||||
padding: 20px 24px;
|
||||
overflow-y: auto;
|
||||
max-height: 60vh;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-content {{
|
||||
white-space: pre-line;
|
||||
line-height: 1.5;
|
||||
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
font-size: 14px;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-section {{
|
||||
margin-bottom: 16px;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-section:last-child {{
|
||||
margin-bottom: 0;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-section-title {{
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-modal-footer {{
|
||||
padding: 16px 24px 20px;
|
||||
border-top: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
text-align: right;
|
||||
}}
|
||||
outline: none;
|
||||
}}"""
|
||||
|
||||
return f"<style>{base_css}{heading_css}{text_css}{element_css}{link_css}{accent_css}{ui_css}</style>"
|
||||
@@ -382,11 +521,34 @@ class CleanDocumentManager:
|
||||
return self._generate_layered_css(layered_props)
|
||||
|
||||
def _generate_html_template(self, markdown_content: str, title: str, css: str = None, template: str = None,
|
||||
edit_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True, original_filename: str = 'document', version_info: dict = None) -> str:
|
||||
edit_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True, original_filename: str = 'document', version_info: dict = None, nodogtag: bool = False) -> str:
|
||||
"""Generate clean HTML template."""
|
||||
|
||||
# Add dogtag to markdown content if not disabled
|
||||
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
|
||||
|
||||
# Escape the markdown content for JavaScript
|
||||
js_markdown_content = json.dumps(markdown_content)
|
||||
js_markdown_content = json.dumps(markdown_content_with_dogtag)
|
||||
|
||||
# Handle CSS styles
|
||||
css_content = ""
|
||||
@@ -413,7 +575,7 @@ class CleanDocumentManager:
|
||||
body_classes = ' class="markitect-edit-mode"'
|
||||
|
||||
# Configuration for clean editor
|
||||
version_str = f"{version_info['repo_name']} v{version_info['version']}{version_info['git_info']}" if version_info else "Markitect v0.3.0"
|
||||
version_str = f"{version_info['repo_name']} v{version_info['version']}{version_info['git_info']}" if version_info else "Markitect v0.5.0.dev"
|
||||
editor_config = f"""
|
||||
const MARKITECT_EDIT_MODE = true;
|
||||
const MARKITECT_EDITOR_CONFIG = {{
|
||||
@@ -466,7 +628,10 @@ class CleanDocumentManager:
|
||||
if (contentDiv) {{
|
||||
if (typeof marked !== 'undefined') {{
|
||||
try {{
|
||||
contentDiv.innerHTML = marked.parse(markdownContent);
|
||||
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;
|
||||
console.log("✓ Content rendered successfully");
|
||||
console.log('✓ Markdown rendered successfully');
|
||||
}} catch (error) {{
|
||||
@@ -1017,7 +1182,10 @@ class DOMRenderer {
|
||||
element.setAttribute('data-section-id', section.id);
|
||||
|
||||
if (typeof marked !== 'undefined') {
|
||||
element.innerHTML = marked.parse(section.currentMarkdown);
|
||||
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;
|
||||
} else {
|
||||
element.innerHTML = `<p>${section.currentMarkdown}</p>`;
|
||||
}
|
||||
@@ -1029,8 +1197,8 @@ class DOMRenderer {
|
||||
}
|
||||
|
||||
handleSectionClick(event) {
|
||||
// Don't handle clicks on form elements or buttons
|
||||
if (event.target.closest('textarea, button, input')) {
|
||||
// Don't handle clicks on form elements, buttons, or links
|
||||
if (event.target.closest('textarea, button, input, a')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1062,6 +1230,7 @@ class DOMRenderer {
|
||||
this.hideCurrentEditor();
|
||||
|
||||
const editorContainer = document.createElement('div');
|
||||
editorContainer.className = 'ui-edit-inline-panel';
|
||||
editorContainer.style.cssText = `
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
@@ -1076,7 +1245,6 @@ class DOMRenderer {
|
||||
flex: 1;
|
||||
min-height: 100px;
|
||||
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
||||
border: 2px solid #007acc;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
@@ -1096,36 +1264,17 @@ class DOMRenderer {
|
||||
gap: 6px;
|
||||
`;
|
||||
|
||||
const createButton = (text, color, handler) => {
|
||||
const createButton = (text, className, handler) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = text;
|
||||
|
||||
// Add CSS classes based on button type
|
||||
btn.className = 'ui-edit-button';
|
||||
if (text.includes('Accept')) {
|
||||
btn.className += ' ui-edit-button-accept';
|
||||
} else if (text.includes('Cancel')) {
|
||||
btn.className += ' ui-edit-button-cancel';
|
||||
} else if (text.includes('Reset')) {
|
||||
btn.className += ' ui-edit-button-reset';
|
||||
}
|
||||
btn.style.cssText = `
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
background: ${color};
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
min-width: 70px;
|
||||
`;
|
||||
btn.className = className;
|
||||
btn.addEventListener('click', handler);
|
||||
return btn;
|
||||
};
|
||||
|
||||
controls.appendChild(createButton('✓ Accept', '#4caf50', () => this.handleAccept(sectionId)));
|
||||
controls.appendChild(createButton('✗ Cancel', '#f44336', () => this.handleCancel(sectionId)));
|
||||
controls.appendChild(createButton('🔄 Reset', '#ff9800', () => this.handleReset(sectionId)));
|
||||
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)));
|
||||
|
||||
editorContainer.appendChild(textarea);
|
||||
editorContainer.appendChild(controls);
|
||||
@@ -1159,7 +1308,10 @@ class DOMRenderer {
|
||||
if (!element) return;
|
||||
|
||||
if (typeof marked !== 'undefined') {
|
||||
element.innerHTML = marked.parse(content);
|
||||
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;
|
||||
} else {
|
||||
element.innerHTML = `<p>${content}</p>`;
|
||||
}
|
||||
@@ -1169,7 +1321,7 @@ class DOMRenderer {
|
||||
}
|
||||
|
||||
setupSectionElement(element) {
|
||||
element.className = 'ui-edit-section ui-edit-section-frame';
|
||||
element.className = 'ui-edit-section';
|
||||
element.style.cssText = `
|
||||
margin: 16px 0;
|
||||
padding: 12px;
|
||||
@@ -1185,8 +1337,8 @@ class DOMRenderer {
|
||||
|
||||
// Create new handlers and store references
|
||||
element._mouseenterHandler = () => {
|
||||
element.style.backgroundColor = 'rgba(33, 150, 243, 0.05)';
|
||||
element.style.borderColor = 'rgba(33, 150, 243, 0.2)';
|
||||
element.style.backgroundColor = 'rgba(0, 0, 0, 0.02)';
|
||||
element.style.borderColor = 'rgba(0, 0, 0, 0.1)';
|
||||
};
|
||||
|
||||
element._mouseleaveHandler = () => {
|
||||
@@ -1370,62 +1522,36 @@ class MarkitectCleanEditor {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: white;
|
||||
border: 2px solid #007acc;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 1000;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
min-width: 200px;
|
||||
max-width: 250px;
|
||||
`;
|
||||
|
||||
// Add internal styling
|
||||
// Add internal styling for structural layout (theme colors come from CSS)
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#markitect-global-controls .control-header h3 {
|
||||
.ui-edit-floater-header h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
color: #007acc;
|
||||
}
|
||||
#markitect-global-controls .control-status {
|
||||
.ui-edit-floater-status {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
#markitect-global-controls .control-btn {
|
||||
.ui-edit-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 6px 0;
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
#markitect-global-controls .control-btn.primary {
|
||||
background: #007acc;
|
||||
color: white;
|
||||
}
|
||||
#markitect-global-controls .control-btn.primary:hover {
|
||||
background: #005a9f;
|
||||
}
|
||||
#markitect-global-controls .control-btn.warning {
|
||||
background: #ff9800;
|
||||
color: white;
|
||||
}
|
||||
#markitect-global-controls .control-btn.warning:hover {
|
||||
background: #f57c00;
|
||||
}
|
||||
#markitect-global-controls .control-btn.secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
#markitect-global-controls .control-btn.secondary:hover {
|
||||
background: #545b62;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
@@ -1451,13 +1577,13 @@ class MarkitectCleanEditor {
|
||||
|
||||
if (editing > 0) {
|
||||
statusEl.textContent = `Editing ${editing} section${editing !== 1 ? 's' : ''}`;
|
||||
statusEl.style.color = '#007acc';
|
||||
statusEl.className = 'ui-edit-floater-status editing';
|
||||
} else if (modified > 0) {
|
||||
statusEl.textContent = `${modified} section${modified !== 1 ? 's' : ''} modified`;
|
||||
statusEl.style.color = '#ff9800';
|
||||
statusEl.style.color = '';
|
||||
} else {
|
||||
statusEl.textContent = 'All sections saved';
|
||||
statusEl.style.color = '#28a745';
|
||||
statusEl.textContent = 'All sections saved ✓';
|
||||
statusEl.style.color = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1572,32 +1698,158 @@ class MarkitectCleanEditor {
|
||||
// Get the actual save filename that will be used
|
||||
const saveFilename = this.generateSaveFilename();
|
||||
|
||||
const message = `${window.editorConfig.repoName} ${window.editorConfig.version}
|
||||
Save file: ${saveFilename}
|
||||
// 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}
|
||||
Source: ${window.editorConfig.originalFilename}
|
||||
${window.location.protocol}//${window.location.host}${window.location.pathname}
|
||||
|
||||
Document Status:
|
||||
• Total sections: ${total}
|
||||
URL: ${window.location.protocol}//${window.location.host}${window.location.pathname}`
|
||||
},
|
||||
{
|
||||
title: 'Document Status',
|
||||
content: `• Total sections: ${total}
|
||||
• Modified sections: ${modified}
|
||||
• Currently editing: ${editing}
|
||||
• Unsaved changes: ${modified > 0 || editing > 0 ? 'Yes' : 'No'}
|
||||
|
||||
SECTION BEHAVIOR:
|
||||
• Each section is a logical unit (heading + content until next heading)
|
||||
• Unsaved changes: ${modified > 0 || editing > 0 ? 'Yes' : 'No'}`
|
||||
},
|
||||
{
|
||||
title: 'Section Behavior',
|
||||
content: `• Each section is a logical unit (heading + content until next heading)
|
||||
• Content with line breaks stays in one section
|
||||
• To split content: Create new headings (# ## ###)
|
||||
• Sections don't auto-split on line breaks
|
||||
|
||||
EDITING CONTROLS:
|
||||
• Click any section to edit its content
|
||||
• Sections don't auto-split on line breaks`
|
||||
},
|
||||
{
|
||||
title: 'Editing Controls',
|
||||
content: `• Click any section to edit its content
|
||||
• 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
|
||||
• Reset All - Restore entire document to original state`;
|
||||
• Reset All - Restore entire document to original state`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
alert(message);
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
showMessage(message, type = 'info') {
|
||||
@@ -1666,4 +1918,4 @@ if (typeof module !== 'undefined' && module.exports) {
|
||||
} else {
|
||||
window.MarkitectEditor = { Section, SectionManager, DOMRenderer, MarkitectCleanEditor };
|
||||
}
|
||||
"""
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user