var Insertr = (function () { 'use strict'; /** * InsertrCore - Core functionality for content management */ class InsertrCore { constructor(options = {}) { this.options = { apiEndpoint: options.apiEndpoint || '/api/content', siteId: options.siteId || 'default', ...options }; } // Find all enhanced elements on the page findEnhancedElements() { return document.querySelectorAll('[data-insertr-enhanced="true"]'); } // Get element metadata getElementMetadata(element) { return { contentId: element.getAttribute('data-content-id'), contentType: element.getAttribute('data-content-type'), element: element }; } // Get all elements with their metadata getAllElements() { const elements = this.findEnhancedElements(); return Array.from(elements).map(el => this.getElementMetadata(el)); } } /** * InsertrFormRenderer - Professional modal editing forms * Ported from prototype with modern ES6+ architecture */ class InsertrFormRenderer { constructor() { this.currentOverlay = null; this.setupStyles(); } /** * Create and show edit form for content element * @param {Object} meta - Element metadata {element, contentId, contentType} * @param {string} currentContent - Current content value * @param {Function} onSave - Save callback * @param {Function} onCancel - Cancel callback */ showEditForm(meta, currentContent, onSave, onCancel) { // Close any existing form this.closeForm(); const { element, contentId, contentType } = meta; const config = this.getFieldConfig(element, contentType); // Create form const form = this.createEditForm(contentId, config, currentContent); // Create overlay with backdrop const overlay = this.createOverlay(form); // Position form this.positionForm(element, overlay); // Setup event handlers this.setupFormHandlers(form, overlay, { onSave, onCancel }); // Show form document.body.appendChild(overlay); this.currentOverlay = overlay; // Focus first input const firstInput = form.querySelector('input, textarea'); if (firstInput) { setTimeout(() => firstInput.focus(), 100); } return overlay; } /** * Close current form */ closeForm() { if (this.currentOverlay) { this.currentOverlay.remove(); this.currentOverlay = null; } } /** * Generate field configuration based on element */ getFieldConfig(element, contentType) { const tagName = element.tagName.toLowerCase(); const classList = Array.from(element.classList); // Default configurations based on element type const configs = { h1: { type: 'text', label: 'Headline', maxLength: 60, placeholder: 'Enter headline...' }, h2: { type: 'text', label: 'Subheading', maxLength: 80, placeholder: 'Enter subheading...' }, h3: { type: 'text', label: 'Section Title', maxLength: 100, placeholder: 'Enter title...' }, h4: { type: 'text', label: 'Title', maxLength: 100, placeholder: 'Enter title...' }, h5: { type: 'text', label: 'Title', maxLength: 100, placeholder: 'Enter title...' }, h6: { type: 'text', label: 'Title', maxLength: 100, placeholder: 'Enter title...' }, p: { type: 'textarea', label: 'Paragraph', rows: 3, placeholder: 'Enter paragraph text...' }, a: { type: 'link', label: 'Link', placeholder: 'Enter link text...', includeUrl: true }, span: { type: 'text', label: 'Text', placeholder: 'Enter text...' }, button: { type: 'text', label: 'Button Text', placeholder: 'Enter button text...' }, }; let config = configs[tagName] || { type: 'text', label: 'Text', placeholder: 'Enter text...' }; // CSS class enhancements if (classList.includes('lead')) { config = { ...config, label: 'Lead Paragraph', rows: 4, placeholder: 'Enter lead paragraph...' }; } // Override with contentType from CLI if specified if (contentType === 'markdown') { config = { ...config, type: 'markdown', label: 'Markdown Content', rows: 8 }; } return config; } /** * Create form HTML structure */ createEditForm(contentId, config, currentContent) { const form = document.createElement('div'); form.className = 'insertr-edit-form'; let formHTML = `
${config.label}
`; if (config.type === 'markdown') { formHTML += this.createMarkdownField(config, currentContent); } else if (config.type === 'link' && config.includeUrl) { formHTML += this.createLinkField(config, currentContent); } else if (config.type === 'textarea') { formHTML += this.createTextareaField(config, currentContent); } else { formHTML += this.createTextField(config, currentContent); } // Form buttons formHTML += `
`; form.innerHTML = formHTML; return form; } /** * Create markdown field with preview */ createMarkdownField(config, currentContent) { return `
Supports Markdown formatting (bold, italic, links, etc.)
`; } /** * Create link field (text + URL) */ createLinkField(config, currentContent) { const linkText = typeof currentContent === 'object' ? currentContent.text || '' : currentContent; const linkUrl = typeof currentContent === 'object' ? currentContent.url || '' : ''; return `
`; } /** * Create textarea field */ createTextareaField(config, currentContent) { const content = typeof currentContent === 'object' ? currentContent.text || '' : currentContent; return `
`; } /** * Create text input field */ createTextField(config, currentContent) { const content = typeof currentContent === 'object' ? currentContent.text || '' : currentContent; return `
`; } /** * Create overlay with backdrop */ createOverlay(form) { const overlay = document.createElement('div'); overlay.className = 'insertr-form-overlay'; overlay.appendChild(form); return overlay; } /** * Position form relative to element */ positionForm(element, overlay) { const rect = element.getBoundingClientRect(); const form = overlay.querySelector('.insertr-edit-form'); // Calculate optimal width (responsive) const viewportWidth = window.innerWidth; let formWidth; if (viewportWidth < 768) { formWidth = Math.min(viewportWidth - 40, 350); } else { formWidth = Math.min(Math.max(rect.width, 300), 500); } form.style.width = `${formWidth}px`; // Position below element with some spacing const top = rect.bottom + window.scrollY + 10; const left = Math.max(20, rect.left + window.scrollX); overlay.style.position = 'absolute'; overlay.style.top = `${top}px`; overlay.style.left = `${left}px`; overlay.style.zIndex = '10000'; } /** * Setup form event handlers */ setupFormHandlers(form, overlay, { onSave, onCancel }) { const saveBtn = form.querySelector('.insertr-btn-save'); const cancelBtn = form.querySelector('.insertr-btn-cancel'); if (saveBtn) { saveBtn.addEventListener('click', () => { const formData = this.extractFormData(form); onSave(formData); }); } if (cancelBtn) { cancelBtn.addEventListener('click', () => { onCancel(); this.closeForm(); }); } // ESC key to cancel const keyHandler = (e) => { if (e.key === 'Escape') { onCancel(); this.closeForm(); document.removeEventListener('keydown', keyHandler); } }; document.addEventListener('keydown', keyHandler); // Click outside to cancel overlay.addEventListener('click', (e) => { if (e.target === overlay) { onCancel(); this.closeForm(); } }); } /** * Extract form data */ extractFormData(form) { const data = {}; // Handle different field types const textInput = form.querySelector('input[name="text"]'); const urlInput = form.querySelector('input[name="url"]'); const contentInput = form.querySelector('input[name="content"], textarea[name="content"]'); if (textInput && urlInput) { // Link field data.text = textInput.value; data.url = urlInput.value; } else if (contentInput) { // Text or textarea field data.text = contentInput.value; } return data; } /** * Escape HTML to prevent XSS */ escapeHtml(text) { if (typeof text !== 'string') return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Setup form styles */ setupStyles() { const styles = ` .insertr-form-overlay { position: absolute; z-index: 10000; } .insertr-edit-form { background: white; border: 2px solid #007cba; border-radius: 8px; padding: 1rem; box-shadow: 0 8px 25px rgba(0,0,0,0.15); width: 100%; box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .insertr-form-header { font-weight: 600; color: #1f2937; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid #e5e7eb; font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.5px; } .insertr-form-group { margin-bottom: 1rem; } .insertr-form-group:last-child { margin-bottom: 0; } .insertr-form-label { display: block; font-weight: 600; color: #374151; margin-bottom: 0.5rem; font-size: 0.875rem; } .insertr-form-input, .insertr-form-textarea { width: 100%; padding: 0.75rem; border: 1px solid #d1d5db; border-radius: 6px; font-family: inherit; font-size: 1rem; transition: border-color 0.2s, box-shadow 0.2s; box-sizing: border-box; } .insertr-form-input:focus, .insertr-form-textarea:focus { outline: none; border-color: #007cba; box-shadow: 0 0 0 3px rgba(0, 124, 186, 0.1); } .insertr-form-textarea { min-height: 120px; resize: vertical; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; } .insertr-markdown-editor { min-height: 200px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 0.9rem; line-height: 1.5; background-color: #f8fafc; } .insertr-form-actions { display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #e5e7eb; } .insertr-btn-save { background: #10b981; color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; font-size: 0.875rem; } .insertr-btn-save:hover { background: #059669; } .insertr-btn-cancel { background: #6b7280; color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; font-size: 0.875rem; } .insertr-btn-cancel:hover { background: #4b5563; } .insertr-form-help { font-size: 0.75rem; color: #6b7280; margin-top: 0.25rem; } `; const styleSheet = document.createElement('style'); styleSheet.type = 'text/css'; styleSheet.innerHTML = styles; document.head.appendChild(styleSheet); } } /** * InsertrEditor - Visual editing functionality */ class InsertrEditor { constructor(core, options = {}) { this.core = core; this.options = options; this.isActive = false; this.formRenderer = new InsertrFormRenderer(); } start() { if (this.isActive) return; console.log('🚀 Starting Insertr Editor'); this.isActive = true; // Add editor styles this.addEditorStyles(); // Initialize all enhanced elements const elements = this.core.getAllElements(); console.log(`📝 Found ${elements.length} editable elements`); elements.forEach(meta => this.initializeElement(meta)); } initializeElement(meta) { const { element, contentId, contentType } = meta; // Add visual indicators element.style.cursor = 'pointer'; element.style.position = 'relative'; // Add interaction handlers this.addHoverEffects(element); this.addClickHandler(element, meta); } addHoverEffects(element) { element.addEventListener('mouseenter', () => { element.classList.add('insertr-editing-hover'); }); element.addEventListener('mouseleave', () => { element.classList.remove('insertr-editing-hover'); }); } addClickHandler(element, meta) { element.addEventListener('click', (e) => { e.preventDefault(); this.openEditor(meta); }); } openEditor(meta) { const { element } = meta; const currentContent = this.extractCurrentContent(element); // Show professional form instead of prompt this.formRenderer.showEditForm( meta, currentContent, (formData) => this.handleSave(meta, formData), () => this.handleCancel(meta) ); } extractCurrentContent(element) { // For links, extract both text and URL if (element.tagName.toLowerCase() === 'a') { return { text: element.textContent.trim(), url: element.getAttribute('href') || '' }; } // For other elements, just return text content return element.textContent.trim(); } handleSave(meta, formData) { console.log('💾 Saving content:', meta.contentId, formData); // Update element content based on type this.updateElementContent(meta.element, formData); // Close form this.formRenderer.closeForm(); // TODO: Save to backend API console.log(`✅ Content saved:`, meta.contentId, formData); } handleCancel(meta) { console.log('❌ Edit cancelled:', meta.contentId); } updateElementContent(element, formData) { if (element.tagName.toLowerCase() === 'a') { // Update link element if (formData.text !== undefined) { element.textContent = formData.text; } if (formData.url !== undefined) { element.setAttribute('href', formData.url); } } else { // Update text content element.textContent = formData.text || ''; } } // Legacy method - now handled by handleSave and updateElementContent addEditorStyles() { const styles = ` .insertr-editing-hover { outline: 2px dashed #007cba !important; outline-offset: 2px !important; background-color: rgba(0, 124, 186, 0.05) !important; } [data-insertr-enhanced="true"]:hover::after { content: "✏️ " attr(data-content-type); position: absolute; top: -25px; left: 0; background: #007cba; color: white; padding: 2px 6px; font-size: 11px; border-radius: 3px; white-space: nowrap; z-index: 1000; font-family: monospace; } `; const styleSheet = document.createElement('style'); styleSheet.type = 'text/css'; styleSheet.innerHTML = styles; document.head.appendChild(styleSheet); } } /** * Insertr - The Tailwind of CMS * Main library entry point */ // Create global Insertr instance window.Insertr = { // Core functionality core: null, editor: null, // Initialize the library init(options = {}) { console.log('🔧 Insertr v1.0.0 initializing... (Hot Reload Ready)'); this.core = new InsertrCore(options); this.editor = new InsertrEditor(this.core, options); // Auto-initialize if DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.start()); } else { this.start(); } return this; }, // Start the editor start() { if (this.editor) { this.editor.start(); } }, // Version info version: '1.0.0' }; // Auto-initialize in development mode if (document.querySelector('[data-insertr-enhanced]')) { window.Insertr.init(); } var index = window.Insertr; return index; })();