- Implement debounced live preview in modal editing (500ms) - Add LivePreviewManager class with element tracking and restoration - Enhance modal sizing for comfortable 60-80 character editing - Add auto-copy plugin to rollup config for seamless development - Update dev command to automatically sync changes to demo-site The live preview system provides real-time visual feedback while typing in modals, showing changes in context without saving. Enhanced dev workflow eliminates manual build steps, enabling instant iteration during development.
714 lines
24 KiB
JavaScript
714 lines
24 KiB
JavaScript
/**
|
|
* LivePreviewManager - Handles debounced live preview updates
|
|
*/
|
|
class LivePreviewManager {
|
|
constructor() {
|
|
this.previewTimeouts = new Map();
|
|
this.activeElement = null;
|
|
this.originalContent = null;
|
|
this.originalStyles = null;
|
|
}
|
|
|
|
schedulePreview(element, newValue, elementType) {
|
|
const elementId = this.getElementId(element);
|
|
|
|
// Clear existing timeout
|
|
if (this.previewTimeouts.has(elementId)) {
|
|
clearTimeout(this.previewTimeouts.get(elementId));
|
|
}
|
|
|
|
// Schedule new preview update with 500ms debounce
|
|
const timeoutId = setTimeout(() => {
|
|
this.updatePreview(element, newValue, elementType);
|
|
}, 500);
|
|
|
|
this.previewTimeouts.set(elementId, timeoutId);
|
|
}
|
|
|
|
updatePreview(element, newValue, elementType) {
|
|
// Store original content if first preview
|
|
if (!this.originalContent && this.activeElement === element) {
|
|
this.originalContent = this.extractOriginalContent(element, elementType);
|
|
}
|
|
|
|
// Apply preview styling and content
|
|
this.applyPreviewContent(element, newValue, elementType);
|
|
}
|
|
|
|
extractOriginalContent(element, elementType) {
|
|
switch (elementType) {
|
|
case 'link':
|
|
return {
|
|
text: element.textContent,
|
|
url: element.href
|
|
};
|
|
default:
|
|
return element.textContent;
|
|
}
|
|
}
|
|
|
|
applyPreviewContent(element, newValue, elementType) {
|
|
// Add preview indicator
|
|
element.classList.add('insertr-preview-active');
|
|
|
|
// Update content based on element type
|
|
switch (elementType) {
|
|
case 'text':
|
|
case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
|
|
case 'span': case 'button':
|
|
if (newValue && newValue.trim()) {
|
|
element.textContent = newValue;
|
|
}
|
|
break;
|
|
|
|
case 'textarea':
|
|
case 'p':
|
|
if (newValue && newValue.trim()) {
|
|
element.textContent = newValue;
|
|
}
|
|
break;
|
|
|
|
case 'link':
|
|
if (typeof newValue === 'object') {
|
|
if (newValue.text !== undefined && newValue.text.trim()) {
|
|
element.textContent = newValue.text;
|
|
}
|
|
if (newValue.url !== undefined && newValue.url.trim()) {
|
|
element.href = newValue.url;
|
|
}
|
|
} else if (newValue && newValue.trim()) {
|
|
element.textContent = newValue;
|
|
}
|
|
break;
|
|
|
|
case 'markdown':
|
|
// For markdown, show raw text preview
|
|
if (newValue && newValue.trim()) {
|
|
element.textContent = newValue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
clearPreview(element) {
|
|
if (!element) return;
|
|
|
|
const elementId = this.getElementId(element);
|
|
|
|
// Clear any pending preview
|
|
if (this.previewTimeouts.has(elementId)) {
|
|
clearTimeout(this.previewTimeouts.get(elementId));
|
|
this.previewTimeouts.delete(elementId);
|
|
}
|
|
|
|
// Restore original content
|
|
if (this.originalContent && element === this.activeElement) {
|
|
this.restoreOriginalContent(element);
|
|
}
|
|
|
|
// Remove preview styling
|
|
element.classList.remove('insertr-preview-active');
|
|
this.activeElement = null;
|
|
this.originalContent = null;
|
|
}
|
|
|
|
restoreOriginalContent(element) {
|
|
if (!this.originalContent) return;
|
|
|
|
if (typeof this.originalContent === 'object') {
|
|
// Link element
|
|
element.textContent = this.originalContent.text;
|
|
if (this.originalContent.url) {
|
|
element.href = this.originalContent.url;
|
|
}
|
|
} else {
|
|
// Text element
|
|
element.textContent = this.originalContent;
|
|
}
|
|
}
|
|
|
|
getElementId(element) {
|
|
// Create unique ID for element tracking
|
|
if (!element._insertrId) {
|
|
element._insertrId = 'insertr_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
}
|
|
return element._insertrId;
|
|
}
|
|
|
|
setActiveElement(element) {
|
|
this.activeElement = element;
|
|
this.originalContent = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* InsertrFormRenderer - Professional modal editing forms with live preview
|
|
* Enhanced with debounced live preview and comfortable input sizing
|
|
*/
|
|
export class InsertrFormRenderer {
|
|
constructor() {
|
|
this.currentOverlay = null;
|
|
this.previewManager = new LivePreviewManager();
|
|
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);
|
|
|
|
// Initialize preview manager for this element
|
|
this.previewManager.setActiveElement(element);
|
|
|
|
// Create form
|
|
const form = this.createEditForm(contentId, config, currentContent);
|
|
|
|
// Create overlay with backdrop
|
|
const overlay = this.createOverlay(form);
|
|
|
|
// Position form with enhanced sizing
|
|
this.positionForm(element, overlay);
|
|
|
|
// Setup event handlers with live preview
|
|
this.setupFormHandlers(form, overlay, element, config, { 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() {
|
|
// Clear any active previews
|
|
if (this.previewManager.activeElement) {
|
|
this.previewManager.clearPreview(this.previewManager.activeElement);
|
|
}
|
|
|
|
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 = `<div class="insertr-form-header">${config.label}</div>`;
|
|
|
|
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 += `
|
|
<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;
|
|
return form;
|
|
}
|
|
|
|
/**
|
|
* Create markdown field with preview
|
|
*/
|
|
createMarkdownField(config, currentContent) {
|
|
return `
|
|
<div class="insertr-form-group">
|
|
<textarea class="insertr-form-textarea insertr-markdown-editor" name="content"
|
|
rows="${config.rows || 8}"
|
|
placeholder="${config.placeholder}">${this.escapeHtml(currentContent)}</textarea>
|
|
<div class="insertr-form-help">
|
|
Supports Markdown formatting (bold, italic, links, etc.)
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Create link field (text + URL)
|
|
*/
|
|
createLinkField(config, currentContent) {
|
|
const linkText = typeof currentContent === 'object' ? currentContent.text || '' : currentContent;
|
|
const linkUrl = typeof currentContent === 'object' ? currentContent.url || '' : '';
|
|
|
|
return `
|
|
<div class="insertr-form-group">
|
|
<label class="insertr-form-label">Link Text:</label>
|
|
<input type="text" class="insertr-form-input" name="text"
|
|
value="${this.escapeHtml(linkText)}"
|
|
placeholder="${config.placeholder}"
|
|
maxlength="${config.maxLength || 200}">
|
|
</div>
|
|
<div class="insertr-form-group">
|
|
<label class="insertr-form-label">Link URL:</label>
|
|
<input type="url" class="insertr-form-input" name="url"
|
|
value="${this.escapeHtml(linkUrl)}"
|
|
placeholder="https://example.com">
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Create textarea field
|
|
*/
|
|
createTextareaField(config, currentContent) {
|
|
const content = typeof currentContent === 'object' ? currentContent.text || '' : currentContent;
|
|
return `
|
|
<div class="insertr-form-group">
|
|
<textarea class="insertr-form-textarea" name="content"
|
|
rows="${config.rows || 3}"
|
|
placeholder="${config.placeholder}"
|
|
maxlength="${config.maxLength || 1000}">${this.escapeHtml(content)}</textarea>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Create text input field
|
|
*/
|
|
createTextField(config, currentContent) {
|
|
const content = typeof currentContent === 'object' ? currentContent.text || '' : currentContent;
|
|
return `
|
|
<div class="insertr-form-group">
|
|
<input type="text" class="insertr-form-input" name="content"
|
|
value="${this.escapeHtml(content)}"
|
|
placeholder="${config.placeholder}"
|
|
maxlength="${config.maxLength || 200}">
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* 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 for comfortable editing (60-80 characters)
|
|
const viewportWidth = window.innerWidth;
|
|
let formWidth;
|
|
|
|
if (viewportWidth < 768) {
|
|
// Mobile: prioritize usability over character count
|
|
formWidth = Math.min(viewportWidth - 40, 500);
|
|
} else {
|
|
// Desktop: ensure comfortable 60-80 character editing
|
|
const minComfortableWidth = 600; // ~70 characters at 1rem
|
|
const maxWidth = Math.min(viewportWidth * 0.9, 800); // Max 800px or 90% viewport
|
|
const elementWidth = rect.width;
|
|
|
|
// Use larger of: comfortable width, 1.5x element width, but cap at maxWidth
|
|
formWidth = Math.max(
|
|
minComfortableWidth,
|
|
Math.min(elementWidth * 1.5, maxWidth)
|
|
);
|
|
}
|
|
|
|
form.style.width = `${formWidth}px`;
|
|
|
|
// Position below element with some spacing
|
|
const top = rect.bottom + window.scrollY + 10;
|
|
|
|
// Center form relative to element, but keep within viewport
|
|
const centerLeft = rect.left + window.scrollX + (rect.width / 2) - (formWidth / 2);
|
|
const minLeft = 20;
|
|
const maxLeft = window.innerWidth - formWidth - 20;
|
|
const left = Math.max(minLeft, Math.min(centerLeft, maxLeft));
|
|
|
|
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, element, config, { onSave, onCancel }) {
|
|
const saveBtn = form.querySelector('.insertr-btn-save');
|
|
const cancelBtn = form.querySelector('.insertr-btn-cancel');
|
|
const elementType = this.getElementType(element, config);
|
|
|
|
// Setup live preview for input changes
|
|
this.setupLivePreview(form, element, elementType);
|
|
|
|
if (saveBtn) {
|
|
saveBtn.addEventListener('click', () => {
|
|
// Clear preview before saving (makes changes permanent)
|
|
this.previewManager.clearPreview(element);
|
|
const formData = this.extractFormData(form);
|
|
onSave(formData);
|
|
this.closeForm();
|
|
});
|
|
}
|
|
|
|
if (cancelBtn) {
|
|
cancelBtn.addEventListener('click', () => {
|
|
// Clear preview to restore original content
|
|
this.previewManager.clearPreview(element);
|
|
onCancel();
|
|
this.closeForm();
|
|
});
|
|
}
|
|
|
|
// ESC key to cancel
|
|
const keyHandler = (e) => {
|
|
if (e.key === 'Escape') {
|
|
this.previewManager.clearPreview(element);
|
|
onCancel();
|
|
this.closeForm();
|
|
document.removeEventListener('keydown', keyHandler);
|
|
}
|
|
};
|
|
document.addEventListener('keydown', keyHandler);
|
|
|
|
// Click outside to cancel
|
|
overlay.addEventListener('click', (e) => {
|
|
if (e.target === overlay) {
|
|
this.previewManager.clearPreview(element);
|
|
onCancel();
|
|
this.closeForm();
|
|
}
|
|
});
|
|
}
|
|
|
|
setupLivePreview(form, element, elementType) {
|
|
// Get all input elements that should trigger preview updates
|
|
const inputs = form.querySelectorAll('input, textarea');
|
|
|
|
inputs.forEach(input => {
|
|
input.addEventListener('input', () => {
|
|
const newValue = this.extractInputValue(form, elementType);
|
|
this.previewManager.schedulePreview(element, newValue, elementType);
|
|
});
|
|
});
|
|
}
|
|
|
|
extractInputValue(form, elementType) {
|
|
// Extract current form values for preview
|
|
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
|
|
return {
|
|
text: textInput.value,
|
|
url: urlInput.value
|
|
};
|
|
} else if (contentInput) {
|
|
// Text or textarea field
|
|
return contentInput.value;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
getElementType(element, config) {
|
|
// Determine element type for preview handling
|
|
if (config.type === 'link') return 'link';
|
|
if (config.type === 'markdown') return 'markdown';
|
|
if (config.type === 'textarea') return 'textarea';
|
|
|
|
const tagName = element.tagName.toLowerCase();
|
|
return tagName === 'p' ? 'p' : 'text';
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/* Live Preview Styles */
|
|
.insertr-preview-active {
|
|
position: relative;
|
|
background: rgba(0, 124, 186, 0.05) !important;
|
|
outline: 2px solid #007cba !important;
|
|
outline-offset: 2px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.insertr-preview-active::after {
|
|
content: "Preview";
|
|
position: absolute;
|
|
top: -25px;
|
|
left: 0;
|
|
background: #007cba;
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: 3px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
z-index: 10001;
|
|
white-space: nowrap;
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
}
|
|
|
|
/* Enhanced modal sizing for comfortable editing */
|
|
.insertr-edit-form {
|
|
min-width: 600px; /* Ensures ~70 character width */
|
|
max-width: 800px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.insertr-edit-form {
|
|
min-width: 90vw;
|
|
max-width: 90vw;
|
|
}
|
|
|
|
.insertr-preview-active::after {
|
|
top: -20px;
|
|
font-size: 0.7rem;
|
|
padding: 1px 6px;
|
|
}
|
|
}
|
|
|
|
/* Enhanced input styling for comfortable editing */
|
|
.insertr-form-input {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
`;
|
|
|
|
const styleSheet = document.createElement('style');
|
|
styleSheet.type = 'text/css';
|
|
styleSheet.innerHTML = styles;
|
|
document.head.appendChild(styleSheet);
|
|
}
|
|
}
|