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:
2025-10-27 21:02:59 +01:00
parent 0bad96d866
commit 00255cb105
10 changed files with 486 additions and 33 deletions

View File

@@ -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