feat: add live preview system and enhance dev workflow
- 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.
This commit is contained in:
@@ -1,5 +1,21 @@
|
|||||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||||
import terser from '@rollup/plugin-terser';
|
import terser from '@rollup/plugin-terser';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
|
||||||
|
// Simple copy plugin to auto-copy to demo-site during development
|
||||||
|
function copyToDemo() {
|
||||||
|
return {
|
||||||
|
name: 'copy-to-demo',
|
||||||
|
writeBundle() {
|
||||||
|
try {
|
||||||
|
execSync('cp dist/insertr.js ../demo-site/insertr.js');
|
||||||
|
console.log('📄 Copied to demo-site/insertr.js');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Failed to copy to demo-site:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
// Development build
|
// Development build
|
||||||
@@ -11,7 +27,8 @@ export default [
|
|||||||
name: 'Insertr'
|
name: 'Insertr'
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
nodeResolve()
|
nodeResolve(),
|
||||||
|
copyToDemo()
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// Production build (minified)
|
// Production build (minified)
|
||||||
|
|||||||
@@ -1,10 +1,154 @@
|
|||||||
/**
|
/**
|
||||||
* InsertrFormRenderer - Professional modal editing forms
|
* LivePreviewManager - Handles debounced live preview updates
|
||||||
* Ported from prototype with modern ES6+ architecture
|
*/
|
||||||
|
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 {
|
export class InsertrFormRenderer {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.currentOverlay = null;
|
this.currentOverlay = null;
|
||||||
|
this.previewManager = new LivePreviewManager();
|
||||||
this.setupStyles();
|
this.setupStyles();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,29 +165,32 @@ export class InsertrFormRenderer {
|
|||||||
|
|
||||||
const { element, contentId, contentType } = meta;
|
const { element, contentId, contentType } = meta;
|
||||||
const config = this.getFieldConfig(element, contentType);
|
const config = this.getFieldConfig(element, contentType);
|
||||||
|
|
||||||
|
// Initialize preview manager for this element
|
||||||
|
this.previewManager.setActiveElement(element);
|
||||||
|
|
||||||
// Create form
|
// Create form
|
||||||
const form = this.createEditForm(contentId, config, currentContent);
|
const form = this.createEditForm(contentId, config, currentContent);
|
||||||
|
|
||||||
// Create overlay with backdrop
|
// Create overlay with backdrop
|
||||||
const overlay = this.createOverlay(form);
|
const overlay = this.createOverlay(form);
|
||||||
|
|
||||||
// Position form
|
// Position form with enhanced sizing
|
||||||
this.positionForm(element, overlay);
|
this.positionForm(element, overlay);
|
||||||
|
|
||||||
// Setup event handlers
|
// Setup event handlers with live preview
|
||||||
this.setupFormHandlers(form, overlay, { onSave, onCancel });
|
this.setupFormHandlers(form, overlay, element, config, { onSave, onCancel });
|
||||||
|
|
||||||
// Show form
|
// Show form
|
||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
this.currentOverlay = overlay;
|
this.currentOverlay = overlay;
|
||||||
|
|
||||||
// Focus first input
|
// Focus first input
|
||||||
const firstInput = form.querySelector('input, textarea');
|
const firstInput = form.querySelector('input, textarea');
|
||||||
if (firstInput) {
|
if (firstInput) {
|
||||||
setTimeout(() => firstInput.focus(), 100);
|
setTimeout(() => firstInput.focus(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
return overlay;
|
return overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +198,11 @@ export class InsertrFormRenderer {
|
|||||||
* Close current form
|
* Close current form
|
||||||
*/
|
*/
|
||||||
closeForm() {
|
closeForm() {
|
||||||
|
// Clear any active previews
|
||||||
|
if (this.previewManager.activeElement) {
|
||||||
|
this.previewManager.clearPreview(this.previewManager.activeElement);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.currentOverlay) {
|
if (this.currentOverlay) {
|
||||||
this.currentOverlay.remove();
|
this.currentOverlay.remove();
|
||||||
this.currentOverlay = null;
|
this.currentOverlay = null;
|
||||||
@@ -63,7 +215,7 @@ export class InsertrFormRenderer {
|
|||||||
getFieldConfig(element, contentType) {
|
getFieldConfig(element, contentType) {
|
||||||
const tagName = element.tagName.toLowerCase();
|
const tagName = element.tagName.toLowerCase();
|
||||||
const classList = Array.from(element.classList);
|
const classList = Array.from(element.classList);
|
||||||
|
|
||||||
// Default configurations based on element type
|
// Default configurations based on element type
|
||||||
const configs = {
|
const configs = {
|
||||||
h1: { type: 'text', label: 'Headline', maxLength: 60, placeholder: 'Enter headline...' },
|
h1: { type: 'text', label: 'Headline', maxLength: 60, placeholder: 'Enter headline...' },
|
||||||
@@ -84,7 +236,7 @@ export class InsertrFormRenderer {
|
|||||||
if (classList.includes('lead')) {
|
if (classList.includes('lead')) {
|
||||||
config = { ...config, label: 'Lead Paragraph', rows: 4, placeholder: 'Enter lead paragraph...' };
|
config = { ...config, label: 'Lead Paragraph', rows: 4, placeholder: 'Enter lead paragraph...' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override with contentType from CLI if specified
|
// Override with contentType from CLI if specified
|
||||||
if (contentType === 'markdown') {
|
if (contentType === 'markdown') {
|
||||||
config = { ...config, type: 'markdown', label: 'Markdown Content', rows: 8 };
|
config = { ...config, type: 'markdown', label: 'Markdown Content', rows: 8 };
|
||||||
@@ -99,9 +251,9 @@ export class InsertrFormRenderer {
|
|||||||
createEditForm(contentId, config, currentContent) {
|
createEditForm(contentId, config, currentContent) {
|
||||||
const form = document.createElement('div');
|
const form = document.createElement('div');
|
||||||
form.className = 'insertr-edit-form';
|
form.className = 'insertr-edit-form';
|
||||||
|
|
||||||
let formHTML = `<div class="insertr-form-header">${config.label}</div>`;
|
let formHTML = `<div class="insertr-form-header">${config.label}</div>`;
|
||||||
|
|
||||||
if (config.type === 'markdown') {
|
if (config.type === 'markdown') {
|
||||||
formHTML += this.createMarkdownField(config, currentContent);
|
formHTML += this.createMarkdownField(config, currentContent);
|
||||||
} else if (config.type === 'link' && config.includeUrl) {
|
} else if (config.type === 'link' && config.includeUrl) {
|
||||||
@@ -111,7 +263,7 @@ export class InsertrFormRenderer {
|
|||||||
} else {
|
} else {
|
||||||
formHTML += this.createTextField(config, currentContent);
|
formHTML += this.createTextField(config, currentContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form buttons
|
// Form buttons
|
||||||
formHTML += `
|
formHTML += `
|
||||||
<div class="insertr-form-actions">
|
<div class="insertr-form-actions">
|
||||||
@@ -119,7 +271,7 @@ export class InsertrFormRenderer {
|
|||||||
<button type="button" class="insertr-btn-cancel">Cancel</button>
|
<button type="button" class="insertr-btn-cancel">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
form.innerHTML = formHTML;
|
form.innerHTML = formHTML;
|
||||||
return form;
|
return form;
|
||||||
}
|
}
|
||||||
@@ -146,7 +298,7 @@ export class InsertrFormRenderer {
|
|||||||
createLinkField(config, currentContent) {
|
createLinkField(config, currentContent) {
|
||||||
const linkText = typeof currentContent === 'object' ? currentContent.text || '' : currentContent;
|
const linkText = typeof currentContent === 'object' ? currentContent.text || '' : currentContent;
|
||||||
const linkUrl = typeof currentContent === 'object' ? currentContent.url || '' : '';
|
const linkUrl = typeof currentContent === 'object' ? currentContent.url || '' : '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="insertr-form-group">
|
<div class="insertr-form-group">
|
||||||
<label class="insertr-form-label">Link Text:</label>
|
<label class="insertr-form-label">Link Text:</label>
|
||||||
@@ -210,23 +362,38 @@ export class InsertrFormRenderer {
|
|||||||
positionForm(element, overlay) {
|
positionForm(element, overlay) {
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
const form = overlay.querySelector('.insertr-edit-form');
|
const form = overlay.querySelector('.insertr-edit-form');
|
||||||
|
|
||||||
// Calculate optimal width (responsive)
|
// Calculate optimal width for comfortable editing (60-80 characters)
|
||||||
const viewportWidth = window.innerWidth;
|
const viewportWidth = window.innerWidth;
|
||||||
let formWidth;
|
let formWidth;
|
||||||
|
|
||||||
if (viewportWidth < 768) {
|
if (viewportWidth < 768) {
|
||||||
formWidth = Math.min(viewportWidth - 40, 350);
|
// Mobile: prioritize usability over character count
|
||||||
|
formWidth = Math.min(viewportWidth - 40, 500);
|
||||||
} else {
|
} else {
|
||||||
formWidth = Math.min(Math.max(rect.width, 300), 500);
|
// 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`;
|
form.style.width = `${formWidth}px`;
|
||||||
|
|
||||||
// Position below element with some spacing
|
// Position below element with some spacing
|
||||||
const top = rect.bottom + window.scrollY + 10;
|
const top = rect.bottom + window.scrollY + 10;
|
||||||
const left = Math.max(20, rect.left + window.scrollX);
|
|
||||||
|
// 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.position = 'absolute';
|
||||||
overlay.style.top = `${top}px`;
|
overlay.style.top = `${top}px`;
|
||||||
overlay.style.left = `${left}px`;
|
overlay.style.left = `${left}px`;
|
||||||
@@ -236,54 +403,107 @@ export class InsertrFormRenderer {
|
|||||||
/**
|
/**
|
||||||
* Setup form event handlers
|
* Setup form event handlers
|
||||||
*/
|
*/
|
||||||
setupFormHandlers(form, overlay, { onSave, onCancel }) {
|
setupFormHandlers(form, overlay, element, config, { onSave, onCancel }) {
|
||||||
const saveBtn = form.querySelector('.insertr-btn-save');
|
const saveBtn = form.querySelector('.insertr-btn-save');
|
||||||
const cancelBtn = form.querySelector('.insertr-btn-cancel');
|
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) {
|
if (saveBtn) {
|
||||||
saveBtn.addEventListener('click', () => {
|
saveBtn.addEventListener('click', () => {
|
||||||
|
// Clear preview before saving (makes changes permanent)
|
||||||
|
this.previewManager.clearPreview(element);
|
||||||
const formData = this.extractFormData(form);
|
const formData = this.extractFormData(form);
|
||||||
onSave(formData);
|
onSave(formData);
|
||||||
|
this.closeForm();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelBtn) {
|
if (cancelBtn) {
|
||||||
cancelBtn.addEventListener('click', () => {
|
cancelBtn.addEventListener('click', () => {
|
||||||
|
// Clear preview to restore original content
|
||||||
|
this.previewManager.clearPreview(element);
|
||||||
onCancel();
|
onCancel();
|
||||||
this.closeForm();
|
this.closeForm();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ESC key to cancel
|
// ESC key to cancel
|
||||||
const keyHandler = (e) => {
|
const keyHandler = (e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
|
this.previewManager.clearPreview(element);
|
||||||
onCancel();
|
onCancel();
|
||||||
this.closeForm();
|
this.closeForm();
|
||||||
document.removeEventListener('keydown', keyHandler);
|
document.removeEventListener('keydown', keyHandler);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener('keydown', keyHandler);
|
document.addEventListener('keydown', keyHandler);
|
||||||
|
|
||||||
// Click outside to cancel
|
// Click outside to cancel
|
||||||
overlay.addEventListener('click', (e) => {
|
overlay.addEventListener('click', (e) => {
|
||||||
if (e.target === overlay) {
|
if (e.target === overlay) {
|
||||||
|
this.previewManager.clearPreview(element);
|
||||||
onCancel();
|
onCancel();
|
||||||
this.closeForm();
|
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
|
* Extract form data
|
||||||
*/
|
*/
|
||||||
extractFormData(form) {
|
extractFormData(form) {
|
||||||
const data = {};
|
const data = {};
|
||||||
|
|
||||||
// Handle different field types
|
// Handle different field types
|
||||||
const textInput = form.querySelector('input[name="text"]');
|
const textInput = form.querySelector('input[name="text"]');
|
||||||
const urlInput = form.querySelector('input[name="url"]');
|
const urlInput = form.querySelector('input[name="url"]');
|
||||||
const contentInput = form.querySelector('input[name="content"], textarea[name="content"]');
|
const contentInput = form.querySelector('input[name="content"], textarea[name="content"]');
|
||||||
|
|
||||||
if (textInput && urlInput) {
|
if (textInput && urlInput) {
|
||||||
// Link field
|
// Link field
|
||||||
data.text = textInput.value;
|
data.text = textInput.value;
|
||||||
@@ -292,7 +512,7 @@ export class InsertrFormRenderer {
|
|||||||
// Text or textarea field
|
// Text or textarea field
|
||||||
data.text = contentInput.value;
|
data.text = contentInput.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,11 +653,61 @@ export class InsertrFormRenderer {
|
|||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
margin-top: 0.25rem;
|
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');
|
const styleSheet = document.createElement('style');
|
||||||
styleSheet.type = 'text/css';
|
styleSheet.type = 'text/css';
|
||||||
styleSheet.innerHTML = styles;
|
styleSheet.innerHTML = styles;
|
||||||
document.head.appendChild(styleSheet);
|
document.head.appendChild(styleSheet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user