678 lines
22 KiB
Go
678 lines
22 KiB
Go
package engine
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"golang.org/x/net/html"
|
||
)
|
||
|
||
// AuthProvider represents authentication provider information
|
||
type AuthProvider struct {
|
||
Type string // "mock", "jwt", "authentik"
|
||
}
|
||
|
||
// ContentEngine is the unified content processing engine
|
||
type ContentEngine struct {
|
||
idGenerator *IDGenerator
|
||
client ContentClient
|
||
authProvider *AuthProvider
|
||
injector *Injector
|
||
}
|
||
|
||
// NewContentEngine creates a new content processing engine
|
||
func NewContentEngine(client ContentClient) *ContentEngine {
|
||
authProvider := &AuthProvider{Type: "mock"} // default
|
||
return &ContentEngine{
|
||
idGenerator: NewIDGenerator(),
|
||
client: client,
|
||
authProvider: authProvider,
|
||
injector: NewInjector(client, ""), // siteID will be set per operation
|
||
}
|
||
}
|
||
|
||
// NewContentEngineWithAuth creates a new content processing engine with auth config
|
||
func NewContentEngineWithAuth(client ContentClient, authProvider *AuthProvider) *ContentEngine {
|
||
if authProvider == nil {
|
||
authProvider = &AuthProvider{Type: "mock"}
|
||
}
|
||
return &ContentEngine{
|
||
idGenerator: NewIDGenerator(),
|
||
client: client,
|
||
authProvider: authProvider,
|
||
injector: NewInjectorWithAuth(client, "", authProvider), // siteID will be set per operation
|
||
}
|
||
}
|
||
|
||
// ProcessContent processes HTML content according to the specified mode
|
||
func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, error) {
|
||
// 1. Parse HTML
|
||
doc, err := html.Parse(strings.NewReader(string(input.HTML)))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("parsing HTML: %w", err)
|
||
}
|
||
|
||
// 2. Find insertr and collection elements
|
||
insertrElements, collectionElements := e.findEditableElements(doc)
|
||
|
||
// 3. Process regular .insertr elements
|
||
generatedIDs := make(map[string]string)
|
||
processedElements := make([]ProcessedElement, len(insertrElements))
|
||
|
||
for i, elem := range insertrElements {
|
||
// Generate structural ID (always deterministic)
|
||
id := e.idGenerator.Generate(elem.Node, input.FilePath)
|
||
|
||
// Database-first approach: Check if content already exists
|
||
existingContent, err := e.client.GetContent(input.SiteID, id)
|
||
contentExists := (err == nil && existingContent != nil)
|
||
|
||
generatedIDs[fmt.Sprintf("element_%d", i)] = id
|
||
|
||
processedElements[i] = ProcessedElement{
|
||
Node: elem.Node,
|
||
ID: id,
|
||
Generated: !contentExists, // Mark as generated only if new to database
|
||
Tag: elem.Node.Data,
|
||
Classes: GetClasses(elem.Node),
|
||
}
|
||
|
||
// Add/update content attributes to the node (only content-id now)
|
||
e.addContentAttributes(elem.Node, id)
|
||
|
||
// Store content only for truly new elements (database-first check)
|
||
if !contentExists && (input.Mode == Enhancement || input.Mode == ContentInjection) {
|
||
// Extract content and template from the unprocessed element
|
||
htmlContent := e.extractHTMLContent(elem.Node)
|
||
originalTemplate := e.extractOriginalTemplate(elem.Node)
|
||
|
||
// Store in database via content client
|
||
_, err := e.client.CreateContent(input.SiteID, id, htmlContent, originalTemplate, "system")
|
||
if err != nil {
|
||
// Log error but don't fail the enhancement - content just won't be stored
|
||
fmt.Printf("⚠️ Failed to store content for %s: %v\n", id, err)
|
||
} else {
|
||
fmt.Printf("✅ Created new content: %s (html)\n", id)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4. Process .insertr-add collection elements
|
||
for _, collectionElem := range collectionElements {
|
||
// Generate structural ID for the collection container
|
||
collectionID := e.idGenerator.Generate(collectionElem.Node, input.FilePath)
|
||
|
||
// Add data-content-id attribute to the collection container
|
||
e.setAttribute(collectionElem.Node, "data-content-id", collectionID)
|
||
|
||
// Process collection during enhancement or content injection
|
||
if input.Mode == Enhancement || input.Mode == ContentInjection {
|
||
err := e.processCollection(collectionElem.Node, collectionID, input.SiteID)
|
||
if err != nil {
|
||
fmt.Printf("⚠️ Failed to process collection %s: %v\n", collectionID, err)
|
||
} else {
|
||
fmt.Printf("✅ Processed collection: %s\n", collectionID)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 5. Inject content if required by mode
|
||
if input.Mode == Enhancement || input.Mode == ContentInjection {
|
||
err = e.injectContent(processedElements, input.SiteID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("injecting content: %w", err)
|
||
}
|
||
}
|
||
|
||
// TODO: Implement collection-specific content injection here if needed
|
||
|
||
// 6. Inject editor assets for enhancement mode (development)
|
||
if input.Mode == Enhancement {
|
||
injector := NewInjectorWithAuth(e.client, input.SiteID, e.authProvider)
|
||
injector.InjectEditorAssets(doc, true, "")
|
||
}
|
||
|
||
return &ContentResult{
|
||
Document: doc,
|
||
Elements: processedElements,
|
||
GeneratedIDs: generatedIDs,
|
||
}, nil
|
||
}
|
||
|
||
// InsertrElement represents an insertr element found in HTML
|
||
type InsertrElement struct {
|
||
Node *html.Node
|
||
}
|
||
|
||
// CollectionElement represents an insertr-add collection element found in HTML
|
||
type CollectionElement struct {
|
||
Node *html.Node
|
||
}
|
||
|
||
// findEditableElements finds all editable elements (.insertr and .insertr-add)
|
||
func (e *ContentEngine) findEditableElements(doc *html.Node) ([]InsertrElement, []CollectionElement) {
|
||
var insertrElements []InsertrElement
|
||
var collectionElements []CollectionElement
|
||
var containersToTransform []*html.Node
|
||
|
||
// First pass: find all .insertr and .insertr-add elements
|
||
e.walkNodes(doc, func(n *html.Node) {
|
||
if n.Type == html.ElementNode {
|
||
if e.hasInsertrClass(n) {
|
||
if isContainer(n) {
|
||
// Container element - mark for transformation
|
||
containersToTransform = append(containersToTransform, n)
|
||
} else {
|
||
// Regular element - add directly
|
||
insertrElements = append(insertrElements, InsertrElement{
|
||
Node: n,
|
||
})
|
||
}
|
||
} else if e.hasInsertrAddClass(n) {
|
||
// Collection element - add directly (no container transformation for collections)
|
||
collectionElements = append(collectionElements, CollectionElement{
|
||
Node: n,
|
||
})
|
||
}
|
||
}
|
||
})
|
||
|
||
// Second pass: transform .insertr containers (remove .insertr from container, add to children)
|
||
for _, container := range containersToTransform {
|
||
// Remove .insertr class from container
|
||
e.removeClass(container, "insertr")
|
||
|
||
// Find viable children and add .insertr class to them
|
||
viableChildren := FindViableChildren(container)
|
||
for _, child := range viableChildren {
|
||
e.addClass(child, "insertr")
|
||
insertrElements = append(insertrElements, InsertrElement{
|
||
Node: child,
|
||
})
|
||
}
|
||
}
|
||
|
||
return insertrElements, collectionElements
|
||
}
|
||
|
||
// findInsertrElements finds all elements with class="insertr" and applies container transformation
|
||
// This implements the "syntactic sugar transformation" from CLASSES.md:
|
||
// - Containers with .insertr get their .insertr class removed
|
||
// - Viable children of those containers get .insertr class added
|
||
// - Regular elements with .insertr are kept as-is
|
||
func (e *ContentEngine) findInsertrElements(doc *html.Node) []InsertrElement {
|
||
insertrElements, _ := e.findEditableElements(doc)
|
||
return insertrElements
|
||
}
|
||
|
||
// walkNodes walks through all nodes in the document
|
||
func (e *ContentEngine) walkNodes(n *html.Node, fn func(*html.Node)) {
|
||
fn(n)
|
||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||
e.walkNodes(c, fn)
|
||
}
|
||
}
|
||
|
||
// hasInsertrClass checks if node has class="insertr"
|
||
func (e *ContentEngine) hasInsertrClass(node *html.Node) bool {
|
||
classes := GetClasses(node)
|
||
for _, class := range classes {
|
||
if class == "insertr" {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// hasInsertrAddClass checks if node has class="insertr-add" (collection)
|
||
func (e *ContentEngine) hasInsertrAddClass(node *html.Node) bool {
|
||
classes := GetClasses(node)
|
||
for _, class := range classes {
|
||
if class == "insertr-add" {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// addContentAttributes adds data-content-id attribute only
|
||
// HTML-first approach: no content-type attribute needed
|
||
func (e *ContentEngine) addContentAttributes(node *html.Node, contentID string) {
|
||
// Add data-content-id attribute
|
||
e.setAttribute(node, "data-content-id", contentID)
|
||
}
|
||
|
||
// getAttribute gets an attribute value from an HTML node
|
||
func (e *ContentEngine) getAttribute(node *html.Node, key string) string {
|
||
for _, attr := range node.Attr {
|
||
if attr.Key == key {
|
||
return attr.Val
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// setAttribute sets an attribute on an HTML node
|
||
func (e *ContentEngine) setAttribute(node *html.Node, key, value string) {
|
||
// Remove existing attribute if it exists
|
||
for i, attr := range node.Attr {
|
||
if attr.Key == key {
|
||
node.Attr[i].Val = value
|
||
return
|
||
}
|
||
}
|
||
// Add new attribute
|
||
node.Attr = append(node.Attr, html.Attribute{
|
||
Key: key,
|
||
Val: value,
|
||
})
|
||
}
|
||
|
||
// addClass safely adds a class to an HTML node
|
||
func (e *ContentEngine) addClass(node *html.Node, className string) {
|
||
var classAttr *html.Attribute
|
||
var classIndex int = -1
|
||
|
||
// Find existing class attribute
|
||
for idx, attr := range node.Attr {
|
||
if attr.Key == "class" {
|
||
classAttr = &attr
|
||
classIndex = idx
|
||
break
|
||
}
|
||
}
|
||
|
||
var classes []string
|
||
if classAttr != nil {
|
||
classes = strings.Fields(classAttr.Val)
|
||
}
|
||
|
||
// Check if class already exists
|
||
for _, class := range classes {
|
||
if class == className {
|
||
return // Class already exists
|
||
}
|
||
}
|
||
|
||
// Add new class
|
||
classes = append(classes, className)
|
||
newClassValue := strings.Join(classes, " ")
|
||
|
||
if classIndex >= 0 {
|
||
// Update existing class attribute
|
||
node.Attr[classIndex].Val = newClassValue
|
||
} else {
|
||
// Add new class attribute
|
||
node.Attr = append(node.Attr, html.Attribute{
|
||
Key: "class",
|
||
Val: newClassValue,
|
||
})
|
||
}
|
||
}
|
||
|
||
// removeClass safely removes a class from an HTML node
|
||
func (e *ContentEngine) removeClass(node *html.Node, className string) {
|
||
var classIndex int = -1
|
||
|
||
// Find existing class attribute
|
||
for idx, attr := range node.Attr {
|
||
if attr.Key == "class" {
|
||
classIndex = idx
|
||
break
|
||
}
|
||
}
|
||
|
||
if classIndex == -1 {
|
||
return // No class attribute found
|
||
}
|
||
|
||
// Parse existing classes
|
||
classes := strings.Fields(node.Attr[classIndex].Val)
|
||
|
||
// Filter out the target class
|
||
var newClasses []string
|
||
for _, class := range classes {
|
||
if class != className {
|
||
newClasses = append(newClasses, class)
|
||
}
|
||
}
|
||
|
||
// Update or remove class attribute
|
||
if len(newClasses) == 0 {
|
||
// Remove class attribute entirely if no classes remain
|
||
node.Attr = append(node.Attr[:classIndex], node.Attr[classIndex+1:]...)
|
||
} else {
|
||
// Update class attribute with remaining classes
|
||
node.Attr[classIndex].Val = strings.Join(newClasses, " ")
|
||
}
|
||
}
|
||
|
||
// injectContent injects content from database into elements
|
||
func (e *ContentEngine) injectContent(elements []ProcessedElement, siteID string) error {
|
||
for i := range elements {
|
||
elem := &elements[i]
|
||
|
||
// Try to get content from database
|
||
contentItem, err := e.client.GetContent(siteID, elem.ID)
|
||
if err != nil {
|
||
// Content not found is not an error - element just won't have injected content
|
||
continue
|
||
}
|
||
|
||
if contentItem != nil {
|
||
// Inject the content into the element
|
||
elem.Content = contentItem.HTMLContent
|
||
|
||
// Update injector siteID for this operation
|
||
e.injector.siteID = siteID
|
||
e.injector.injectHTMLContent(elem.Node, contentItem.HTMLContent)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// extractHTMLContent extracts the inner HTML content from a node
|
||
func (e *ContentEngine) extractHTMLContent(node *html.Node) string {
|
||
var content strings.Builder
|
||
|
||
// Render all child nodes in order to preserve HTML structure
|
||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||
if err := html.Render(&content, child); err == nil {
|
||
// All nodes (text and element) rendered in correct order
|
||
}
|
||
}
|
||
|
||
return strings.TrimSpace(content.String())
|
||
}
|
||
|
||
// extractOriginalTemplate extracts the outer HTML of the element (including the element itself)
|
||
func (e *ContentEngine) extractOriginalTemplate(node *html.Node) string {
|
||
var buf strings.Builder
|
||
if err := html.Render(&buf, node); err != nil {
|
||
return ""
|
||
}
|
||
return buf.String()
|
||
}
|
||
|
||
// processCollection handles collection detection, persistence and reconstruction
|
||
func (e *ContentEngine) processCollection(collectionNode *html.Node, collectionID, siteID string) error {
|
||
// 1. Check if collection exists in database
|
||
existingCollection, err := e.client.GetCollection(siteID, collectionID)
|
||
collectionExists := (err == nil && existingCollection != nil)
|
||
|
||
if !collectionExists {
|
||
// 2. New collection: extract container HTML and create collection record
|
||
containerHTML := e.extractOriginalTemplate(collectionNode)
|
||
|
||
_, err := e.client.CreateCollection(siteID, collectionID, containerHTML, "system")
|
||
if err != nil {
|
||
return fmt.Errorf("failed to create collection %s: %w", collectionID, err)
|
||
}
|
||
|
||
// 3. Extract templates and store initial items from existing children
|
||
err = e.extractAndStoreTemplatesAndItems(collectionNode, collectionID, siteID)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to extract templates and items for collection %s: %w", collectionID, err)
|
||
}
|
||
|
||
fmt.Printf("✅ Created new collection: %s with templates and initial items\n", collectionID)
|
||
} else {
|
||
// 4. Database-first approach: Check if collection items already exist
|
||
existingItems, err := e.client.GetCollectionItems(siteID, collectionID)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to check existing collection items: %w", err)
|
||
}
|
||
|
||
if len(existingItems) == 0 {
|
||
// 5. Collection exists but no items - store original children as initial items
|
||
err = e.storeInitialCollectionItems(collectionNode, collectionID, siteID)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to store initial collection items for %s: %w", collectionID, err)
|
||
}
|
||
fmt.Printf("✅ Stored initial items for existing collection: %s\n", collectionID)
|
||
} else {
|
||
// 6. Items exist: reconstruct from database (normal case)
|
||
err = e.reconstructCollectionItems(collectionNode, collectionID, siteID)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to reconstruct collection %s: %w", collectionID, err)
|
||
}
|
||
fmt.Printf("✅ Reconstructed collection: %s from database (%d items)\n", collectionID, len(existingItems))
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// extractAndStoreTemplatesAndItems extracts templates and stores initial items from existing collection children
|
||
func (e *ContentEngine) extractAndStoreTemplatesAndItems(collectionNode *html.Node, collectionID, siteID string) error {
|
||
// Find existing children elements to use as templates
|
||
var templateElements []*html.Node
|
||
|
||
// Walk through direct children of the collection
|
||
for child := collectionNode.FirstChild; child != nil; child = child.NextSibling {
|
||
if child.Type == html.ElementNode {
|
||
templateElements = append(templateElements, child)
|
||
}
|
||
}
|
||
|
||
if len(templateElements) == 0 {
|
||
// No existing children - create a default empty template
|
||
_, err := e.client.CreateCollectionTemplate(siteID, collectionID, "default", "<div>New item</div>", true)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to create default template: %w", err)
|
||
}
|
||
fmt.Printf("✅ Created default template for collection %s\n", collectionID)
|
||
return nil
|
||
}
|
||
|
||
// Extract templates from existing children and store them
|
||
var templateIDs []int
|
||
for i, templateElement := range templateElements {
|
||
templateHTML := e.extractOriginalTemplate(templateElement)
|
||
templateName := fmt.Sprintf("template-%d", i+1)
|
||
isDefault := (i == 0) // First template is default
|
||
|
||
template, err := e.client.CreateCollectionTemplate(siteID, collectionID, templateName, templateHTML, isDefault)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to create template %s: %w", templateName, err)
|
||
}
|
||
|
||
templateIDs = append(templateIDs, template.TemplateID)
|
||
fmt.Printf("✅ Created template '%s' for collection %s\n", templateName, collectionID)
|
||
}
|
||
|
||
// Store original children as initial collection items (database-first approach)
|
||
err := e.storeChildrenAsCollectionItems(collectionNode, collectionID, siteID, templateIDs)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to store initial collection items: %w", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// reconstructCollectionItems rebuilds collection items from database and adds them to DOM
|
||
func (e *ContentEngine) reconstructCollectionItems(collectionNode *html.Node, collectionID, siteID string) error {
|
||
// Get all items for this collection from database
|
||
items, err := e.client.GetCollectionItems(siteID, collectionID)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to get collection items: %w", err)
|
||
}
|
||
|
||
// Get templates for this collection
|
||
templates, err := e.client.GetCollectionTemplates(siteID, collectionID)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to get collection templates: %w", err)
|
||
}
|
||
|
||
// Create a template map for quick lookup
|
||
templateMap := make(map[int]string)
|
||
for _, template := range templates {
|
||
templateMap[template.TemplateID] = template.HTMLTemplate
|
||
}
|
||
|
||
// Clear existing children (they will be replaced with database items)
|
||
for child := collectionNode.FirstChild; child != nil; {
|
||
next := child.NextSibling
|
||
collectionNode.RemoveChild(child)
|
||
child = next
|
||
}
|
||
|
||
// Add items from database in position order
|
||
for _, item := range items {
|
||
// Get the template for this item
|
||
templateHTML, exists := templateMap[item.TemplateID]
|
||
if !exists {
|
||
fmt.Printf("⚠️ Template %d not found for item %s\n", item.TemplateID, item.ItemID)
|
||
continue
|
||
}
|
||
|
||
// Parse the template HTML
|
||
templateDoc, err := html.Parse(strings.NewReader(templateHTML))
|
||
if err != nil {
|
||
fmt.Printf("⚠️ Failed to parse template HTML for %s: %v\n", item.ItemID, err)
|
||
continue
|
||
}
|
||
|
||
// Find the body element and extract the template structure
|
||
var templateBody *html.Node
|
||
e.walkNodes(templateDoc, func(n *html.Node) {
|
||
if n.Type == html.ElementNode && n.Data == "body" {
|
||
templateBody = n
|
||
}
|
||
})
|
||
|
||
if templateBody != nil && templateBody.FirstChild != nil {
|
||
// Clone the template structure (first child of body)
|
||
templateNode := templateBody.FirstChild
|
||
clonedTemplate := e.cloneNode(templateNode)
|
||
|
||
// Replace the template's inner content with the stored item content
|
||
// Clear the cloned template's children
|
||
for child := clonedTemplate.FirstChild; child != nil; {
|
||
next := child.NextSibling
|
||
clonedTemplate.RemoveChild(child)
|
||
child = next
|
||
}
|
||
|
||
// Parse and add the item's content
|
||
itemDoc, err := html.Parse(strings.NewReader(item.HTMLContent))
|
||
if err != nil {
|
||
fmt.Printf("⚠️ Failed to parse item content for %s: %v\n", item.ItemID, err)
|
||
continue
|
||
}
|
||
|
||
var itemBody *html.Node
|
||
e.walkNodes(itemDoc, func(n *html.Node) {
|
||
if n.Type == html.ElementNode && n.Data == "body" {
|
||
itemBody = n
|
||
}
|
||
})
|
||
|
||
if itemBody != nil {
|
||
// Move all children from item body to cloned template
|
||
for itemChild := itemBody.FirstChild; itemChild != nil; {
|
||
next := itemChild.NextSibling
|
||
itemBody.RemoveChild(itemChild)
|
||
clonedTemplate.AppendChild(itemChild)
|
||
itemChild = next
|
||
}
|
||
}
|
||
|
||
// Add the reconstructed item to collection
|
||
collectionNode.AppendChild(clonedTemplate)
|
||
}
|
||
}
|
||
|
||
fmt.Printf("✅ Reconstructed %d items for collection %s\n", len(items), collectionID)
|
||
return nil
|
||
}
|
||
|
||
// cloneNode creates a deep copy of an HTML node
|
||
func (e *ContentEngine) cloneNode(node *html.Node) *html.Node {
|
||
cloned := &html.Node{
|
||
Type: node.Type,
|
||
Data: node.Data,
|
||
DataAtom: node.DataAtom,
|
||
Namespace: node.Namespace,
|
||
}
|
||
|
||
// Clone attributes
|
||
for _, attr := range node.Attr {
|
||
cloned.Attr = append(cloned.Attr, html.Attribute{
|
||
Namespace: attr.Namespace,
|
||
Key: attr.Key,
|
||
Val: attr.Val,
|
||
})
|
||
}
|
||
|
||
// Clone children recursively
|
||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||
clonedChild := e.cloneNode(child)
|
||
cloned.AppendChild(clonedChild)
|
||
}
|
||
|
||
return cloned
|
||
}
|
||
|
||
// storeInitialCollectionItems stores original children as collection items (for existing collections)
|
||
func (e *ContentEngine) storeInitialCollectionItems(collectionNode *html.Node, collectionID, siteID string) error {
|
||
// Get existing templates for this collection
|
||
templates, err := e.client.GetCollectionTemplates(siteID, collectionID)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to get collection templates: %w", err)
|
||
}
|
||
|
||
if len(templates) == 0 {
|
||
fmt.Printf("⚠️ No templates found for collection %s, skipping initial items storage\n", collectionID)
|
||
return nil
|
||
}
|
||
|
||
// Use template IDs from existing templates
|
||
var templateIDs []int
|
||
for _, template := range templates {
|
||
templateIDs = append(templateIDs, template.TemplateID)
|
||
}
|
||
|
||
return e.storeChildrenAsCollectionItems(collectionNode, collectionID, siteID, templateIDs)
|
||
}
|
||
|
||
// storeChildrenAsCollectionItems stores HTML children as collection items in database
|
||
func (e *ContentEngine) storeChildrenAsCollectionItems(collectionNode *html.Node, collectionID, siteID string, templateIDs []int) error {
|
||
// Find existing children elements to store as items
|
||
var childElements []*html.Node
|
||
|
||
// Walk through direct children of the collection
|
||
for child := collectionNode.FirstChild; child != nil; child = child.NextSibling {
|
||
if child.Type == html.ElementNode {
|
||
childElements = append(childElements, child)
|
||
}
|
||
}
|
||
|
||
if len(childElements) == 0 {
|
||
fmt.Printf("ℹ️ No children found to store as collection items for %s\n", collectionID)
|
||
return nil
|
||
}
|
||
|
||
// Store each child as a collection item (database-first pattern like .insertr)
|
||
for i, childElement := range childElements {
|
||
// Generate item ID (like content ID generation)
|
||
itemID := fmt.Sprintf("%s-initial-%d", collectionID, i+1)
|
||
|
||
// Extract HTML content from the child (reuse .insertr pattern)
|
||
htmlContent := e.extractHTMLContent(childElement)
|
||
|
||
// Use appropriate template ID (cycle through available templates)
|
||
templateID := templateIDs[i%len(templateIDs)]
|
||
|
||
// Store as collection item
|
||
_, err := e.client.CreateCollectionItem(siteID, collectionID, itemID, templateID, htmlContent, i+1, "system")
|
||
if err != nil {
|
||
return fmt.Errorf("failed to create collection item %s: %w", itemID, err)
|
||
}
|
||
|
||
fmt.Printf("✅ Stored initial collection item: %s (template %d)\n", itemID, templateID)
|
||
}
|
||
|
||
return nil
|
||
}
|