Implement class-based template differentiation and fix collection item creation
- Add class-based template comparison to differentiate styling variants - Implement template deduplication based on structure + class signatures - Add GetCollectionTemplate method to repository interface and implementations - Fix collection item creation by replacing unimplemented CreateCollectionItemAtomic - Add template selection modal with auto-default selection in frontend - Generate meaningful template names from distinctive CSS classes - Fix unique constraint violations with timestamp-based collection item IDs - Add collection templates API endpoint for frontend template fetching - Update simple demo with featured/compact/dark testimonial variants for testing
This commit is contained in:
@@ -242,6 +242,29 @@ export class ApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available templates for a collection
|
||||
* @param {string} collectionId - Collection ID
|
||||
* @returns {Promise<Array>} Array of collection templates
|
||||
*/
|
||||
async getCollectionTemplates(collectionId) {
|
||||
try {
|
||||
const collectionsUrl = this.getCollectionsUrl();
|
||||
const response = await fetch(`${collectionsUrl}/${collectionId}/templates?site_id=${this.siteId}`);
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
return result.templates || [];
|
||||
} else {
|
||||
console.warn(`⚠️ Failed to fetch collection templates (${response.status}): ${collectionId}`);
|
||||
return [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch collection templates:', collectionId, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder collection items in bulk
|
||||
* @param {string} collectionId - Collection ID
|
||||
|
||||
@@ -28,6 +28,7 @@ export class CollectionManager {
|
||||
this.template = null;
|
||||
this.items = [];
|
||||
this.isActive = false;
|
||||
this.cachedTemplates = null; // Cache for available templates
|
||||
|
||||
// UI elements
|
||||
this.addButton = null;
|
||||
@@ -411,17 +412,31 @@ export class CollectionManager {
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Create collection item in database first (backend-first approach)
|
||||
const templateId = 1; // Use first template by default
|
||||
const collectionItem = await this.apiClient.createCollectionItem(this.collectionId, templateId);
|
||||
// 1. Get available templates for this collection
|
||||
const templates = await this.getAvailableTemplates();
|
||||
if (!templates || templates.length === 0) {
|
||||
console.error('❌ No templates available for collection:', this.collectionId);
|
||||
alert('No templates available for this collection. Please refresh the page.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Select template (auto-select if only one, otherwise present options)
|
||||
const selectedTemplate = await this.selectTemplate(templates);
|
||||
if (!selectedTemplate) {
|
||||
console.log('Template selection cancelled by user');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Create collection item in database first (backend-first approach)
|
||||
const collectionItem = await this.apiClient.createCollectionItem(this.collectionId, selectedTemplate.template_id);
|
||||
|
||||
// 2. Create DOM element from the returned collection item data
|
||||
// 4. Create DOM element from the returned collection item data
|
||||
const newItem = this.createItemFromCollectionData(collectionItem);
|
||||
|
||||
// 3. Add to DOM
|
||||
// 5. Add to DOM
|
||||
this.container.insertBefore(newItem, this.addButton);
|
||||
|
||||
// 4. Update items array with backend data
|
||||
// 6. Update items array with backend data
|
||||
const newItemData = {
|
||||
element: newItem,
|
||||
index: this.items.length,
|
||||
@@ -430,16 +445,16 @@ export class CollectionManager {
|
||||
};
|
||||
this.items.push(newItemData);
|
||||
|
||||
// 5. Add controls to new item
|
||||
// 7. Add controls to new item
|
||||
this.addItemControls(newItem, this.items.length - 1);
|
||||
|
||||
// 6. Re-initialize any .insertr elements in the new item
|
||||
// 8. Re-initialize any .insertr elements in the new item
|
||||
this.initializeInsertrElements(newItem);
|
||||
|
||||
// 7. Update all item controls (indices may have changed)
|
||||
// 9. Update all item controls (indices may have changed)
|
||||
this.updateAllItemControls();
|
||||
|
||||
// 8. Trigger site enhancement to update static files
|
||||
// 10. Trigger site enhancement to update static files
|
||||
await this.apiClient.enhanceSite();
|
||||
|
||||
console.log('✅ New item added successfully:', collectionItem.item_id);
|
||||
@@ -448,6 +463,190 @@ export class CollectionManager {
|
||||
alert('Failed to add new item. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available templates for this collection
|
||||
* @returns {Promise<Array>} Array of template objects
|
||||
*/
|
||||
async getAvailableTemplates() {
|
||||
try {
|
||||
if (!this.cachedTemplates) {
|
||||
console.log('🔍 Fetching templates for collection:', this.collectionId);
|
||||
this.cachedTemplates = await this.apiClient.getCollectionTemplates(this.collectionId);
|
||||
console.log('📋 Templates fetched:', this.cachedTemplates);
|
||||
}
|
||||
return this.cachedTemplates;
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to fetch templates for collection:', this.collectionId, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a template for creating new items
|
||||
* @param {Array} templates - Available templates
|
||||
* @returns {Promise<Object|null>} Selected template or null if cancelled
|
||||
*/
|
||||
async selectTemplate(templates) {
|
||||
// Auto-select if only one template
|
||||
if (templates.length === 1) {
|
||||
console.log('🎯 Auto-selecting single template:', templates[0].name);
|
||||
return templates[0];
|
||||
}
|
||||
|
||||
// Present selection UI for multiple templates
|
||||
console.log('🎨 Multiple templates available, showing selection UI');
|
||||
return this.showTemplateSelectionModal(templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show template selection modal
|
||||
* @param {Array} templates - Available templates
|
||||
* @returns {Promise<Object|null>} Selected template or null if cancelled
|
||||
*/
|
||||
async showTemplateSelectionModal(templates) {
|
||||
return new Promise((resolve) => {
|
||||
// Create modal overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'insertr-modal-overlay';
|
||||
overlay.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999999;
|
||||
`;
|
||||
|
||||
// Create modal content
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'insertr-template-selector';
|
||||
modal.style.cssText = `
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
`;
|
||||
|
||||
// Create modal HTML
|
||||
modal.innerHTML = `
|
||||
<h3 style="margin: 0 0 16px 0; font-size: 18px; font-weight: 600;">Choose Template</h3>
|
||||
<div class="template-options">
|
||||
${templates.map(template => `
|
||||
<div class="template-option" data-template-id="${template.template_id}" style="
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
${template.is_default ? 'border-color: #3b82f6; background: #eff6ff;' : ''}
|
||||
">
|
||||
<div style="font-weight: 500; margin-bottom: 4px;">
|
||||
${template.name}${template.is_default ? ' (default)' : ''}
|
||||
</div>
|
||||
<div style="font-size: 14px; color: #6b7280; font-family: monospace; background: #f9fafb; padding: 8px; border-radius: 4px; overflow: hidden;">
|
||||
${this.truncateHtml(template.html_template, 100)}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px;">
|
||||
<button class="cancel-btn" style="
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #d1d5db;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
">Cancel</button>
|
||||
<button class="select-btn" style="
|
||||
padding: 8px 16px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
" disabled>Add Item</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let selectedTemplate = null;
|
||||
|
||||
// Add event listeners
|
||||
modal.querySelectorAll('.template-option').forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
// Remove previous selection
|
||||
modal.querySelectorAll('.template-option').forEach(opt => {
|
||||
opt.style.borderColor = '#e5e7eb';
|
||||
opt.style.background = 'white';
|
||||
});
|
||||
|
||||
// Apply selection styling
|
||||
option.style.borderColor = '#3b82f6';
|
||||
option.style.background = '#eff6ff';
|
||||
|
||||
// Find selected template
|
||||
const templateId = parseInt(option.dataset.templateId);
|
||||
selectedTemplate = templates.find(t => t.template_id === templateId);
|
||||
|
||||
// Enable select button
|
||||
const selectBtn = modal.querySelector('.select-btn');
|
||||
selectBtn.disabled = false;
|
||||
selectBtn.style.opacity = '1';
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-select default template after all event listeners are set up
|
||||
const defaultTemplate = templates.find(t => t.is_default);
|
||||
if (defaultTemplate) {
|
||||
const defaultOption = modal.querySelector(`[data-template-id="${defaultTemplate.template_id}"]`);
|
||||
if (defaultOption) {
|
||||
defaultOption.click();
|
||||
}
|
||||
}
|
||||
|
||||
modal.querySelector('.cancel-btn').addEventListener('click', () => {
|
||||
document.body.removeChild(overlay);
|
||||
resolve(null);
|
||||
});
|
||||
|
||||
modal.querySelector('.select-btn').addEventListener('click', () => {
|
||||
document.body.removeChild(overlay);
|
||||
resolve(selectedTemplate);
|
||||
});
|
||||
|
||||
// Close on overlay click
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
document.body.removeChild(overlay);
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
|
||||
overlay.appendChild(modal);
|
||||
document.body.appendChild(overlay);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate HTML for preview
|
||||
* @param {string} html - HTML string
|
||||
* @param {number} maxLength - Maximum character length
|
||||
* @returns {string} Truncated HTML
|
||||
*/
|
||||
truncateHtml(html, maxLength) {
|
||||
if (html.length <= maxLength) return html;
|
||||
return html.substring(0, maxLength) + '...';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a DOM element from collection item data returned by backend
|
||||
|
||||
Reference in New Issue
Block a user