Refactor engine into focused files to improve maintainability

Split monolithic engine.go (776 lines) into specialized files:
- engine.go: Core orchestration (142 lines, 82% reduction)
- collection.go: Collection processing and management (445 lines)
- content.go: Content injection and extraction (152 lines)
- discovery.go: Element discovery and DOM traversal (85 lines)

Benefits:
- Single responsibility principle applied to each file
- Better code organization and navigation
- Improved testability of individual components
- Easier team development and code reviews
- Maintained full API compatibility with no breaking changes
This commit is contained in:
2025-10-26 19:15:55 +01:00
parent a52d9bb600
commit b46f643df7
4 changed files with 671 additions and 627 deletions

144
internal/engine/content.go Normal file
View File

@@ -0,0 +1,144 @@
package engine
import (
"slices"
"strings"
"golang.org/x/net/html"
)
// addContentAttributes adds data-content-id attribute only
func (e *ContentEngine) addContentAttributes(node *html.Node, contentID string) {
// Add data-content-id attribute
SetAttribute(node, "data-content-id", contentID)
}
// injectContent injects content from database into elements
func (e *ContentEngine) injectContent(elements []ProcessedElement, siteID string) error {
for i := range elements {
elem := &elements[i]
// Get content from database by ID
contentItem, err := e.client.GetContent(nil, siteID, elem.ID)
if err != nil {
// Content not found - skip silently (enhancement mode should not fail on missing content)
continue
}
if contentItem != nil {
// Inject the content into the element
elem.Content = contentItem.HTMLContent
// Update injector siteID for this operation
// HACK: I do not like this. Injector refactor?
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()
}
// extractCleanTemplate extracts a clean template without data-content-id attributes and with placeholder content. Used for collection template variants.
func (e *ContentEngine) extractCleanTemplate(node *html.Node) string {
// Clone the node to avoid modifying the original
clonedNode := e.cloneNode(node)
// Remove all data-content-id attributes and replace content with placeholders
e.walkNodes(clonedNode, func(n *html.Node) {
if n.Type == html.ElementNode {
// Remove data-content-id attribute
e.removeAttribute(n, "data-content-id")
// If this is an .insertr element, replace content with placeholder
if e.hasClass(n, "insertr") {
placeholderText := e.getPlaceholderForElement(n.Data)
// Clear existing children and add placeholder text
for child := n.FirstChild; child != nil; {
next := child.NextSibling
n.RemoveChild(child)
child = next
}
n.AppendChild(&html.Node{
Type: html.TextNode,
Data: placeholderText,
})
}
}
})
var buf strings.Builder
if err := html.Render(&buf, clonedNode); err != nil {
return ""
}
return buf.String()
}
// removeAttribute removes an attribute from an HTML node
func (e *ContentEngine) removeAttribute(n *html.Node, key string) {
for i, attr := range n.Attr {
if attr.Key == key {
n.Attr = slices.Delete(n.Attr, i, i+1)
break
}
}
}
// hasClass checks if a node has a specific class
func (e *ContentEngine) hasClass(n *html.Node, className string) bool {
for _, attr := range n.Attr {
if attr.Key == "class" {
classes := strings.Fields(attr.Val)
if slices.Contains(classes, className) {
return true
}
}
}
return false
}
// getPlaceholderForElement returns appropriate placeholder text for different element types
func (e *ContentEngine) getPlaceholderForElement(elementType string) string {
placeholders := map[string]string{
"h1": "Heading 1",
"h2": "Heading 2",
"h3": "Heading 3",
"h4": "Heading 4",
"h5": "Heading 5",
"h6": "Heading 6",
"p": "Paragraph text",
"span": "Text",
"div": "Content block",
"button": "Button",
"a": "Link text",
"li": "List item",
"blockquote": "Quote text",
}
if placeholder, exists := placeholders[elementType]; exists {
return placeholder
}
return "Enter content..."
}