Skip to main content
Glama
element-manipulation-service.ts8.12 kB
// Elementor Element Manipulation Service - Find, update, delete, move elements import { logger } from '../../utils/logger.js'; import { ErrorHandler, MCPError, ErrorCategory } from '../../utils/error-handler.js'; import { ElementorElement, ElementorData, ElementorWidget } from '../../types/elementor.js'; export class ElementorManipulationService { /** * Find an element by ID in the Elementor data tree */ findElementById( elements: ElementorElement[], elementId: string ): { element: ElementorElement | null; parent: ElementorElement | null; index: number } { const search = ( els: ElementorElement[], parent: ElementorElement | null = null ): { element: ElementorElement | null; parent: ElementorElement | null; index: number } => { for (let i = 0; i < els.length; i++) { const el = els[i]; if (!el) continue; if (el.id === elementId) { return { element: el, parent, index: i }; } if (el.elements && el.elements.length > 0) { const found = search(el.elements, el); if (found.element) return found; } } return { element: null, parent: null, index: -1 }; }; return search(elements); } /** * Find all elements of a specific type */ findElementsByType(elements: ElementorElement[], elType: string): ElementorElement[] { const found: ElementorElement[] = []; const search = (els: ElementorElement[]) => { for (const el of els) { if (el.elType === elType) { found.push(el); } if (el.elements && el.elements.length > 0) { search(el.elements); } } }; search(elements); return found; } /** * Find widgets by widget type */ findWidgetsByType(elements: ElementorElement[], widgetType: string): ElementorWidget[] { const found: ElementorWidget[] = []; const search = (els: ElementorElement[]) => { for (const el of els) { if (el.elType === 'widget' && el.widgetType === widgetType) { found.push(el as ElementorWidget); } if (el.elements && el.elements.length > 0) { search(el.elements); } } }; search(elements); return found; } /** * Update element settings */ updateElement( elements: ElementorElement[], elementId: string, settings: Record<string, any> ): boolean { const { element } = this.findElementById(elements, elementId); if (!element) { logger.warn(`Element ${elementId} not found for update`); return false; } element.settings = { ...element.settings, ...settings }; logger.debug(`Updated element ${elementId}`, { settings }); return true; } /** * Delete an element */ deleteElement(elements: ElementorElement[], elementId: string): boolean { const { parent, index } = this.findElementById(elements, elementId); if (index === -1) { logger.warn(`Element ${elementId} not found for deletion`); return false; } if (parent && parent.elements) { parent.elements.splice(index, 1); } else { elements.splice(index, 1); } logger.info(`Deleted element ${elementId}`); return true; } /** * Clone an element */ cloneElement(element: ElementorElement): ElementorElement { const cloned = JSON.parse(JSON.stringify(element)); // Generate new IDs for all elements const regenerateIds = (el: ElementorElement) => { el.id = Math.random().toString(36).substring(2, 10); if (el.elements && el.elements.length > 0) { el.elements.forEach(regenerateIds); } }; regenerateIds(cloned); logger.debug(`Cloned element`, { originalId: element.id, newId: cloned.id }); return cloned; } /** * Move element to a new position */ moveElement( elements: ElementorElement[], elementId: string, targetParentId: string | null, position: number ): boolean { const { element, parent, index } = this.findElementById(elements, elementId); if (!element) { logger.warn(`Element ${elementId} not found for move`); return false; } // Remove from current location if (parent && parent.elements) { parent.elements.splice(index, 1); } else { elements.splice(index, 1); } // Add to new location if (targetParentId) { const { element: targetParent } = this.findElementById(elements, targetParentId); if (targetParent && targetParent.elements) { targetParent.elements.splice(position, 0, element); } else { logger.error(`Target parent ${targetParentId} not found`); return false; } } else { elements.splice(position, 0, element); } logger.info(`Moved element ${elementId} to position ${position}`); return true; } /** * Reorder elements */ reorderElements( elements: ElementorElement[], parentId: string | null, newOrder: string[] ): boolean { let targetElements: ElementorElement[]; if (parentId) { const { element: parent } = this.findElementById(elements, parentId); if (!parent || !parent.elements) { logger.error(`Parent ${parentId} not found for reorder`); return false; } targetElements = parent.elements; } else { targetElements = elements; } // Create a map of elements by ID const elementMap = new Map<string, ElementorElement>(); targetElements.forEach(el => elementMap.set(el.id, el)); // Reorder according to newOrder array const reordered: ElementorElement[] = []; for (const id of newOrder) { const el = elementMap.get(id); if (el) { reordered.push(el); elementMap.delete(id); } } // Add any remaining elements that weren't in newOrder elementMap.forEach(el => reordered.push(el)); // Replace elements if (parentId) { const { element: parent } = this.findElementById(elements, parentId); if (parent) { parent.elements = reordered; } } else { elements.length = 0; elements.push(...reordered); } logger.info(`Reordered ${reordered.length} elements`); return true; } /** * Copy settings from one element to another */ copyElementSettings( elements: ElementorElement[], sourceId: string, targetId: string, settingsKeys?: string[] ): boolean { const { element: source } = this.findElementById(elements, sourceId); const { element: target } = this.findElementById(elements, targetId); if (!source || !target) { logger.error('Source or target element not found for settings copy'); return false; } if (settingsKeys && settingsKeys.length > 0) { // Copy only specified settings settingsKeys.forEach(key => { if (source.settings[key] !== undefined) { target.settings[key] = JSON.parse(JSON.stringify(source.settings[key])); } }); } else { // Copy all settings target.settings = JSON.parse(JSON.stringify(source.settings)); } logger.info(`Copied settings from ${sourceId} to ${targetId}`); return true; } /** * Get page structure (flattened tree) */ getPageStructure( elements: ElementorElement[], includeSettings: boolean = false ): any[] { const structure: any[] = []; const traverse = (els: ElementorElement[], level: number = 0) => { for (const el of els) { const item: any = { id: el.id, type: el.elType, widgetType: el.widgetType || null, level }; if (includeSettings) { item.settings = el.settings; } if (el.elements && el.elements.length > 0) { item.childCount = el.elements.length; structure.push(item); traverse(el.elements, level + 1); } else { structure.push(item); } } }; traverse(elements); return structure; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mbrown1837/Ultimate-Elementor-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server