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
145 lines
4.1 KiB
Go
145 lines
4.1 KiB
Go
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..."
|
|
}
|