Added comprehensive plugin system for independent JavaScript UI development: **Plugin Infrastructure:** - Extended existing MarkiTect plugin system with RenderingEnginePlugin base class - Added RENDERING plugin type to PluginType enum - Created RenderingConfig for asset management and deployment - Implemented RenderingEngineManager for plugin discovery and lifecycle **TestDrive JSUI Plugin:** - Extracted JavaScript UI components to independent testdrive-jsui plugin - Created standalone development environment (no Python required) - Implemented compass-positioned control panels (NW, NE, E, SE) - Added clean JSON configuration interface for Python↔JavaScript data transfer **Asset Management:** - Development mode: serve assets directly from plugin source directory - Production mode: deploy to _markitect/plugins/[plugin-name]/ structure - Configurable asset URLs and deployment strategies - Support for external dependencies (CDN resources) **Standalone Development:** - testdrive-jsui/test.html for browser-based development - Package.json with npm scripts for development server - Complete separation of JavaScript development from Python environment - Hot reload and standard web development workflow **Integration Demo:** - demo_plugin_integration.py showcasing all plugin capabilities - Standalone, plugin discovery, production deployment examples - Asset URL generation for different deployment modes This enables JavaScript-first development while maintaining clean integration with the MarkiTect Python ecosystem. Developers can now work on UI components independently using standard web development tools and workflows. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
279 lines
7.8 KiB
JavaScript
279 lines
7.8 KiB
JavaScript
/**
|
|
* DocumentControls Component
|
|
*
|
|
* Extracted from monolithic editor.js as part of architecture refactoring.
|
|
* Handles the floating control panel and document-level actions.
|
|
*
|
|
* Dependencies:
|
|
* - None (standalone component)
|
|
*/
|
|
|
|
/**
|
|
* DocumentControls - Manages the floating control panel and its buttons
|
|
*/
|
|
class DocumentControls {
|
|
constructor() {
|
|
this.controlPanel = null;
|
|
this.buttons = new Map();
|
|
this.eventHandlers = new Map();
|
|
this.isVisible = true;
|
|
}
|
|
|
|
/**
|
|
* Create the control panel and add it to the DOM
|
|
*/
|
|
create() {
|
|
if (this.controlPanel) {
|
|
this.destroy(); // Remove existing panel
|
|
}
|
|
|
|
// Also remove any existing panel with the same ID in the DOM
|
|
const existingPanel = document.getElementById('markitect-global-controls');
|
|
if (existingPanel && existingPanel.parentNode) {
|
|
existingPanel.parentNode.removeChild(existingPanel);
|
|
}
|
|
|
|
// Create the floating control panel
|
|
this.controlPanel = document.createElement('div');
|
|
this.controlPanel.id = 'markitect-global-controls';
|
|
this.controlPanel.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: rgba(248, 249, 250, 0.95);
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
z-index: 1000;
|
|
backdrop-filter: blur(8px);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
font-size: 14px;
|
|
min-width: 200px;
|
|
`;
|
|
|
|
// Add title
|
|
const title = document.createElement('div');
|
|
title.style.cssText = `
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
color: #495057;
|
|
border-bottom: 1px solid #dee2e6;
|
|
padding-bottom: 4px;
|
|
`;
|
|
title.textContent = 'Document Controls';
|
|
|
|
// Create button container
|
|
const buttonContainer = document.createElement('div');
|
|
buttonContainer.id = 'button-container';
|
|
buttonContainer.style.cssText = `
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
`;
|
|
|
|
this.controlPanel.appendChild(title);
|
|
this.controlPanel.appendChild(buttonContainer);
|
|
|
|
// Add default buttons
|
|
this.addDefaultButtons();
|
|
|
|
// Add debug messages container
|
|
this.addDebugContainer();
|
|
|
|
// Add to DOM
|
|
document.body.appendChild(this.controlPanel);
|
|
}
|
|
|
|
/**
|
|
* Add default buttons to the control panel
|
|
*/
|
|
addDefaultButtons() {
|
|
// Save Document button
|
|
this.addButton('save-document', '💾 Save Document', '#28a745');
|
|
|
|
// Reset All button
|
|
this.addButton('reset-all', '🔄 Reset All', '#ffc107', '#212529');
|
|
|
|
// Show Status button
|
|
this.addButton('show-status', '📊 Show Status', '#17a2b8');
|
|
|
|
// Debug button
|
|
this.addButton('toggle-debug', '🔍 Debug', '#6c757d');
|
|
}
|
|
|
|
/**
|
|
* Add debug container to the control panel
|
|
*/
|
|
addDebugContainer() {
|
|
const debugContainer = document.createElement('div');
|
|
debugContainer.id = 'debug-messages-container';
|
|
debugContainer.style.cssText = `
|
|
margin-top: 12px;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 4px;
|
|
background: #f8f9fa;
|
|
padding: 8px;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 12px;
|
|
line-height: 1.4;
|
|
display: none;
|
|
`;
|
|
|
|
this.controlPanel.appendChild(debugContainer);
|
|
}
|
|
|
|
/**
|
|
* Add a button to the control panel
|
|
*/
|
|
addButton(id, text, backgroundColor, textColor = 'white') {
|
|
const buttonContainer = this.controlPanel.querySelector('#button-container');
|
|
if (!buttonContainer) {
|
|
throw new Error('Button container not found. Call create() first.');
|
|
}
|
|
|
|
const button = document.createElement('button');
|
|
button.id = id;
|
|
button.textContent = text;
|
|
button.style.cssText = `
|
|
background: ${backgroundColor};
|
|
color: ${textColor};
|
|
border: none;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
transition: background-color 0.2s;
|
|
`;
|
|
|
|
buttonContainer.appendChild(button);
|
|
this.buttons.set(id, button);
|
|
|
|
return button;
|
|
}
|
|
|
|
/**
|
|
* Remove a button from the control panel
|
|
*/
|
|
removeButton(id) {
|
|
const button = this.buttons.get(id);
|
|
if (button && button.parentNode) {
|
|
button.parentNode.removeChild(button);
|
|
this.buttons.delete(id);
|
|
this.eventHandlers.delete(id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set event handlers for buttons
|
|
*/
|
|
setEventHandlers(handlers) {
|
|
for (const [buttonId, handler] of Object.entries(handlers)) {
|
|
const button = this.buttons.get(buttonId);
|
|
if (button) {
|
|
// Remove existing handler if any
|
|
if (this.eventHandlers.has(buttonId)) {
|
|
button.removeEventListener('click', this.eventHandlers.get(buttonId));
|
|
}
|
|
|
|
// Add new handler
|
|
button.addEventListener('click', handler);
|
|
this.eventHandlers.set(buttonId, handler);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show the control panel
|
|
*/
|
|
show() {
|
|
if (this.controlPanel) {
|
|
this.controlPanel.style.display = 'block';
|
|
this.isVisible = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hide the control panel
|
|
*/
|
|
hide() {
|
|
if (this.controlPanel) {
|
|
this.controlPanel.style.display = 'none';
|
|
this.isVisible = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update status display (can be extended as needed)
|
|
*/
|
|
updateStatus(status) {
|
|
// This method can be extended to show status information
|
|
// For now, it just stores the status for potential display
|
|
this.lastStatus = status;
|
|
|
|
// Could update a status indicator in the panel if needed
|
|
if (status && this.controlPanel) {
|
|
const title = this.controlPanel.querySelector('div');
|
|
if (title) {
|
|
const statusText = `Document Controls (${status.totalSections} sections, ${status.editingSections} editing)`;
|
|
// Could update title or add status indicator
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the control panel element
|
|
*/
|
|
getControlPanel() {
|
|
return this.controlPanel;
|
|
}
|
|
|
|
/**
|
|
* Destroy the control panel and clean up
|
|
*/
|
|
destroy() {
|
|
if (this.controlPanel && this.controlPanel.parentNode) {
|
|
this.controlPanel.parentNode.removeChild(this.controlPanel);
|
|
}
|
|
|
|
// Clean up references
|
|
this.controlPanel = null;
|
|
this.buttons.clear();
|
|
this.eventHandlers.clear();
|
|
this.isVisible = true;
|
|
}
|
|
|
|
/**
|
|
* Check if the control panel is visible
|
|
*/
|
|
isVisible() {
|
|
return this.isVisible && this.controlPanel && this.controlPanel.style.display !== 'none';
|
|
}
|
|
|
|
/**
|
|
* Get all button IDs
|
|
*/
|
|
getButtonIds() {
|
|
return Array.from(this.buttons.keys());
|
|
}
|
|
|
|
/**
|
|
* Get a specific button by ID
|
|
*/
|
|
getButton(id) {
|
|
return this.buttons.get(id);
|
|
}
|
|
}
|
|
|
|
// Export for use in tests and other modules
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = { DocumentControls };
|
|
}
|
|
|
|
// Export for browser use
|
|
if (typeof window !== 'undefined') {
|
|
window.DocumentControls = DocumentControls;
|
|
} |