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
- 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>
517 lines
15 KiB
JavaScript
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;
|
|
} |