fix: systematic element matching bug in enhancement pipeline
- Problem: Element ID collisions between similar elements (logo h1 vs hero h1) causing content to be injected into wrong elements - Root cause: Enhancer used naive tag+class matching instead of parser's sophisticated semantic analysis for element identification Systematic solution: - Enhanced parser architecture with exported utilities (GetClasses, ContainsClass) - Added FindElementInDocument() with content-based semantic matching - Replaced naive findAndInjectNodes() with parser-based element matching - Removed code duplication between parser and enhancer packages Backend improvements: - Moved ID generation to backend for single source of truth - Added ElementContext struct for frontend-backend communication - Updated API handlers to support context-based content ID generation Frontend improvements: - Enhanced getElementMetadata() to extract semantic context - Updated save flow to handle both enhanced and non-enhanced elements - Improved API client to use backend-generated content IDs Result: - Unique content IDs: navbar-logo-200530 vs hero-title-a1de7b - Precise element matching using content validation - Single source of truth for DOM utilities in parser package - Eliminated 40+ lines of duplicate code while fixing core bug
This commit is contained in:
@@ -98,52 +98,32 @@ func (e *Enhancer) injectElementContent(doc *html.Node, elem parser.Element) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// findAndInjectNodes recursively finds nodes and injects content
|
||||
func (e *Enhancer) findAndInjectNodes(node *html.Node, elem parser.Element, contentItem *ContentItem) {
|
||||
if node.Type == html.ElementNode {
|
||||
// Check if this node matches our element criteria
|
||||
classes := getClasses(node)
|
||||
if containsClass(classes, "insertr") && node.Data == elem.Tag {
|
||||
// This might be our target node - inject content
|
||||
e.injector.addContentAttributes(node, elem.ContentID, string(elem.Type))
|
||||
|
||||
if contentItem != nil {
|
||||
switch elem.Type {
|
||||
case parser.ContentText:
|
||||
e.injector.injectTextContent(node, contentItem.Value)
|
||||
case parser.ContentMarkdown:
|
||||
e.injector.injectMarkdownContent(node, contentItem.Value)
|
||||
case parser.ContentLink:
|
||||
e.injector.injectLinkContent(node, contentItem.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
// findAndInjectNodes finds the specific node for this element and injects content
|
||||
func (e *Enhancer) findAndInjectNodes(rootNode *html.Node, elem parser.Element, contentItem *ContentItem) {
|
||||
// Use parser-based element matching to find the correct specific node
|
||||
targetNode := e.findNodeInDocument(rootNode, elem)
|
||||
if targetNode == nil {
|
||||
// Element not found - this is normal for elements without content in database
|
||||
return
|
||||
}
|
||||
|
||||
// Recursively process children
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
e.findAndInjectNodes(child, elem, contentItem)
|
||||
// Inject content attributes for the correctly matched node
|
||||
e.injector.addContentAttributes(targetNode, elem.ContentID, string(elem.Type))
|
||||
|
||||
// Inject content if available
|
||||
if contentItem != nil {
|
||||
switch elem.Type {
|
||||
case parser.ContentText:
|
||||
e.injector.injectTextContent(targetNode, contentItem.Value)
|
||||
case parser.ContentMarkdown:
|
||||
e.injector.injectMarkdownContent(targetNode, contentItem.Value)
|
||||
case parser.ContentLink:
|
||||
e.injector.injectLinkContent(targetNode, contentItem.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions from parser package
|
||||
func getClasses(node *html.Node) []string {
|
||||
for _, attr := range node.Attr {
|
||||
if attr.Key == "class" {
|
||||
return strings.Fields(attr.Val)
|
||||
}
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func containsClass(classes []string, target string) bool {
|
||||
for _, class := range classes {
|
||||
if class == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
// Helper functions are now provided by the parser package
|
||||
|
||||
// EnhanceDirectory processes all HTML files in a directory
|
||||
func (e *Enhancer) EnhanceDirectory(inputDir, outputDir string) error {
|
||||
@@ -295,28 +275,10 @@ func (e *Enhancer) enhanceFileInPlace(filePath string, elements []parser.Element
|
||||
return e.writeHTML(doc, filePath)
|
||||
}
|
||||
|
||||
// findNodeInDocument finds a specific node in the HTML document tree
|
||||
// findNodeInDocument finds a specific node in the HTML document tree using parser utilities
|
||||
func (e *Enhancer) findNodeInDocument(doc *html.Node, elem parser.Element) *html.Node {
|
||||
// This is a simplified approach - in a production system we might need
|
||||
// more sophisticated node matching based on attributes, position, etc.
|
||||
return e.findNodeByTagAndClass(doc, elem.Tag, "insertr")
|
||||
// Use parser's sophisticated matching logic
|
||||
return parser.FindElementInDocument(doc, elem)
|
||||
}
|
||||
|
||||
// findNodeByTagAndClass recursively searches for a node with specific tag and class
|
||||
func (e *Enhancer) findNodeByTagAndClass(node *html.Node, targetTag, targetClass string) *html.Node {
|
||||
if node.Type == html.ElementNode && node.Data == targetTag {
|
||||
classes := getClasses(node)
|
||||
if containsClass(classes, targetClass) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
// Search children
|
||||
for child := node.FirstChild; child != nil; child = child.NextSibling {
|
||||
if result := e.findNodeByTagAndClass(child, targetTag, targetClass); result != nil {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
// All element matching functions are now provided by the parser package
|
||||
|
||||
Reference in New Issue
Block a user