/** * Insertr Form Renderer Module * Handles form creation and UI interactions */ class InsertrFormRenderer { constructor(validation) { this.validation = validation; } /** * Create edit form for a content element * @param {string} contentId - Content identifier * @param {Object} config - Field configuration * @param {string|Object} currentContent - Current content value * @returns {HTMLElement} Form element */ createEditForm(contentId, config, currentContent) { const form = document.createElement('div'); form.className = 'insertr-edit-form'; let formHTML = `
${config.label}
`; if (config.type === 'markdown') { // Markdown collection editing formHTML += `
Live preview will appear here when you start typing
`; } else if (config.type === 'link' && config.includeUrl) { // Link with URL field const linkText = typeof currentContent === 'object' ? currentContent.text || '' : currentContent; const linkUrl = typeof currentContent === 'object' ? currentContent.url || '' : ''; formHTML += `
`; } else if (config.type === 'textarea') { // Textarea for longer content const content = typeof currentContent === 'object' ? currentContent.text || '' : currentContent; formHTML += `
`; } else { // Regular text input const content = typeof currentContent === 'object' ? currentContent.text || '' : currentContent; formHTML += `
`; } // Form buttons formHTML += `
`; form.innerHTML = formHTML; // Setup form validation this.setupFormValidation(form, config); return form; } /** * Setup real-time validation for form inputs * @param {HTMLElement} form - Form element * @param {Object} config - Field configuration */ setupFormValidation(form, config) { const inputs = form.querySelectorAll('input, textarea'); inputs.forEach(input => { // Real-time validation on input input.addEventListener('input', () => { this.validateFormField(input, config); }); // Also validate on blur for better UX input.addEventListener('blur', () => { this.validateFormField(input, config); }); }); // Setup markdown preview if applicable if (config.type === 'markdown') { this.setupMarkdownPreview(form); } } /** * Validate individual form field * @param {HTMLElement} input - Input element to validate * @param {Object} config - Field configuration */ validateFormField(input, config) { const value = input.value.trim(); let fieldType = config.type; // Determine validation type based on input if (input.type === 'url' || input.name === 'url') { fieldType = 'link'; } const validation = this.validation.validateInput(value, fieldType); // Visual feedback input.classList.toggle('error', !validation.valid); input.classList.toggle('valid', validation.valid && value.length > 0); // Show/hide validation message if (!validation.valid) { this.validation.showValidationMessage(input, validation.message, true); } else { this.validation.showValidationMessage(input, '', false); } return validation.valid; } /** * Setup live markdown preview * @param {HTMLElement} form - Form containing markdown textarea */ setupMarkdownPreview(form) { const textarea = form.querySelector('.insertr-markdown-editor'); const preview = form.querySelector('.insertr-markdown-preview'); const previewContent = form.querySelector('.insertr-preview-content'); if (!textarea || !preview || !previewContent) return; let previewTimeout; textarea.addEventListener('input', () => { const content = textarea.value.trim(); if (content) { preview.style.display = 'block'; // Debounced preview update clearTimeout(previewTimeout); previewTimeout = setTimeout(() => { this.updateMarkdownPreview(previewContent, content); }, 300); } else { preview.style.display = 'none'; } }); } /** * Update markdown preview content * @param {HTMLElement} previewElement - Preview container * @param {string} markdown - Markdown content */ updateMarkdownPreview(previewElement, markdown) { // This method will be called by the main Insertr class // which has access to the markdown processor if (window.insertr && window.insertr.updateMarkdownPreview) { window.insertr.updateMarkdownPreview(previewElement, markdown); } else { previewElement.innerHTML = '

Preview unavailable

'; } } /** * Position edit form relative to element * @param {HTMLElement} element - Element being edited * @param {HTMLElement} overlay - Form overlay */ positionEditForm(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'; } /** * Show edit form * @param {HTMLElement} element - Element being edited * @param {HTMLElement} form - Form to show */ showEditForm(element, form) { // Create overlay const overlay = document.createElement('div'); overlay.className = 'insertr-form-overlay'; overlay.appendChild(form); // Position and show document.body.appendChild(overlay); this.positionEditForm(element, overlay); // Focus first input const firstInput = form.querySelector('input, textarea'); if (firstInput) { setTimeout(() => firstInput.focus(), 100); } // Handle clicking outside to close overlay.addEventListener('click', (e) => { if (e.target === overlay) { this.hideEditForm(overlay); } }); return overlay; } /** * Hide edit form * @param {HTMLElement} overlay - Form overlay to hide */ hideEditForm(overlay) { if (overlay && overlay.parentNode) { overlay.remove(); } } /** * Extract form data * @param {HTMLElement} form - Form to extract data from * @param {Object} config - Field configuration * @returns {Object|string} Extracted form data */ extractFormData(form, config) { if (config.type === 'markdown') { const textarea = form.querySelector('[name="content"]'); return textarea ? textarea.value.trim() : ''; } else if (config.type === 'link' && config.includeUrl) { const textInput = form.querySelector('[name="text"]'); const urlInput = form.querySelector('[name="url"]'); return { text: textInput ? textInput.value.trim() : '', url: urlInput ? urlInput.value.trim() : '' }; } else { const input = form.querySelector('[name="content"]'); return input ? input.value.trim() : ''; } } } // Export for module usage if (typeof module !== 'undefined' && module.exports) { module.exports = InsertrFormRenderer; } // Global export for browser usage if (typeof window !== 'undefined') { window.InsertrFormRenderer = InsertrFormRenderer; }