Files
markitect-main/src/clean_editor_integration.js
tegwick d0abaab63a
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
chore: update project state and prepare for image support development
- Add comprehensive image test document with various image types
- Update project structure with development artifacts
- Prepare foundation for image support enhancement phase
- Include test files for validating image editing workflows

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 08:06:22 +01:00

517 lines
15 KiB
JavaScript

/**
* Clean Editor Integration
*
* This file provides a complete, drop-in replacement for the existing
* section editing functionality using our clean object-oriented architecture.
*/
// Include the core classes (in real implementation, these would be imported)
// For now, assuming they're loaded separately
/**
* MarkitectCleanEditor - Main integration class
*
* This replaces the existing complex editor implementation with our
* clean, testable architecture.
*/
class MarkitectCleanEditor {
constructor(markdownContent, containerElement, options = {}) {
this.options = {
theme: 'github',
keyboardShortcuts: true,
autosave: false,
...options
};
// Initialize the core system
this.sectionManager = new MarkitectEditor.SectionManager();
this.domRenderer = new MarkitectEditor.DOMRenderer(this.sectionManager, containerElement);
// Store original content
this.originalMarkdown = markdownContent;
// Event handlers for external integration
this.onDocumentChange = null;
this.onSectionChange = null;
// Setup external event forwarding
this.setupEventForwarding();
// Initialize sections
this.initialize();
}
/**
* Initialize the editor with the markdown content
*/
initialize() {
try {
// Create sections from markdown
const sections = this.sectionManager.createSectionsFromMarkdown(this.originalMarkdown);
console.log(`✓ Initialized clean editor with ${sections.length} sections`);
// Set up global keyboard shortcuts
this.setupGlobalKeyboardShortcuts();
return true;
} catch (error) {
console.error('Failed to initialize clean editor:', error);
return false;
}
}
/**
* Setup event forwarding to external callbacks
*/
setupEventForwarding() {
// Forward document-level changes
this.sectionManager.on('changes-accepted', () => {
if (this.onDocumentChange) {
this.onDocumentChange(this.getDocumentStatus());
}
});
this.sectionManager.on('changes-cancelled', () => {
if (this.onDocumentChange) {
this.onDocumentChange(this.getDocumentStatus());
}
});
// Forward section-level changes
this.sectionManager.on('content-updated', (data) => {
if (this.onSectionChange) {
this.onSectionChange(data);
}
});
}
/**
* Setup global keyboard shortcuts
*/
setupGlobalKeyboardShortcuts() {
if (!this.options.keyboardShortcuts) return;
document.addEventListener('keydown', (event) => {
if (event.ctrlKey || event.metaKey) {
switch (event.key) {
case 's':
event.preventDefault();
this.saveDocument();
break;
case 'z':
if (event.shiftKey) {
event.preventDefault();
// Could implement redo
} else {
event.preventDefault();
// Could implement undo
}
break;
case 'r':
event.preventDefault();
this.resetAllSections();
break;
}
}
});
}
/**
* Get current document markdown
* @returns {string} The complete document markdown
*/
getDocumentMarkdown() {
return this.sectionManager.getDocumentMarkdown();
}
/**
* Get document status
* @returns {Object} Document status object
*/
getDocumentStatus() {
return this.sectionManager.getDocumentStatus();
}
/**
* Check if document has unsaved changes
* @returns {boolean} True if there are unsaved changes
*/
hasUnsavedChanges() {
const status = this.getDocumentStatus();
return status.hasUnsavedChanges;
}
/**
* Save document (triggers download)
*/
saveDocument() {
const markdown = this.getDocumentMarkdown();
const status = this.getDocumentStatus();
// Create download
const blob = new Blob([markdown], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.md';
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log('📄 Document saved:', status);
this.showMessage('Document saved successfully!', 'success');
}
/**
* Reset all sections to original state
*/
resetAllSections() {
if (confirm('Reset all content to original markdown? This will lose all edits.')) {
this.domRenderer.resetAllSections();
this.showMessage('All sections reset to original content', 'info');
}
}
/**
* Show message to user
* @param {string} message - The message to show
* @param {string} type - Message type: 'success', 'error', 'info'
*/
showMessage(message, type = 'info') {
// Create message element
const messageDiv = document.createElement('div');
messageDiv.className = `markitect-message markitect-message-${type}`;
messageDiv.textContent = message;
// Style the message
const styles = {
'success': { bg: '#4caf50', color: 'white' },
'error': { bg: '#f44336', color: 'white' },
'info': { bg: '#2196f3', color: 'white' }
};
const style = styles[type] || styles.info;
messageDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${style.bg};
color: ${style.color};
padding: 12px 20px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
z-index: 10000;
font-size: 14px;
max-width: 300px;
animation: slideIn 0.3s ease;
`;
// Add animation
const styleSheet = document.createElement('style');
styleSheet.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`;
document.head.appendChild(styleSheet);
document.body.appendChild(messageDiv);
// Remove after 3 seconds
setTimeout(() => {
if (messageDiv.parentNode) {
messageDiv.style.animation = 'slideIn 0.3s ease reverse';
setTimeout(() => {
if (messageDiv.parentNode) {
messageDiv.parentNode.removeChild(messageDiv);
}
}, 300);
}
}, 3000);
}
/**
* Add control panel to the page
*/
addControlPanel() {
const panel = document.createElement('div');
panel.className = 'markitect-control-panel-clean';
panel.innerHTML = `
<div class="control-header">
<h3>Document Editor</h3>
<div class="control-status" id="clean-status">Ready</div>
</div>
<div class="control-actions">
<button id="clean-save" class="control-btn primary">Save & Download</button>
<button id="clean-reset" class="control-btn secondary">Reset All</button>
<button id="clean-status-btn" class="control-btn">Show Status</button>
</div>
`;
// Style the panel
panel.style.cssText = `
position: fixed;
top: 20px;
left: 20px;
background: white;
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
z-index: 1000;
font-family: system-ui, -apple-system, sans-serif;
min-width: 200px;
`;
// Add styles for buttons
const styles = document.createElement('style');
styles.textContent = `
.control-header h3 { margin: 0 0 8px 0; font-size: 16px; }
.control-status { font-size: 12px; color: #666; }
.control-actions { margin-top: 12px; }
.control-btn {
display: block;
width: 100%;
margin: 4px 0;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
font-size: 14px;
}
.control-btn.primary { background: #2196f3; color: white; border-color: #2196f3; }
.control-btn.secondary { background: #ff9800; color: white; border-color: #ff9800; }
.control-btn:hover { opacity: 0.9; }
`;
document.head.appendChild(styles);
document.body.appendChild(panel);
// Add event listeners
panel.querySelector('#clean-save').addEventListener('click', () => this.saveDocument());
panel.querySelector('#clean-reset').addEventListener('click', () => this.resetAllSections());
panel.querySelector('#clean-status-btn').addEventListener('click', () => this.showStatusDialog());
// Update status periodically
setInterval(() => {
const status = this.getDocumentStatus();
const statusEl = panel.querySelector('#clean-status');
if (status.hasUnsavedChanges) {
statusEl.textContent = `${status.modifiedSections} sections modified`;
statusEl.style.color = '#ff9800';
} else {
statusEl.textContent = 'All changes saved';
statusEl.style.color = '#4caf50';
}
}, 1000);
}
/**
* Show status dialog
*/
showStatusDialog() {
const status = this.getDocumentStatus();
const message = `
Document Status:
• Total sections: ${status.totalSections}
• Modified sections: ${status.modifiedSections}
• Editing sections: ${status.editingSections}
• Saved sections: ${status.savedSections}
• Has unsaved changes: ${status.hasUnsavedChanges ? 'Yes' : 'No'}
`.trim();
alert(message);
}
/**
* Destroy the editor and clean up
*/
destroy() {
if (this.domRenderer) {
this.domRenderer.destroy();
}
// Remove control panel
const panel = document.querySelector('.markitect-control-panel-clean');
if (panel) {
panel.remove();
}
}
}
// CSS for the clean editor
const CLEAN_EDITOR_CSS = `
/* Clean Section Editor Styles */
.markitect-section-editable {
margin: 16px 0;
padding: 12px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
border: 2px solid transparent;
}
.markitect-section-editable:hover {
background-color: rgba(33, 150, 243, 0.05);
border-color: rgba(33, 150, 243, 0.2);
}
.markitect-section-editable.section-editing {
background-color: rgba(33, 150, 243, 0.1);
border-color: #2196f3;
}
.markitect-section-editable.section-modified {
background-color: rgba(255, 235, 59, 0.1);
border-left: 4px solid #ffc107;
}
.markitect-section-editable.section-saved {
background-color: rgba(76, 175, 80, 0.1);
border-left: 4px solid #4caf50;
}
/* Edit container layout */
.markitect-edit-container {
display: flex;
gap: 12px;
align-items: flex-start;
width: 100%;
}
.markitect-textarea-wrapper {
flex: 1;
min-width: 0;
}
.edit-mode {
width: 100%;
min-height: 60px;
max-height: 360px;
font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace;
border: 2px solid #007acc;
border-radius: 6px;
padding: 12px;
font-size: 14px;
line-height: 1.5;
resize: vertical;
overflow: auto;
box-sizing: border-box;
transition: height 0.15s ease;
background: white;
}
.edit-mode:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);
}
/* Section controls */
.markitect-section-controls {
display: flex;
flex-direction: column;
gap: 6px;
padding: 8px;
background: rgba(255, 255, 255, 0.95);
border: 1px solid #e0e0e0;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
min-width: 80px;
flex-shrink: 0;
}
.markitect-section-btn {
padding: 8px 12px;
border: none;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
white-space: nowrap;
min-height: 32px;
}
.markitect-section-btn.accept {
background: #4caf50;
color: white;
}
.markitect-section-btn.accept:hover {
background: #45a049;
}
.markitect-section-btn.cancel {
background: #f44336;
color: white;
}
.markitect-section-btn.cancel:hover {
background: #da190b;
}
.markitect-section-btn.reset {
background: #ff9800;
color: white;
}
.markitect-section-btn.reset:hover {
background: #f57c00;
}
/* Responsive design */
@media (max-width: 768px) {
.markitect-edit-container {
flex-direction: column;
gap: 8px;
}
.markitect-section-controls {
flex-direction: row;
justify-content: center;
min-width: auto;
}
.edit-mode {
min-width: 100%;
}
}
@media (max-width: 480px) {
.markitect-section-controls {
flex-wrap: wrap;
gap: 4px;
}
.markitect-section-btn {
flex: 1;
min-width: 70px;
}
}
`;
// Auto-inject CSS when loaded
if (typeof document !== 'undefined') {
const style = document.createElement('style');
style.textContent = CLEAN_EDITOR_CSS;
document.head.appendChild(style);
}
// Export for use
if (typeof module !== 'undefined' && module.exports) {
module.exports = { MarkitectCleanEditor };
} else {
window.MarkitectEditor = window.MarkitectEditor || {};
window.MarkitectEditor.MarkitectCleanEditor = MarkitectCleanEditor;
}