- 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
305 lines
11 KiB
JavaScript
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;
|
|
} |