Files
insertr/demo-site/archive/insertr-old/form-renderer.js
Joakim 9bc2751ff2 Clean up demo site: remove manual IDs and frontend-only approach
- Remove all manual data-content-id attributes from HTML files
- Archive old insertr JS/CSS assets to demo-site/archive/
- Remove hardcoded script includes and CSS links
- Remove old authentication controls and mock API
- Enable pure zero-config approach with class='insertr' only
- Parser now generates all 40 content IDs automatically
2025-09-03 12:18:43 +02:00

305 lines
11 KiB
JavaScript

/**
* 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 = `<div class="insertr-form-header">${config.label}</div>`;
if (config.type === 'markdown') {
// Markdown collection editing
formHTML += `
<div class="insertr-form-group">
<textarea class="insertr-form-textarea insertr-markdown-editor" name="content"
rows="${config.rows || 8}"
placeholder="${config.placeholder}">${this.validation.escapeHtml(currentContent)}</textarea>
<div class="insertr-form-help">
Live preview will appear here when you start typing
</div>
<div class="insertr-markdown-preview" style="display: none;">
<div class="insertr-preview-label">Preview:</div>
<div class="insertr-preview-content"></div>
</div>
</div>
`;
} 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 += `
<div class="insertr-form-group">
<label>Link Text:</label>
<input type="text" class="insertr-form-input" name="text"
value="${this.validation.escapeHtml(linkText)}"
placeholder="${config.placeholder}"
maxlength="${config.maxLength || 200}">
</div>
<div class="insertr-form-group">
<label>Link URL:</label>
<input type="url" class="insertr-form-input" name="url"
value="${this.validation.escapeHtml(linkUrl)}"
placeholder="https://example.com">
</div>
`;
} else if (config.type === 'textarea') {
// Textarea for longer content
const content = typeof currentContent === 'object' ? currentContent.text || '' : currentContent;
formHTML += `
<div class="insertr-form-group">
<textarea class="insertr-form-textarea" name="content"
rows="${config.rows || 3}"
placeholder="${config.placeholder}"
maxlength="${config.maxLength || 1000}">${this.validation.escapeHtml(content)}</textarea>
</div>
`;
} else {
// Regular text input
const content = typeof currentContent === 'object' ? currentContent.text || '' : currentContent;
formHTML += `
<div class="insertr-form-group">
<input type="text" class="insertr-form-input" name="content"
value="${this.validation.escapeHtml(content)}"
placeholder="${config.placeholder}"
maxlength="${config.maxLength || 200}">
</div>
`;
}
// Form buttons
formHTML += `
<div class="insertr-form-actions">
<button type="button" class="insertr-btn-save">Save</button>
<button type="button" class="insertr-btn-cancel">Cancel</button>
</div>
`;
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 = '<p><em>Preview unavailable</em></p>';
}
}
/**
* 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;
}