/** * InsertrCore - Core functionality for content management */ export class InsertrCore { constructor(options = {}) { this.options = { apiEndpoint: options.apiEndpoint || '/api/content', siteId: options.siteId || 'default', ...options }; } // Find all enhanced elements on the page with container expansion findEnhancedElements() { const directElements = document.querySelectorAll('.insertr'); const expandedElements = []; directElements.forEach(element => { if (this.isContainer(element) && !element.classList.contains('insertr-group')) { // Container element (.insertr) - expand to viable children const children = this.findViableChildren(element); expandedElements.push(...children); } else { // Regular element or group (.insertr-group) expandedElements.push(element); } }); return expandedElements; } // Check if element is a container that should expand to children isContainer(element) { const containerTags = new Set([ 'div', 'section', 'article', 'header', 'footer', 'main', 'aside', 'nav' ]); return containerTags.has(element.tagName.toLowerCase()); } // Find viable children for editing (elements with only text content) findViableChildren(containerElement) { const viable = []; for (const child of containerElement.children) { // Skip elements that already have .insertr class if (child.classList.contains('insertr')) { continue; } // Skip self-closing elements if (this.isSelfClosing(child)) { continue; } // Check if element has only text content (no nested HTML elements) if (this.hasOnlyTextContent(child)) { viable.push(child); } } return viable; } // Check if element is viable for editing (allows simple formatting) hasOnlyTextContent(element) { // Allow elements with simple formatting tags const allowedTags = new Set(['strong', 'b', 'em', 'i', 'a', 'span', 'code']); for (const child of element.children) { const tagName = child.tagName.toLowerCase(); // If child is not an allowed formatting tag, reject if (!allowedTags.has(tagName)) { return false; } // If formatting tag has nested complex elements, reject if (child.children.length > 0) { // Recursively check nested content isn't too complex for (const nestedChild of child.children) { const nestedTag = nestedChild.tagName.toLowerCase(); if (!allowedTags.has(nestedTag)) { return false; } } } } // Element has only text and/or simple formatting - this is viable return element.textContent.trim().length > 0; } // Check if element is self-closing isSelfClosing(element) { const selfClosingTags = new Set([ 'img', 'input', 'br', 'hr', 'meta', 'link', 'area', 'base', 'col', 'embed', 'source', 'track', 'wbr' ]); return selfClosingTags.has(element.tagName.toLowerCase()); } // Get element metadata getElementMetadata(element) { const existingId = element.getAttribute('data-content-id'); // Ensure element has insertr class for server processing if (!element.classList.contains('insertr')) { element.classList.add('insertr'); } // Send HTML markup to server for unified ID generation return { contentId: existingId, // null if new content, existing ID if updating contentType: element.getAttribute('data-content-type') || this.detectContentType(element), element: element, htmlMarkup: element.outerHTML // Server will generate ID from this }; } // Get current file path from URL for consistent ID generation getCurrentFilePath() { const path = window.location.pathname; if (path === '/' || path === '') { return 'index.html'; } // Remove leading slash: "/about.html" → "about.html" return path.replace(/^\//, ''); } // Detect content type for elements without data-content-type detectContentType(element) { const tag = element.tagName.toLowerCase(); // Only return database-valid types: 'text' or 'link' if (tag === 'a' || tag === 'button') { return 'link'; } // All other elements are text content return 'text'; } // Get all elements with their metadata, including group elements getAllElements() { const directElements = document.querySelectorAll('.insertr, .insertr-group'); const processedElements = []; directElements.forEach(element => { if (element.classList.contains('insertr-group')) { // Group element - treat as single editable unit processedElements.push(element); } else if (this.isContainer(element)) { // Container element - expand to children const children = this.findViableChildren(element); processedElements.push(...children); } else { // Regular element processedElements.push(element); } }); return Array.from(processedElements).map(el => this.getElementMetadata(el)); } }