Implement live collection preview system with contextual template selection
Replace isolated template previews with live collection reconstruction:
- Frontend now reconstructs collection container with all template variants
- Users click directly on rendered templates in proper CSS context
- Perfect preservation of grid/flex layouts and responsive behavior
- Simplified API: preview endpoint returns container_html + templates for frontend reconstruction
- Enhanced UX: WYSIWYG template selection shows exactly what will be added
- Removed redundant templates endpoint in favor of unified preview approach
Backend changes:
- Add GET /api/collections/{id}/preview endpoint
- Remove GET /api/collections/{id}/templates endpoint
- Return container HTML + templates for frontend reconstruction
Frontend changes:
- Replace isolated template modal with live collection preview
- Add generateLivePreview() method for container reconstruction
- Update CollectionManager to use preview API
- Add interactive CSS styling for template selection
This provides true contextual template selection where CSS inheritance,
grid layouts, and responsive design work perfectly in preview mode.
This commit is contained in:
@@ -405,8 +405,8 @@ func (h *ContentHandler) GetCollectionItems(w http.ResponseWriter, r *http.Reque
|
|||||||
json.NewEncoder(w).Encode(items)
|
json.NewEncoder(w).Encode(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCollectionTemplates handles GET /api/collections/{id}/templates
|
// GetCollectionPreview handles GET /api/collections/{id}/preview
|
||||||
func (h *ContentHandler) GetCollectionTemplates(w http.ResponseWriter, r *http.Request) {
|
func (h *ContentHandler) GetCollectionPreview(w http.ResponseWriter, r *http.Request) {
|
||||||
collectionID := chi.URLParam(r, "id")
|
collectionID := chi.URLParam(r, "id")
|
||||||
siteID := r.URL.Query().Get("site_id")
|
siteID := r.URL.Query().Get("site_id")
|
||||||
|
|
||||||
@@ -415,13 +415,23 @@ func (h *ContentHandler) GetCollectionTemplates(w http.ResponseWriter, r *http.R
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get collection container
|
||||||
|
collection, err := h.repository.GetCollection(context.Background(), siteID, collectionID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("Collection not found: %v", err), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all templates for this collection
|
||||||
templates, err := h.repository.GetCollectionTemplates(context.Background(), siteID, collectionID)
|
templates, err := h.repository.GetCollectionTemplates(context.Background(), siteID, collectionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("Database error: %v", err), http.StatusInternalServerError)
|
http.Error(w, fmt.Sprintf("Templates not found: %v", err), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := map[string]interface{}{
|
response := map[string]interface{}{
|
||||||
|
"collection_id": collectionID,
|
||||||
|
"container_html": collection.ContainerHTML,
|
||||||
"templates": templates,
|
"templates": templates,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -528,7 +538,7 @@ func (h *ContentHandler) RegisterRoutes(r chi.Router) {
|
|||||||
r.Get("/", h.GetAllCollections) // GET /api/collections?site_id=X
|
r.Get("/", h.GetAllCollections) // GET /api/collections?site_id=X
|
||||||
r.Get("/{id}", h.GetCollection) // GET /api/collections/{id}?site_id=X
|
r.Get("/{id}", h.GetCollection) // GET /api/collections/{id}?site_id=X
|
||||||
r.Get("/{id}/items", h.GetCollectionItems) // GET /api/collections/{id}/items?site_id=X
|
r.Get("/{id}/items", h.GetCollectionItems) // GET /api/collections/{id}/items?site_id=X
|
||||||
r.Get("/{id}/templates", h.GetCollectionTemplates) // GET /api/collections/{id}/templates?site_id=X
|
r.Get("/{id}/preview", h.GetCollectionPreview) // GET /api/collections/{id}/preview?site_id=X
|
||||||
|
|
||||||
// Protected routes
|
// Protected routes
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
|
|||||||
@@ -243,25 +243,25 @@ export class ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get available templates for a collection
|
* Get collection preview data (container + templates for frontend reconstruction)
|
||||||
* @param {string} collectionId - Collection ID
|
* @param {string} collectionId - Collection ID
|
||||||
* @returns {Promise<Array>} Array of collection templates
|
* @returns {Promise<Object>} Object with collection_id, container_html, and templates
|
||||||
*/
|
*/
|
||||||
async getCollectionTemplates(collectionId) {
|
async getCollectionPreview(collectionId) {
|
||||||
try {
|
try {
|
||||||
const collectionsUrl = this.getCollectionsUrl();
|
const collectionsUrl = this.getCollectionsUrl();
|
||||||
const response = await fetch(`${collectionsUrl}/${collectionId}/templates?site_id=${this.siteId}`);
|
const response = await fetch(`${collectionsUrl}/${collectionId}/preview?site_id=${this.siteId}`);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
return result.templates || [];
|
return result;
|
||||||
} else {
|
} else {
|
||||||
console.warn(`⚠️ Failed to fetch collection templates (${response.status}): ${collectionId}`);
|
console.warn(`⚠️ Failed to fetch collection preview (${response.status}): ${collectionId}`);
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch collection templates:', collectionId, error);
|
console.error('Failed to fetch collection preview:', collectionId, error);
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1098,3 +1098,147 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =================================================================
|
||||||
|
LIVE COLLECTION PREVIEW MODAL
|
||||||
|
================================================================= */
|
||||||
|
|
||||||
|
.insertr-collection-preview-modal {
|
||||||
|
background: var(--insertr-bg-primary);
|
||||||
|
color: var(--insertr-text-primary);
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||||
|
max-width: 90vw;
|
||||||
|
max-height: 90vh;
|
||||||
|
width: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: var(--insertr-z-modal);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-header {
|
||||||
|
padding: var(--insertr-spacing-lg);
|
||||||
|
border-bottom: 1px solid var(--insertr-border-color);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-header h3 {
|
||||||
|
margin: 0 0 var(--insertr-spacing-xs) 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--insertr-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-header p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--insertr-text-secondary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-container {
|
||||||
|
padding: var(--insertr-spacing-lg);
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-actions {
|
||||||
|
padding: var(--insertr-spacing-md) var(--insertr-spacing-lg);
|
||||||
|
border-top: 1px solid var(--insertr-border-color);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--insertr-spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Preview item selection styling */
|
||||||
|
.insertr-preview-item {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
position: relative;
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-item:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: 0 4px 20px rgba(59, 130, 246, 0.25);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-item::after {
|
||||||
|
content: 'Click to select';
|
||||||
|
position: absolute;
|
||||||
|
top: var(--insertr-spacing-xs);
|
||||||
|
right: var(--insertr-spacing-xs);
|
||||||
|
background: rgba(59, 130, 246, 0.95);
|
||||||
|
color: white;
|
||||||
|
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-item:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced hover effect for better visual feedback */
|
||||||
|
.insertr-preview-item:hover {
|
||||||
|
outline: 2px solid var(--insertr-primary-color);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button styling for preview modal */
|
||||||
|
.insertr-template-btn {
|
||||||
|
background: var(--insertr-bg-secondary);
|
||||||
|
color: var(--insertr-text-primary);
|
||||||
|
border: 1px solid var(--insertr-border-color);
|
||||||
|
border-radius: var(--insertr-border-radius);
|
||||||
|
padding: var(--insertr-spacing-sm) var(--insertr-spacing-md);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-template-btn:hover {
|
||||||
|
background: var(--insertr-bg-hover);
|
||||||
|
border-color: var(--insertr-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-template-btn-cancel {
|
||||||
|
background: var(--insertr-bg-secondary);
|
||||||
|
color: var(--insertr-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-template-btn-cancel:hover {
|
||||||
|
background: var(--insertr-danger-color);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--insertr-danger-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile responsiveness for preview modal */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.insertr-collection-preview-modal {
|
||||||
|
max-width: 95vw;
|
||||||
|
max-height: 95vh;
|
||||||
|
margin: var(--insertr-spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-container {
|
||||||
|
padding: var(--insertr-spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-item:hover {
|
||||||
|
transform: scale(1.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
.insertr-preview-item::after {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
padding: 2px 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class CollectionManager {
|
|||||||
this.template = null;
|
this.template = null;
|
||||||
this.items = [];
|
this.items = [];
|
||||||
this.isActive = false;
|
this.isActive = false;
|
||||||
this.cachedTemplates = null; // Cache for available templates
|
this.cachedPreview = null; // Cache for collection preview data
|
||||||
|
|
||||||
// UI elements
|
// UI elements
|
||||||
this.addButton = null;
|
this.addButton = null;
|
||||||
@@ -412,16 +412,16 @@ export class CollectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Get available templates for this collection
|
// 1. Get collection preview data
|
||||||
const templates = await this.getAvailableTemplates();
|
const previewData = await this.getCollectionPreview();
|
||||||
if (!templates || templates.length === 0) {
|
if (!previewData || !previewData.templates || previewData.templates.length === 0) {
|
||||||
console.error('❌ No templates available for collection:', this.collectionId);
|
console.error('❌ No templates available for collection:', this.collectionId);
|
||||||
alert('No templates available for this collection. Please refresh the page.');
|
alert('No templates available for this collection. Please refresh the page.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Select template (auto-select if only one, otherwise present options)
|
// 2. Select template (auto-select if only one, otherwise show live preview)
|
||||||
const selectedTemplate = await this.selectTemplate(templates);
|
const selectedTemplate = await this.selectTemplate(previewData);
|
||||||
if (!selectedTemplate) {
|
if (!selectedTemplate) {
|
||||||
console.log('Template selection cancelled by user');
|
console.log('Template selection cancelled by user');
|
||||||
return;
|
return;
|
||||||
@@ -465,46 +465,48 @@ export class CollectionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get available templates for this collection
|
* Get collection preview data (container + templates)
|
||||||
* @returns {Promise<Array>} Array of template objects
|
* @returns {Promise<Object>} Object with collection_id, container_html, and templates
|
||||||
*/
|
*/
|
||||||
async getAvailableTemplates() {
|
async getCollectionPreview() {
|
||||||
try {
|
try {
|
||||||
if (!this.cachedTemplates) {
|
if (!this.cachedPreview) {
|
||||||
console.log('🔍 Fetching templates for collection:', this.collectionId);
|
console.log('🔍 Fetching preview for collection:', this.collectionId);
|
||||||
this.cachedTemplates = await this.apiClient.getCollectionTemplates(this.collectionId);
|
this.cachedPreview = await this.apiClient.getCollectionPreview(this.collectionId);
|
||||||
console.log('📋 Templates fetched:', this.cachedTemplates);
|
console.log('📋 Preview fetched:', this.cachedPreview);
|
||||||
}
|
}
|
||||||
return this.cachedTemplates;
|
return this.cachedPreview;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to fetch templates for collection:', this.collectionId, error);
|
console.error('❌ Failed to fetch preview for collection:', this.collectionId, error);
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a template for creating new items
|
* Select a template for creating new items
|
||||||
* @param {Array} templates - Available templates
|
* @param {Object} previewData - Preview data with container_html and templates
|
||||||
* @returns {Promise<Object|null>} Selected template or null if cancelled
|
* @returns {Promise<Object|null>} Selected template or null if cancelled
|
||||||
*/
|
*/
|
||||||
async selectTemplate(templates) {
|
async selectTemplate(previewData) {
|
||||||
|
const templates = previewData.templates;
|
||||||
|
|
||||||
// Auto-select if only one template
|
// Auto-select if only one template
|
||||||
if (templates.length === 1) {
|
if (templates.length === 1) {
|
||||||
console.log('🎯 Auto-selecting single template:', templates[0].name);
|
console.log('🎯 Auto-selecting single template:', templates[0].name);
|
||||||
return templates[0];
|
return templates[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present selection UI for multiple templates
|
// Present live collection preview for multiple templates
|
||||||
console.log('🎨 Multiple templates available, showing selection UI');
|
console.log('🎨 Multiple templates available, showing live preview');
|
||||||
return this.showTemplateSelectionModal(templates);
|
return this.showLiveCollectionPreview(previewData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show template selection modal
|
* Show live collection preview for template selection
|
||||||
* @param {Array} templates - Available templates
|
* @param {Object} previewData - Preview data with container_html and templates
|
||||||
* @returns {Promise<Object|null>} Selected template or null if cancelled
|
* @returns {Promise<Object|null>} Selected template or null if cancelled
|
||||||
*/
|
*/
|
||||||
async showTemplateSelectionModal(templates) {
|
async showLiveCollectionPreview(previewData) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// Create modal overlay
|
// Create modal overlay
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
@@ -512,73 +514,38 @@ export class CollectionManager {
|
|||||||
|
|
||||||
// Create modal content
|
// Create modal content
|
||||||
const modal = document.createElement('div');
|
const modal = document.createElement('div');
|
||||||
modal.className = 'insertr-template-selector';
|
modal.className = 'insertr-collection-preview-modal';
|
||||||
|
|
||||||
|
// Generate live preview by reconstructing collection with all templates
|
||||||
|
const previewHTML = this.generateLivePreview(previewData.container_html, previewData.templates);
|
||||||
|
|
||||||
// Create modal HTML using CSS classes
|
|
||||||
modal.innerHTML = `
|
modal.innerHTML = `
|
||||||
|
<div class="insertr-preview-header">
|
||||||
<h3>Choose Template</h3>
|
<h3>Choose Template</h3>
|
||||||
<div class="insertr-template-options">
|
<p>Click on the item you want to add</p>
|
||||||
${templates.map(template => `
|
|
||||||
<div class="insertr-template-option ${template.is_default ? 'insertr-template-default' : ''}"
|
|
||||||
data-template-id="${template.template_id}">
|
|
||||||
<div class="insertr-template-name">
|
|
||||||
${template.name}${template.is_default ? ' (default)' : ''}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="insertr-template-preview-container">
|
<div class="insertr-preview-container">
|
||||||
${this.createStyledTemplatePreview(template.html_template)}
|
${previewHTML}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="insertr-preview-actions">
|
||||||
`).join('')}
|
|
||||||
</div>
|
|
||||||
<div class="insertr-template-actions">
|
|
||||||
<button class="insertr-template-btn insertr-template-btn-cancel">Cancel</button>
|
<button class="insertr-template-btn insertr-template-btn-cancel">Cancel</button>
|
||||||
<button class="insertr-template-btn insertr-template-btn-select" disabled>Add Item</button>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
let selectedTemplate = null;
|
// Handle template selection by clicking on preview items
|
||||||
let userHasInteracted = false;
|
modal.addEventListener('click', (e) => {
|
||||||
|
const previewItem = e.target.closest('.insertr-preview-item');
|
||||||
|
if (previewItem) {
|
||||||
|
const templateId = parseInt(previewItem.dataset.templateId);
|
||||||
|
const selectedTemplate = previewData.templates.find(t => t.template_id === templateId);
|
||||||
|
|
||||||
// Add event listeners for template selection
|
if (selectedTemplate) {
|
||||||
modal.querySelectorAll('.insertr-template-option').forEach(option => {
|
console.log('🎯 Template selected from preview:', selectedTemplate.name);
|
||||||
option.addEventListener('click', (e) => {
|
document.body.removeChild(overlay);
|
||||||
// Mark that user has interacted
|
resolve(selectedTemplate);
|
||||||
userHasInteracted = true;
|
|
||||||
|
|
||||||
// Remove previous selection from all options
|
|
||||||
modal.querySelectorAll('.insertr-template-option').forEach(opt => {
|
|
||||||
opt.classList.remove('insertr-template-selected');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add selection class to clicked option
|
|
||||||
option.classList.add('insertr-template-selected');
|
|
||||||
|
|
||||||
// Find selected template
|
|
||||||
const templateId = parseInt(option.dataset.templateId);
|
|
||||||
selectedTemplate = templates.find(t => t.template_id === templateId);
|
|
||||||
|
|
||||||
// Enable select button
|
|
||||||
const selectBtn = modal.querySelector('.insertr-template-btn-select');
|
|
||||||
selectBtn.disabled = false;
|
|
||||||
|
|
||||||
console.log('🎯 Template selected by user:', selectedTemplate.name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auto-select default template only if user hasn't interacted
|
|
||||||
const defaultTemplate = templates.find(t => t.is_default);
|
|
||||||
if (defaultTemplate) {
|
|
||||||
// Set initial selection without triggering click event
|
|
||||||
selectedTemplate = defaultTemplate;
|
|
||||||
const defaultOption = modal.querySelector(`[data-template-id="${defaultTemplate.template_id}"]`);
|
|
||||||
if (defaultOption) {
|
|
||||||
// Apply visual selection without click event
|
|
||||||
defaultOption.classList.add('insertr-template-selected');
|
|
||||||
const selectBtn = modal.querySelector('.insertr-template-btn-select');
|
|
||||||
selectBtn.disabled = false;
|
|
||||||
console.log('🎯 Default template pre-selected:', defaultTemplate.name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Cancel button handler
|
// Cancel button handler
|
||||||
modal.querySelector('.insertr-template-btn-cancel').addEventListener('click', () => {
|
modal.querySelector('.insertr-template-btn-cancel').addEventListener('click', () => {
|
||||||
@@ -586,13 +553,6 @@ export class CollectionManager {
|
|||||||
resolve(null);
|
resolve(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Select button handler
|
|
||||||
modal.querySelector('.insertr-template-btn-select').addEventListener('click', () => {
|
|
||||||
console.log('✅ Adding item with template:', selectedTemplate ? selectedTemplate.name : 'none');
|
|
||||||
document.body.removeChild(overlay);
|
|
||||||
resolve(selectedTemplate);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close on overlay click
|
// Close on overlay click
|
||||||
overlay.addEventListener('click', (e) => {
|
overlay.addEventListener('click', (e) => {
|
||||||
if (e.target === overlay) {
|
if (e.target === overlay) {
|
||||||
@@ -983,4 +943,57 @@ export class CollectionManager {
|
|||||||
|
|
||||||
console.log('🧹 CollectionManager destroyed');
|
console.log('🧹 CollectionManager destroyed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate live collection preview by reconstructing container with all template variants
|
||||||
|
* @param {string} containerHTML - Collection container HTML
|
||||||
|
* @param {Array} templates - Array of template objects
|
||||||
|
* @returns {string} HTML string with reconstructed collection
|
||||||
|
*/
|
||||||
|
generateLivePreview(containerHTML, templates) {
|
||||||
|
try {
|
||||||
|
// Parse the container HTML
|
||||||
|
const tempContainer = document.createElement('div');
|
||||||
|
tempContainer.innerHTML = containerHTML;
|
||||||
|
const collectionContainer = tempContainer.querySelector('.insertr-add');
|
||||||
|
|
||||||
|
if (!collectionContainer) {
|
||||||
|
console.error('❌ No .insertr-add container found in collection HTML');
|
||||||
|
return '<div>Preview generation failed</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing children
|
||||||
|
collectionContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Add one instance of each template with preview classes
|
||||||
|
templates.forEach(template => {
|
||||||
|
// Parse template HTML
|
||||||
|
const templateContainer = document.createElement('div');
|
||||||
|
templateContainer.innerHTML = template.html_template;
|
||||||
|
const templateElement = templateContainer.firstElementChild;
|
||||||
|
|
||||||
|
if (templateElement) {
|
||||||
|
// Clone the template element
|
||||||
|
const previewElement = templateElement.cloneNode(true);
|
||||||
|
|
||||||
|
// Add preview classes and data attributes for selection
|
||||||
|
previewElement.classList.add('insertr-preview-item');
|
||||||
|
previewElement.setAttribute('data-template-id', template.template_id);
|
||||||
|
previewElement.setAttribute('data-template-name', template.name);
|
||||||
|
|
||||||
|
// Add the preview element to the collection container
|
||||||
|
collectionContainer.appendChild(previewElement);
|
||||||
|
|
||||||
|
console.log(`✅ Added template ${template.template_id} (${template.name}) to preview`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return the complete collection HTML
|
||||||
|
return tempContainer.innerHTML;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to generate live preview:', error);
|
||||||
|
return '<div>Preview generation failed</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user