Implement atomic collection item creation API with unified content engine approach
Updates collection creation to use database-first atomic operations for reliable collection item management. Replaces manual database calls with unified content engine methods that handle content extraction, storage, and structural template generation consistently. Key changes: - Replace manual database operations in CreateCollectionItem handler with DatabaseClient.CreateCollectionItemAtomic() - Implement unified content engine approach for API-based collection item creation - Add atomic collection item creation methods across all content clients - Enhance reconstruction to use stored structural templates with content ID hydration - Add comprehensive collection management API methods in JavaScript client - Implement collection manager UI with create, delete, and reorder functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,13 @@ export class CollectionManager {
|
||||
this.apiClient = apiClient;
|
||||
this.auth = auth;
|
||||
|
||||
// Extract collection ID from container
|
||||
this.collectionId = this.container.getAttribute('data-content-id');
|
||||
if (!this.collectionId) {
|
||||
console.error('❌ Collection container missing data-content-id attribute');
|
||||
return;
|
||||
}
|
||||
|
||||
// Collection state
|
||||
this.template = null;
|
||||
this.items = [];
|
||||
@@ -26,19 +33,22 @@ export class CollectionManager {
|
||||
this.addButton = null;
|
||||
this.itemControls = new Map(); // Map item element to its controls
|
||||
|
||||
console.log('🔄 CollectionManager initialized for:', this.container);
|
||||
console.log('🔄 CollectionManager initialized for:', this.container, 'Collection ID:', this.collectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the collection manager
|
||||
*/
|
||||
initialize() {
|
||||
async initialize() {
|
||||
if (this.isActive) return;
|
||||
|
||||
console.log('🚀 Starting collection management for:', this.container.className);
|
||||
|
||||
// Analyze existing content to detect template
|
||||
this.analyzeTemplate();
|
||||
|
||||
// Sync with backend to map existing items to collection item IDs
|
||||
await this.syncWithBackend();
|
||||
|
||||
// Add collection management UI only when in edit mode
|
||||
this.setupEditModeDetection();
|
||||
@@ -135,10 +145,12 @@ export class CollectionManager {
|
||||
console.log('📋 Template detected:', this.template);
|
||||
|
||||
// Store reference to current items
|
||||
// For existing items, try to extract collection item IDs if they exist
|
||||
this.items = children.map((child, index) => ({
|
||||
element: child,
|
||||
index: index,
|
||||
id: this.generateItemId(index)
|
||||
id: this.generateItemId(index),
|
||||
collectionItemId: this.extractCollectionItemId(child)
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -227,6 +239,61 @@ export class CollectionManager {
|
||||
generateItemId(index) {
|
||||
return `item-${Date.now()}-${index}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract collection item ID from existing DOM element
|
||||
* This is used for existing items that were reconstructed from database
|
||||
*/
|
||||
extractCollectionItemId(element) {
|
||||
// Look for data-collection-item-id attribute first (newly created items)
|
||||
let itemId = element.getAttribute('data-collection-item-id');
|
||||
if (itemId) {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
// For existing items reconstructed from database, try to infer from data-content-id
|
||||
// The backend should have generated collection item IDs based on collection ID
|
||||
const contentId = element.getAttribute('data-content-id');
|
||||
if (contentId && this.collectionId) {
|
||||
// This is a heuristic - we'll need to fetch the actual mapping from the backend
|
||||
// For now, return null and let the backend operations handle missing IDs
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync frontend state with backend collection items
|
||||
* This maps existing DOM elements to their collection item IDs
|
||||
*/
|
||||
async syncWithBackend() {
|
||||
if (!this.collectionId) {
|
||||
console.warn('⚠️ Cannot sync with backend: no collection ID');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch current collection items from backend
|
||||
const backendItems = await this.apiClient.getCollectionItems(this.collectionId);
|
||||
console.log('📋 Backend collection items:', backendItems);
|
||||
|
||||
// Map backend items to existing DOM elements by position
|
||||
// This assumes the DOM order matches the database order
|
||||
backendItems.forEach((backendItem, index) => {
|
||||
if (this.items[index]) {
|
||||
this.items[index].collectionItemId = backendItem.item_id;
|
||||
this.items[index].element.setAttribute('data-collection-item-id', backendItem.item_id);
|
||||
console.log(`🔗 Mapped DOM element ${index} to collection item ${backendItem.item_id}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ Frontend-backend sync completed');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to sync with backend:', error);
|
||||
// Continue without backend sync - collection management will still work for new items
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the "+ Add" button positioned in top right of container
|
||||
@@ -334,45 +401,85 @@ export class CollectionManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new item to the collection
|
||||
* Add a new item to the collection (backend-first approach)
|
||||
*/
|
||||
addNewItem() {
|
||||
async addNewItem() {
|
||||
console.log('➕ Adding new item to collection');
|
||||
|
||||
if (!this.template) {
|
||||
console.error('❌ No template available for creating new items');
|
||||
if (!this.template || !this.collectionId) {
|
||||
console.error('❌ No template or collection ID available for creating new items');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new item from template
|
||||
const newItem = this.createItemFromTemplate();
|
||||
|
||||
// Add to DOM
|
||||
this.container.insertBefore(newItem, this.addButton);
|
||||
|
||||
// Update items array
|
||||
const newItemData = {
|
||||
element: newItem,
|
||||
index: this.items.length,
|
||||
id: this.generateItemId(this.items.length)
|
||||
};
|
||||
this.items.push(newItemData);
|
||||
|
||||
// Add controls to new item
|
||||
this.addItemControls(newItem, this.items.length - 1);
|
||||
|
||||
// Re-initialize any .insertr elements in the new item
|
||||
// This allows the existing editor system to handle individual field editing
|
||||
this.initializeInsertrElements(newItem);
|
||||
|
||||
// Update all item controls (indices may have changed)
|
||||
this.updateAllItemControls();
|
||||
|
||||
console.log('✅ New item added successfully');
|
||||
|
||||
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);
|
||||
|
||||
// 2. Create DOM element from the returned collection item data
|
||||
const newItem = this.createItemFromCollectionData(collectionItem);
|
||||
|
||||
// 3. Add to DOM
|
||||
this.container.insertBefore(newItem, this.addButton);
|
||||
|
||||
// 4. Update items array with backend data
|
||||
const newItemData = {
|
||||
element: newItem,
|
||||
index: this.items.length,
|
||||
id: collectionItem.item_id,
|
||||
collectionItem: collectionItem
|
||||
};
|
||||
this.items.push(newItemData);
|
||||
|
||||
// 5. Add controls to new item
|
||||
this.addItemControls(newItem, this.items.length - 1);
|
||||
|
||||
// 6. Re-initialize any .insertr elements in the new item
|
||||
this.initializeInsertrElements(newItem);
|
||||
|
||||
// 7. Update all item controls (indices may have changed)
|
||||
this.updateAllItemControls();
|
||||
|
||||
// 8. Trigger site enhancement to update static files
|
||||
await this.apiClient.enhanceSite();
|
||||
|
||||
console.log('✅ New item added successfully:', collectionItem.item_id);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to add new collection item:', error);
|
||||
alert('Failed to add new item. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new item from the template
|
||||
* Create a DOM element from collection item data returned by backend
|
||||
* Backend is the source of truth - use its HTML content directly
|
||||
*/
|
||||
createItemFromCollectionData(collectionItem) {
|
||||
// Use backend HTML content directly (database is source of truth)
|
||||
if (collectionItem.html_content && collectionItem.html_content.trim()) {
|
||||
const tempContainer = document.createElement('div');
|
||||
tempContainer.innerHTML = collectionItem.html_content;
|
||||
const newItem = tempContainer.firstElementChild;
|
||||
|
||||
// Set the collection item ID as data attribute for future reference
|
||||
newItem.setAttribute('data-collection-item-id', collectionItem.item_id);
|
||||
|
||||
return newItem;
|
||||
} else {
|
||||
// Fallback: create from frontend template if backend content is empty
|
||||
const tempContainer = document.createElement('div');
|
||||
tempContainer.innerHTML = this.template.htmlTemplate;
|
||||
const newItem = tempContainer.firstElementChild;
|
||||
|
||||
// Set the collection item ID as data attribute for future reference
|
||||
newItem.setAttribute('data-collection-item-id', collectionItem.item_id);
|
||||
|
||||
return newItem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new item from the template (legacy method, kept for compatibility)
|
||||
*/
|
||||
createItemFromTemplate() {
|
||||
// Create element from template HTML
|
||||
@@ -511,38 +618,60 @@ export class CollectionManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the collection
|
||||
* Remove an item from the collection (backend-first approach)
|
||||
*/
|
||||
removeItem(itemElement) {
|
||||
async removeItem(itemElement) {
|
||||
if (!confirm('Are you sure you want to remove this item?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🗑️ Removing item from collection');
|
||||
|
||||
// Remove controls
|
||||
const controls = this.itemControls.get(itemElement);
|
||||
if (controls) {
|
||||
controls.remove();
|
||||
this.itemControls.delete(itemElement);
|
||||
|
||||
try {
|
||||
// 1. Get the collection item ID from the element
|
||||
const collectionItemId = itemElement.getAttribute('data-collection-item-id');
|
||||
if (!collectionItemId) {
|
||||
console.error('❌ Cannot remove item: missing data-collection-item-id attribute');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Delete from database first (backend-first approach)
|
||||
const success = await this.apiClient.deleteCollectionItem(this.collectionId, collectionItemId);
|
||||
if (!success) {
|
||||
alert('Failed to remove item from database. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Remove controls
|
||||
const controls = this.itemControls.get(itemElement);
|
||||
if (controls) {
|
||||
controls.remove();
|
||||
this.itemControls.delete(itemElement);
|
||||
}
|
||||
|
||||
// 4. Remove from items array
|
||||
this.items = this.items.filter(item => item.element !== itemElement);
|
||||
|
||||
// 5. Remove from DOM
|
||||
itemElement.remove();
|
||||
|
||||
// 6. Update all item controls (indices changed)
|
||||
this.updateAllItemControls();
|
||||
|
||||
// 7. Trigger site enhancement to update static files
|
||||
await this.apiClient.enhanceSite();
|
||||
|
||||
console.log('✅ Item removed successfully:', collectionItemId);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to remove collection item:', error);
|
||||
alert('Failed to remove item. Please try again.');
|
||||
}
|
||||
|
||||
// Remove from items array
|
||||
this.items = this.items.filter(item => item.element !== itemElement);
|
||||
|
||||
// Remove from DOM
|
||||
itemElement.remove();
|
||||
|
||||
// Update all item controls (indices changed)
|
||||
this.updateAllItemControls();
|
||||
|
||||
console.log('✅ Item removed successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an item up or down in the collection
|
||||
* Move an item up or down in the collection (backend-first approach)
|
||||
*/
|
||||
moveItem(itemElement, direction) {
|
||||
async moveItem(itemElement, direction) {
|
||||
console.log(`🔄 Moving item ${direction}`);
|
||||
|
||||
const currentIndex = this.items.findIndex(item => item.element === itemElement);
|
||||
@@ -556,26 +685,49 @@ export class CollectionManager {
|
||||
} else {
|
||||
return; // Can't move in that direction
|
||||
}
|
||||
|
||||
// Get the target position in DOM
|
||||
const targetItem = this.items[newIndex];
|
||||
|
||||
// Move in DOM
|
||||
if (direction === 'up') {
|
||||
this.container.insertBefore(itemElement, targetItem.element);
|
||||
} else {
|
||||
this.container.insertBefore(itemElement, targetItem.element.nextSibling);
|
||||
|
||||
try {
|
||||
// 1. Get the collection item ID
|
||||
const collectionItemId = itemElement.getAttribute('data-collection-item-id');
|
||||
if (!collectionItemId) {
|
||||
console.error('❌ Cannot move item: missing data-collection-item-id attribute');
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Update position in database first (backend-first approach)
|
||||
// Note: Backend expects 0-based positions, but we may need to adjust based on backend implementation
|
||||
const success = await this.apiClient.updateCollectionItemPosition(this.collectionId, collectionItemId, newIndex);
|
||||
if (!success) {
|
||||
alert('Failed to update item position in database. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Get the target position in DOM
|
||||
const targetItem = this.items[newIndex];
|
||||
|
||||
// 4. Move in DOM
|
||||
if (direction === 'up') {
|
||||
this.container.insertBefore(itemElement, targetItem.element);
|
||||
} else {
|
||||
this.container.insertBefore(itemElement, targetItem.element.nextSibling);
|
||||
}
|
||||
|
||||
// 5. Update items array
|
||||
[this.items[currentIndex], this.items[newIndex]] = [this.items[newIndex], this.items[currentIndex]];
|
||||
this.items[currentIndex].index = currentIndex;
|
||||
this.items[newIndex].index = newIndex;
|
||||
|
||||
// 6. Update all item controls
|
||||
this.updateAllItemControls();
|
||||
|
||||
// 7. Trigger site enhancement to update static files
|
||||
await this.apiClient.enhanceSite();
|
||||
|
||||
console.log('✅ Item moved successfully:', collectionItemId, '→ position', newIndex);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to move collection item:', error);
|
||||
alert('Failed to move item. Please try again.');
|
||||
}
|
||||
|
||||
// Update items array
|
||||
[this.items[currentIndex], this.items[newIndex]] = [this.items[newIndex], this.items[currentIndex]];
|
||||
this.items[currentIndex].index = currentIndex;
|
||||
this.items[newIndex].index = newIndex;
|
||||
|
||||
// Update all item controls
|
||||
this.updateAllItemControls();
|
||||
|
||||
console.log('✅ Item moved successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user