Implement complete collection persistence with database-backed survival across server restarts

• Add full multi-table schema for collections with normalized design (collections, collection_templates, collection_items, collection_item_versions)
• Implement collection detection and processing in enhancement pipeline for .insertr-add elements
• Add template extraction and storage from existing HTML children with multi-variant support
• Enable collection reconstruction from database on server restart with proper DOM rebuilding
• Extend ContentClient interface with collection operations and full database integration
• Update enhance command to use engine.DatabaseClient for collection persistence support
This commit is contained in:
2025-09-22 18:29:58 +02:00
parent b25663f76b
commit 2315ba4750
36 changed files with 4356 additions and 46 deletions

View File

@@ -827,4 +827,130 @@ body:not(.insertr-edit-mode) .insertr-editing-hover::after {
margin-right: 0;
order: -1;
}
}
/* =================================================================
COLLECTION MANAGEMENT STYLES (.insertr-add)
================================================================= */
/* Collection container when active */
.insertr-collection-active {
outline: 2px dashed var(--insertr-primary);
outline-offset: 4px;
border-radius: var(--insertr-border-radius);
}
/* Add button positioned in top right of container */
.insertr-add-btn {
position: absolute;
top: -12px;
right: -12px;
background: var(--insertr-primary);
color: var(--insertr-text-inverse);
border: none;
padding: var(--insertr-spacing-xs) var(--insertr-spacing-sm);
border-radius: var(--insertr-border-radius);
font-size: var(--insertr-font-size-sm);
font-weight: 600;
cursor: pointer;
z-index: var(--insertr-z-floating);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
.insertr-add-btn:hover {
background: var(--insertr-primary-hover);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.insertr-add-btn:active {
transform: translateY(0);
}
/* Item controls positioned in top right corner of each item */
.insertr-item-controls {
position: absolute;
top: 8px;
right: 8px;
display: flex;
gap: 2px;
opacity: 0;
transition: opacity 0.2s ease;
z-index: var(--insertr-z-floating);
}
/* Individual control buttons */
.insertr-control-btn {
width: 20px;
height: 20px;
background: var(--insertr-bg-primary);
border: 1px solid var(--insertr-border-color);
border-radius: 3px;
font-size: 12px;
font-weight: bold;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--insertr-text-primary);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: all 0.15s ease;
}
.insertr-control-btn:hover {
background: var(--insertr-bg-secondary);
border-color: var(--insertr-primary);
color: var(--insertr-primary);
transform: scale(1.1);
}
/* Remove button specific styling */
.insertr-control-btn:last-child {
color: var(--insertr-danger);
}
.insertr-control-btn:last-child:hover {
background: var(--insertr-danger);
color: var(--insertr-text-inverse);
border-color: var(--insertr-danger);
}
/* Collection items hover state */
.insertr-collection-active > *:hover {
background: rgba(0, 123, 255, 0.03);
outline: 1px solid rgba(var(--insertr-primary), 0.2);
outline-offset: 2px;
border-radius: var(--insertr-border-radius);
}
/* Show item controls on hover */
.insertr-collection-active > *:hover .insertr-item-controls {
opacity: 1;
}
/* Responsive adjustments for collection management */
@media (max-width: 768px) {
.insertr-add-btn {
position: static;
display: block;
margin: var(--insertr-spacing-sm) auto 0;
width: 100%;
max-width: 200px;
}
.insertr-item-controls {
position: relative;
opacity: 1;
top: auto;
right: auto;
justify-content: center;
margin-top: var(--insertr-spacing-xs);
}
.insertr-control-btn {
width: 32px;
height: 32px;
font-size: 14px;
}
}

View File

@@ -171,3 +171,20 @@ func (c *HTTPClient) CreateContent(siteID, contentID, htmlContent, originalTempl
// This would typically be used in API-driven enhancement scenarios
return nil, fmt.Errorf("CreateContent not implemented for HTTPClient - use DatabaseClient for enhancement")
}
// Collection method stubs - TODO: Implement these for HTTP API
func (c *HTTPClient) GetCollection(siteID, collectionID string) (*engine.CollectionItem, error) {
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
}
func (c *HTTPClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*engine.CollectionItem, error) {
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
}
func (c *HTTPClient) GetCollectionItems(siteID, collectionID string) ([]engine.CollectionItemWithTemplate, error) {
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
}
func (c *HTTPClient) CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*engine.CollectionTemplateItem, error) {
return nil, fmt.Errorf("collection operations not implemented in HTTPClient")
}

View File

@@ -233,3 +233,20 @@ func toNullString(s string) sql.NullString {
}
return sql.NullString{String: s, Valid: true}
}
// Collection method stubs - TODO: Implement these
func (d *DatabaseClient) GetCollection(siteID, collectionID string) (*engine.CollectionItem, error) {
return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient")
}
func (d *DatabaseClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*engine.CollectionItem, error) {
return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient")
}
func (d *DatabaseClient) GetCollectionItems(siteID, collectionID string) ([]engine.CollectionItemWithTemplate, error) {
return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient")
}
func (d *DatabaseClient) CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*engine.CollectionTemplateItem, error) {
return nil, fmt.Errorf("collection operations not implemented in content.DatabaseClient")
}

View File

@@ -1,6 +1,7 @@
package content
import (
"fmt"
"time"
"github.com/insertr/insertr/internal/engine"
@@ -156,3 +157,20 @@ func (m *MockClient) CreateContent(siteID, contentID, htmlContent, originalTempl
return &item, nil
}
// Collection method stubs - TODO: Implement these for mock testing
func (m *MockClient) GetCollection(siteID, collectionID string) (*engine.CollectionItem, error) {
return nil, fmt.Errorf("collection operations not implemented in MockClient")
}
func (m *MockClient) CreateCollection(siteID, collectionID, containerHTML, lastEditedBy string) (*engine.CollectionItem, error) {
return nil, fmt.Errorf("collection operations not implemented in MockClient")
}
func (m *MockClient) GetCollectionItems(siteID, collectionID string) ([]engine.CollectionItemWithTemplate, error) {
return nil, fmt.Errorf("collection operations not implemented in MockClient")
}
func (m *MockClient) CreateCollectionTemplate(siteID, collectionID, name, htmlTemplate string, isDefault bool) (*engine.CollectionTemplateItem, error) {
return nil, fmt.Errorf("collection operations not implemented in MockClient")
}