Files
markdown_editor/static/js/macro-processor.js
Mahmoud-Emad 0ed6bcf1f2 refactor: Modularize UI components and utilities
- Extract UI components into separate JS files
- Centralize configuration values in config.js
- Introduce a dedicated logger module
- Improve file tree drag-and-drop and undo functionality
- Refactor modal handling to a single manager
- Add URL routing support for SPA navigation
- Implement view mode for read-only access
2025-10-26 15:42:15 +03:00

157 lines
4.9 KiB
JavaScript

/**
* Macro Processor
* Handles macro execution and result rendering
*/
class MacroProcessor {
constructor(webdavClient) {
this.webdavClient = webdavClient;
this.plugins = new Map();
this.includeStack = []; // Track includes to detect cycles
this.registerDefaultPlugins();
}
/**
* Register a macro plugin
* Plugin must implement: { canHandle(actor, method), process(macro, webdavClient) }
*/
registerPlugin(actor, method, plugin) {
const key = `${actor}.${method}`;
this.plugins.set(key, plugin);
}
/**
* Process all macros in content
* Returns { success: boolean, content: string, errors: [] }
*/
async processMacros(content) {
const macros = MacroParser.extractMacros(content);
const errors = [];
let processedContent = content;
// Process macros in reverse order to preserve positions
for (let i = macros.length - 1; i >= 0; i--) {
const macro = macros[i];
try {
const result = await this.processMacro(macro);
if (result.success) {
// Replace macro with result
processedContent =
processedContent.substring(0, macro.start) +
result.content +
processedContent.substring(macro.end);
} else {
errors.push({
macro: macro.fullMatch,
error: result.error
});
// Replace with error message
const errorMsg = `\n\n⚠️ **Macro Error**: ${result.error}\n\n`;
processedContent =
processedContent.substring(0, macro.start) +
errorMsg +
processedContent.substring(macro.end);
}
} catch (error) {
errors.push({
macro: macro.fullMatch,
error: error.message
});
const errorMsg = `\n\n⚠️ **Macro Error**: ${error.message}\n\n`;
processedContent =
processedContent.substring(0, macro.start) +
errorMsg +
processedContent.substring(macro.end);
}
}
return {
success: errors.length === 0,
content: processedContent,
errors
};
}
/**
* Process single macro
*/
async processMacro(macro) {
const key = `${macro.actor}.${macro.method}`;
const plugin = this.plugins.get(key);
// Check for circular includes
if (macro.method === 'include') {
const path = macro.params.path || macro.params[''];
if (this.includeStack.includes(path)) {
return {
success: false,
error: `Circular include detected: ${this.includeStack.join(' → ')}${path}`
};
}
}
if (!plugin) {
return {
success: false,
error: `Unknown macro: !!${key}`
};
}
// Validate macro
const validation = MacroParser.validateMacro(macro);
if (!validation.valid) {
return { success: false, error: validation.error };
}
// Execute plugin
try {
return await plugin.process(macro, this.webdavClient);
} catch (error) {
return {
success: false,
error: `Plugin error: ${error.message}`
};
}
}
/**
* Register default plugins
*/
registerDefaultPlugins() {
// Include plugin
this.registerPlugin('core', 'include', {
process: async (macro, webdavClient) => {
const path = macro.params.path || macro.params[''];
if (!path) {
return {
success: false,
error: 'include macro requires "path" parameter'
};
}
try {
// Add to include stack
this.includeStack.push(path);
const content = await webdavClient.includeFile(path);
// Remove from include stack
this.includeStack.pop();
return { success: true, content };
} catch (error) {
// Remove from include stack on error
this.includeStack.pop();
return {
success: false,
error: `Failed to include "${path}": ${error.message}`
};
}
}
});
}
}
window.MacroProcessor = MacroProcessor;