/**
* 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 = `
`;
if (config.type === 'markdown') {
// Markdown collection editing
formHTML += `
`;
} 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 += `
Link Text:
Link URL:
`;
} 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 += `
Save
Cancel
`;
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;
}