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>
616 lines
27 KiB
JavaScript
616 lines
27 KiB
JavaScript
/**
|
|
* Status Control - Document statistics and change tracking
|
|
*/
|
|
class StatusControl {
|
|
constructor() {
|
|
this.control = Object.create(Control);
|
|
|
|
// Configure for status functionality
|
|
this.control.config = {
|
|
icon: '📊',
|
|
title: 'Status',
|
|
className: 'status-control',
|
|
defaultContent: 'Document statistics and changes',
|
|
ariaLabel: 'Status Control',
|
|
position: 'e', // East positioning
|
|
footer: `Updated ${new Date().toLocaleTimeString()}`
|
|
};
|
|
|
|
// Initialize change tracking
|
|
this.control.changeTracking = {
|
|
headings: new Set(),
|
|
sections: new Set(),
|
|
images: new Set(),
|
|
tables: new Set(),
|
|
lastScanTime: null,
|
|
initialCounts: {
|
|
headings: 0,
|
|
sections: 0,
|
|
images: 0,
|
|
tables: 0,
|
|
lines: 0,
|
|
words: 0,
|
|
characters: 0
|
|
}
|
|
};
|
|
|
|
this.bindMethods();
|
|
}
|
|
|
|
bindMethods() {
|
|
// Bind utility functions
|
|
this.control.safeTextExtraction = this.safeTextExtraction.bind(this);
|
|
this.control.sanitizeText = this.sanitizeText.bind(this);
|
|
this.control.validateElement = this.validateElement.bind(this);
|
|
this.control.safeStatsOperation = this.safeStatsOperation.bind(this);
|
|
|
|
// Bind existing methods
|
|
this.control.calculateStats = this.calculateStats.bind(this);
|
|
this.control.isContentSection = this.isContentSection.bind(this);
|
|
this.control.isContentTable = this.isContentTable.bind(this);
|
|
this.control.updateChangeTracking = this.updateChangeTracking.bind(this);
|
|
this.control.buildContent = this.buildContent.bind(this);
|
|
this.control.refreshStats = this.refreshStats.bind(this);
|
|
this.control.resetChangeTracking = this.resetChangeTracking.bind(this);
|
|
this.control.setupAutoRefresh = this.setupAutoRefresh.bind(this);
|
|
|
|
// Override collapse to clean up intervals
|
|
const originalCollapse = this.control.collapse;
|
|
this.control.collapse = () => {
|
|
if (this.control.autoRefreshInterval) {
|
|
clearInterval(this.control.autoRefreshInterval);
|
|
this.control.autoRefreshInterval = null;
|
|
}
|
|
originalCollapse.call(this.control);
|
|
};
|
|
}
|
|
|
|
// Utility functions for safe operations
|
|
safeTextExtraction(element) {
|
|
if (!this.validateElement(element)) {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
const text = element.textContent || element.innerText || '';
|
|
return this.sanitizeText(text.trim());
|
|
} catch (error) {
|
|
console.warn('Text extraction failed:', error);
|
|
return '';
|
|
}
|
|
}
|
|
|
|
sanitizeText(text) {
|
|
if (typeof text !== 'string') {
|
|
return '';
|
|
}
|
|
|
|
// Remove potentially harmful characters and limit length
|
|
const maxLength = 100000; // 100KB text limit
|
|
const sanitized = text
|
|
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '') // Remove control chars
|
|
.slice(0, maxLength); // Limit length
|
|
|
|
return sanitized;
|
|
}
|
|
|
|
validateElement(element) {
|
|
return element &&
|
|
element.nodeType === Node.ELEMENT_NODE &&
|
|
element.isConnected &&
|
|
!element.closest('.control-panel'); // Avoid control elements
|
|
}
|
|
|
|
safeStatsOperation(operation, fallback = 0, context = 'stats') {
|
|
try {
|
|
const result = operation();
|
|
// Validate numeric results
|
|
return typeof result === 'number' && isFinite(result) ? result : fallback;
|
|
} catch (error) {
|
|
console.warn(`Stats operation failed in ${context}:`, error);
|
|
if (window.MarkitectDebugSystem) {
|
|
window.MarkitectDebugSystem.addMessage(
|
|
`Stats operation failed: ${error.message}`,
|
|
'WARNING',
|
|
'StatusControl',
|
|
{ context, eventType: 'ERROR' }
|
|
);
|
|
}
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
calculateStats() {
|
|
const stats = {
|
|
headings: { total: 0, changed: 0 },
|
|
sections: { total: 0, changed: 0 },
|
|
images: { total: 0, changed: 0 },
|
|
tables: { total: 0, changed: 0 },
|
|
document: { lines: 0, words: 0, characters: 0 },
|
|
sections_detail: { lines: 0, words: 0, characters: 0 },
|
|
tables_detail: { lines: 0, words: 0, characters: 0 }
|
|
};
|
|
|
|
return this.safeStatsOperation(() => {
|
|
// Count headings (h1-h6, excluding control titles)
|
|
const headings = this.control.safeQuerySelectorAll('h1, h2, h3, h4, h5, h6');
|
|
const maxElements = 10000; // Limit processing to prevent DoS
|
|
|
|
headings.slice(0, maxElements).forEach(heading => {
|
|
if (!this.validateElement(heading)) return;
|
|
|
|
const text = this.safeTextExtraction(heading).toLowerCase();
|
|
// Skip control headings with enhanced filtering
|
|
const controlKeywords = ['contents', 'debug', 'status', 'control', 'menu', 'toolbar'];
|
|
const isControlHeading = controlKeywords.some(keyword => text.includes(keyword));
|
|
|
|
if (text.length > 0 && !isControlHeading) {
|
|
stats.headings.total++;
|
|
const fullText = this.safeTextExtraction(heading);
|
|
if (this.control.changeTracking.headings.has(fullText)) {
|
|
stats.headings.changed++;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Count sections (content blocks excluding headings and table cells)
|
|
const sections = this.control.safeQuerySelectorAll('p, blockquote, pre, li, div');
|
|
sections.slice(0, maxElements).forEach(section => {
|
|
if (this.isContentSection(section)) {
|
|
stats.sections.total++;
|
|
const sectionText = this.safeTextExtraction(section);
|
|
if (sectionText.length > 0) {
|
|
const lines = this.safeStatsOperation(() => sectionText.split('\n').length, 0, 'countLines');
|
|
const words = this.safeStatsOperation(() =>
|
|
sectionText.split(/\s+/).filter(w => w.length > 0).length, 0, 'countWords');
|
|
const characters = Math.min(sectionText.length, 1000000); // Cap at 1MB
|
|
|
|
stats.sections_detail.lines += lines;
|
|
stats.sections_detail.words += words;
|
|
stats.sections_detail.characters += characters;
|
|
|
|
if (this.control.changeTracking.sections.has(sectionText)) {
|
|
stats.sections.changed++;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Count tables as separate entities
|
|
const tables = this.control.safeQuerySelectorAll('table');
|
|
tables.slice(0, maxElements).forEach(table => {
|
|
if (this.isContentTable(table)) {
|
|
stats.tables.total++;
|
|
const tableText = this.safeTextExtraction(table);
|
|
if (tableText.length > 0) {
|
|
const lines = this.safeStatsOperation(() => tableText.split('\n').length, 0, 'countTableLines');
|
|
const words = this.safeStatsOperation(() =>
|
|
tableText.split(/\s+/).filter(w => w.length > 0).length, 0, 'countTableWords');
|
|
const characters = Math.min(tableText.length, 1000000); // Cap at 1MB
|
|
|
|
stats.tables_detail.lines += lines;
|
|
stats.tables_detail.words += words;
|
|
stats.tables_detail.characters += characters;
|
|
|
|
// Generate safer table identifier
|
|
const tableId = this.sanitizeText(table.id ||
|
|
table.outerHTML.substring(0, 100).replace(/[<>"'&]/g, ''));
|
|
if (this.control.changeTracking.tables.has(tableId)) {
|
|
stats.tables.changed++;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Count images with validation
|
|
const images = this.control.safeQuerySelectorAll('img');
|
|
images.slice(0, maxElements).forEach(img => {
|
|
if (this.validateElement(img)) {
|
|
stats.images.total++;
|
|
// Safely extract and validate image source
|
|
const imgSrc = this.sanitizeText(img.src || img.getAttribute('src') || '');
|
|
if (imgSrc && this.control.changeTracking.images.has(imgSrc)) {
|
|
stats.images.changed++;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Calculate total document stats with protection
|
|
const bodyText = this.safeTextExtraction(document.body);
|
|
if (bodyText) {
|
|
const cleanText = bodyText.replace(/\s+/g, ' ');
|
|
stats.document.lines = this.safeStatsOperation(() =>
|
|
bodyText.split('\n').length, 0, 'countDocLines');
|
|
stats.document.words = this.safeStatsOperation(() =>
|
|
cleanText.split(/\s+/).filter(w => w.length > 0).length, 0, 'countDocWords');
|
|
stats.document.characters = Math.min(cleanText.length, 10000000); // Cap at 10MB
|
|
}
|
|
|
|
return stats;
|
|
}, stats, 'calculateStats');
|
|
}
|
|
|
|
isContentSection(element) {
|
|
return this.safeStatsOperation(() => {
|
|
if (!this.validateElement(element)) {
|
|
return false;
|
|
}
|
|
|
|
// Enhanced control detection with timeout protection
|
|
let current = element;
|
|
let depth = 0;
|
|
const maxDepth = 50; // Prevent infinite loops
|
|
|
|
while (current && current !== document.body && depth < maxDepth) {
|
|
if (current.classList && (
|
|
current.classList.contains('control-panel') ||
|
|
current.classList.contains('control-content') ||
|
|
current.classList.contains('control-header') ||
|
|
current.className.includes('control') ||
|
|
current.id?.includes('control')
|
|
)) {
|
|
return false;
|
|
}
|
|
current = current.parentElement;
|
|
depth++;
|
|
}
|
|
|
|
// Skip if element is inside a table (tables are counted separately)
|
|
if (element.closest && element.closest('table')) {
|
|
return false;
|
|
}
|
|
|
|
// Skip if element has no meaningful text content
|
|
const text = this.safeTextExtraction(element);
|
|
return text.length > 0 && text.length < 50000; // Reasonable size limit
|
|
}, false, 'isContentSection');
|
|
}
|
|
|
|
isContentTable(table) {
|
|
return this.safeStatsOperation(() => {
|
|
if (!this.validateElement(table) || table.tagName !== 'TABLE') {
|
|
return false;
|
|
}
|
|
|
|
// Enhanced control detection with depth limiting
|
|
let current = table;
|
|
let depth = 0;
|
|
const maxDepth = 50;
|
|
|
|
while (current && current !== document.body && depth < maxDepth) {
|
|
if (current.classList && (
|
|
current.classList.contains('control-panel') ||
|
|
current.classList.contains('control-content') ||
|
|
current.classList.contains('control-header') ||
|
|
current.className.includes('control') ||
|
|
current.id?.includes('control')
|
|
)) {
|
|
return false;
|
|
}
|
|
current = current.parentElement;
|
|
depth++;
|
|
}
|
|
|
|
// Check if table has meaningful content with limits
|
|
const text = this.safeTextExtraction(table);
|
|
return text.length > 0 && text.length < 100000; // Reasonable table size limit
|
|
}, false, 'isContentTable');
|
|
}
|
|
|
|
updateChangeTracking() {
|
|
const now = Date.now();
|
|
|
|
// Headings
|
|
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
|
headings.forEach(heading => {
|
|
const text = heading.textContent.trim();
|
|
if (text && !text.toLowerCase().includes('control')) {
|
|
const changed = heading.dataset.lastModified &&
|
|
(now - parseInt(heading.dataset.lastModified)) < 300000; // 5 minutes
|
|
if (changed) {
|
|
this.control.changeTracking.headings.add(text);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Sections
|
|
const sections = document.querySelectorAll('p, blockquote, pre, li, div');
|
|
sections.forEach(section => {
|
|
if (this.isContentSection(section)) {
|
|
const text = section.textContent.trim();
|
|
if (text.length > 0) {
|
|
const changed = section.dataset.lastModified &&
|
|
(now - parseInt(section.dataset.lastModified)) < 300000; // 5 minutes
|
|
if (changed) {
|
|
this.control.changeTracking.sections.add(text);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Tables
|
|
const tables = document.querySelectorAll('table');
|
|
tables.forEach(table => {
|
|
if (this.isContentTable(table)) {
|
|
const tableId = table.id || table.outerHTML.substring(0, 100);
|
|
const changed = table.dataset.lastModified &&
|
|
(now - parseInt(table.dataset.lastModified)) < 300000; // 5 minutes
|
|
if (changed) {
|
|
this.control.changeTracking.tables.add(tableId);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Images
|
|
const images = document.querySelectorAll('img');
|
|
images.forEach(img => {
|
|
const src = img.src || img.getAttribute('src') || '';
|
|
const changed = img.dataset.lastModified &&
|
|
(now - parseInt(img.dataset.lastModified)) < 300000; // 5 minutes
|
|
if (changed && src) {
|
|
this.control.changeTracking.images.add(src);
|
|
}
|
|
});
|
|
|
|
this.control.changeTracking.lastScanTime = now;
|
|
}
|
|
|
|
buildContent() {
|
|
this.control.safeOperation(() => {
|
|
console.log("📊 Building status control content...");
|
|
|
|
const content = this.control.safeQuerySelector('.control-content', this.control.element);
|
|
if (!content) {
|
|
console.error("📊 Status control content element not found");
|
|
return;
|
|
}
|
|
|
|
// Update tracking and calculate stats with timeout protection
|
|
const timeout = setTimeout(() => {
|
|
console.warn('Status content build operation timed out');
|
|
}, 10000); // 10 second timeout
|
|
|
|
this.updateChangeTracking();
|
|
const stats = this.calculateStats();
|
|
|
|
clearTimeout(timeout);
|
|
|
|
// Sanitize numeric values to prevent injection
|
|
const safeStats = {
|
|
document: {
|
|
lines: Math.max(0, Math.floor(stats.document.lines || 0)),
|
|
words: Math.max(0, Math.floor(stats.document.words || 0)),
|
|
characters: Math.max(0, Math.floor(stats.document.characters || 0))
|
|
},
|
|
headings: {
|
|
total: Math.max(0, Math.floor(stats.headings.total || 0)),
|
|
changed: Math.max(0, Math.floor(stats.headings.changed || 0))
|
|
},
|
|
sections: {
|
|
total: Math.max(0, Math.floor(stats.sections.total || 0)),
|
|
changed: Math.max(0, Math.floor(stats.sections.changed || 0))
|
|
},
|
|
sections_detail: {
|
|
lines: Math.max(0, Math.floor(stats.sections_detail.lines || 0)),
|
|
words: Math.max(0, Math.floor(stats.sections_detail.words || 0)),
|
|
characters: Math.max(0, Math.floor(stats.sections_detail.characters || 0))
|
|
},
|
|
tables: {
|
|
total: Math.max(0, Math.floor(stats.tables.total || 0)),
|
|
changed: Math.max(0, Math.floor(stats.tables.changed || 0))
|
|
},
|
|
tables_detail: {
|
|
lines: Math.max(0, Math.floor(stats.tables_detail.lines || 0)),
|
|
words: Math.max(0, Math.floor(stats.tables_detail.words || 0)),
|
|
characters: Math.max(0, Math.floor(stats.tables_detail.characters || 0))
|
|
},
|
|
images: {
|
|
total: Math.max(0, Math.floor(stats.images.total || 0)),
|
|
changed: Math.max(0, Math.floor(stats.images.changed || 0))
|
|
}
|
|
};
|
|
|
|
// Use safe stats for display with proper escaping
|
|
content.innerHTML = `
|
|
<div style="font-size: 0.8rem; line-height: 1.4; color: #495057;">
|
|
<!-- Document Overview -->
|
|
<div style="margin-bottom: 0.75rem; padding: 0.5rem; background: #f8f9fa; border-radius: 4px;">
|
|
<div style="font-weight: 600; color: #212529; margin-bottom: 0.5rem;">📄 Document</div>
|
|
<div style="font-size: 0.7rem;">
|
|
<span>Lines: <strong>${safeStats.document.lines.toLocaleString()}</strong> | Words: <strong>${safeStats.document.words.toLocaleString()}</strong> | Chars: <strong>${safeStats.document.characters.toLocaleString()}</strong></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Headings -->
|
|
<div style="margin-bottom: 0.75rem; padding: 0.5rem; background: #f3e5f5; border-radius: 4px;">
|
|
<div style="font-weight: 600; color: #7b1fa2;">
|
|
📋 Headings: <strong>${safeStats.headings.total}</strong>
|
|
${safeStats.headings.changed > 0 ? `<span style="color: #28a745; font-size: 0.6rem;"> (+${safeStats.headings.changed})</span>` : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sections -->
|
|
<div style="margin-bottom: 0.75rem; padding: 0.5rem; background: #e3f2fd; border-radius: 4px;">
|
|
<div style="font-weight: 600; color: #1565c0; margin-bottom: 0.5rem;">
|
|
📄 Sections: <strong>${safeStats.sections.total}</strong>
|
|
${safeStats.sections.changed > 0 ? `<span style="color: #28a745; font-size: 0.6rem;"> (+${safeStats.sections.changed})</span>` : ''}
|
|
</div>
|
|
<div style="font-size: 0.7rem;">
|
|
<span>Lines: <strong>${safeStats.sections_detail.lines.toLocaleString()}</strong> | Words: <strong>${safeStats.sections_detail.words.toLocaleString()}</strong> | Chars: <strong>${safeStats.sections_detail.characters.toLocaleString()}</strong></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tables -->
|
|
<div style="margin-bottom: 0.75rem; padding: 0.5rem; background: #fff3e0; border-radius: 4px;">
|
|
<div style="font-weight: 600; color: #ef6c00; margin-bottom: 0.5rem;">
|
|
🗂️ Tables: <strong>${safeStats.tables.total}</strong>
|
|
${safeStats.tables.changed > 0 ? `<span style="color: #28a745; font-size: 0.6rem;"> (+${safeStats.tables.changed})</span>` : ''}
|
|
</div>
|
|
<div style="font-size: 0.7rem;">
|
|
<span>Lines: <strong>${safeStats.tables_detail.lines.toLocaleString()}</strong> | Words: <strong>${safeStats.tables_detail.words.toLocaleString()}</strong> | Chars: <strong>${safeStats.tables_detail.characters.toLocaleString()}</strong></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Images -->
|
|
<div style="margin-bottom: 0.75rem; padding: 0.5rem; background: #e8f5e8; border-radius: 4px;">
|
|
<div style="font-weight: 600; color: #2e7d32;">
|
|
🖼️ Images: <strong>${safeStats.images.total}</strong>
|
|
${safeStats.images.changed > 0 ? `<span style="color: #28a745; font-size: 0.6rem;"> (+${safeStats.images.changed})</span>` : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions with safer onclick handlers -->
|
|
<div style="display: flex; gap: 0.25rem; margin-top: 0.5rem;">
|
|
<button id="status-refresh-btn"
|
|
style="flex: 1; padding: 0.4rem; background: #6c757d; color: white;
|
|
border: none; border-radius: 3px; cursor: pointer; font-size: 0.7rem;">
|
|
🔄 Refresh
|
|
</button>
|
|
<button id="status-reset-btn"
|
|
style="flex: 1; padding: 0.4rem; background: #dc3545; color: white;
|
|
border: none; border-radius: 3px; cursor: pointer; font-size: 0.7rem;">
|
|
🔄 Reset Changes
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
`;
|
|
|
|
// Add safer event listeners instead of inline onclick
|
|
const refreshBtn = content.querySelector('#status-refresh-btn');
|
|
const resetBtn = content.querySelector('#status-reset-btn');
|
|
|
|
if (refreshBtn) {
|
|
refreshBtn.addEventListener('click', () => {
|
|
this.control.safeOperation(() => {
|
|
if (window.statusControl && window.statusControl.refreshStats) {
|
|
window.statusControl.refreshStats();
|
|
}
|
|
}, null, 'refreshButton');
|
|
});
|
|
}
|
|
|
|
if (resetBtn) {
|
|
resetBtn.addEventListener('click', () => {
|
|
this.control.safeOperation(() => {
|
|
if (window.statusControl && window.statusControl.resetChangeTracking) {
|
|
window.statusControl.resetChangeTracking();
|
|
}
|
|
}, null, 'resetButton');
|
|
});
|
|
}
|
|
|
|
console.log("📊 Status control content built successfully");
|
|
|
|
// Set up auto-refresh
|
|
this.setupAutoRefresh();
|
|
|
|
// Show panel and expand
|
|
this.control.expand();
|
|
|
|
}, () => {
|
|
console.error("📊 Error in buildContent: Failed to build status control content");
|
|
const content = this.control.safeQuerySelector('.control-content', this.control.element);
|
|
if (content) {
|
|
content.innerHTML = '<div style="color: #dc3545;">Status loading failed</div>';
|
|
}
|
|
}, 'buildContent');
|
|
}
|
|
|
|
refreshStats() {
|
|
if (this.control.isExpanded) {
|
|
this.updateChangeTracking();
|
|
// Update footer timestamp
|
|
this.control.config.footer = `Updated ${new Date().toLocaleTimeString()}`;
|
|
this.control.styleFooter();
|
|
|
|
const content = this.control.element.querySelector('.control-content');
|
|
if (content) {
|
|
const stats = this.calculateStats();
|
|
// Update the display without rebuilding entire content
|
|
this.buildContent();
|
|
}
|
|
}
|
|
}
|
|
|
|
resetChangeTracking() {
|
|
if (confirm('Reset all document changes? This will revert all sections to their original state.')) {
|
|
console.log('📊 Resetting document changes...');
|
|
|
|
// Reset using available infrastructure
|
|
if (window.sectionManager && window.domRenderer) {
|
|
// Use the proper document management infrastructure
|
|
try {
|
|
// Hide any open editors
|
|
window.domRenderer.hideCurrentEditor();
|
|
|
|
// Reset all sections to original state
|
|
const allSections = Array.from(window.sectionManager.sections.values());
|
|
allSections.forEach(section => {
|
|
section.resetToOriginal();
|
|
});
|
|
|
|
// Re-render all sections
|
|
window.domRenderer.renderAllSections(allSections);
|
|
|
|
console.log('📊 Document reset successful');
|
|
|
|
// Add to debug system
|
|
if (window.MarkitectDebugSystem) {
|
|
window.MarkitectDebugSystem.addMessage(
|
|
`Document reset completed - ${allSections.length} sections restored`,
|
|
'SUCCESS',
|
|
'StatusControl',
|
|
{ eventType: 'SYSTEM' }
|
|
);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('📊 Document reset failed:', error);
|
|
|
|
if (window.MarkitectDebugSystem) {
|
|
window.MarkitectDebugSystem.addMessage(
|
|
`Document reset failed: ${error.message}`,
|
|
'ERROR',
|
|
'StatusControl',
|
|
{ eventType: 'SYSTEM' }
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// Fallback to page reload if infrastructure not available
|
|
console.log('📊 Document management infrastructure not available, using page reload');
|
|
window.location.reload();
|
|
}
|
|
|
|
// Clear our own change tracking
|
|
this.control.changeTracking.headings.clear();
|
|
this.control.changeTracking.sections.clear();
|
|
this.control.changeTracking.images.clear();
|
|
this.control.changeTracking.tables.clear();
|
|
this.control.changeTracking.lastScanTime = Date.now();
|
|
|
|
// Refresh our display
|
|
this.refreshStats();
|
|
}
|
|
}
|
|
|
|
setupAutoRefresh() {
|
|
if (this.control.autoRefreshInterval) {
|
|
clearInterval(this.control.autoRefreshInterval);
|
|
}
|
|
|
|
this.control.autoRefreshInterval = setInterval(() => {
|
|
if (this.control.isExpanded) {
|
|
this.refreshStats();
|
|
}
|
|
}, 30000); // 30 seconds
|
|
}
|
|
|
|
createControl() {
|
|
return this.control.createControl();
|
|
}
|
|
}
|
|
|
|
// Export for global access
|
|
window.StatusControl = StatusControl; |