Consolidate DOM manipulation utilities to eliminate code duplication

- Move addClass and setAttribute from ContentEngine/Injector to utils.go
- Remove duplicate hasInsertrClass implementation
- Add RemoveClass and HasClass utilities for completeness
- Eliminates 74+ lines of exact duplication across files
This commit is contained in:
2025-10-26 18:07:12 +01:00
parent c34a1a033e
commit a52d9bb600
3 changed files with 116 additions and 130 deletions

View File

@@ -106,7 +106,7 @@ func (e *ContentEngine) ProcessContent(input ContentInput) (*ContentResult, erro
collectionID := e.idGenerator.Generate(collectionElem.Node, input.FilePath)
// Add data-collection-id attribute to the collection container
e.setAttribute(collectionElem.Node, "data-collection-id", collectionID)
SetAttribute(collectionElem.Node, "data-collection-id", collectionID)
// Process collection during enhancement or content injection
if input.Mode == Enhancement || input.Mode == ContentInjection {
@@ -161,7 +161,7 @@ func (e *ContentEngine) findEditableElements(doc *html.Node) ([]InsertrElement,
// 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 hasInsertrClass(n) {
if isContainer(n) {
// Container element - mark for transformation
containersToTransform = append(containersToTransform, n)
@@ -189,7 +189,7 @@ func (e *ContentEngine) findEditableElements(doc *html.Node) ([]InsertrElement,
// Find viable children and add .insertr class to them
viableChildren := FindViableChildren(container)
for _, child := range viableChildren {
e.addClass(child, "insertr")
AddClass(child, "insertr")
insertrElements = append(insertrElements, InsertrElement{
Node: child,
})
@@ -207,12 +207,6 @@ func (e *ContentEngine) walkNodes(n *html.Node, fn func(*html.Node)) {
}
}
// hasInsertrClass checks if node has class="insertr"
func (e *ContentEngine) hasInsertrClass(node *html.Node) bool {
classes := GetClasses(node)
return slices.Contains(classes, "insertr")
}
// hasInsertrAddClass checks if node has class="insertr-add" (collection)
func (e *ContentEngine) hasInsertrAddClass(node *html.Node) bool {
classes := GetClasses(node)
@@ -222,63 +216,7 @@ func (e *ContentEngine) hasInsertrAddClass(node *html.Node) bool {
// addContentAttributes adds data-content-id attribute only
func (e *ContentEngine) addContentAttributes(node *html.Node, contentID string) {
// Add data-content-id attribute
e.setAttribute(node, "data-content-id", contentID)
}
// setAttribute sets an attribute on a 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
if slices.Contains(classes, className) {
return
}
// 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,
})
}
SetAttribute(node, "data-content-id", contentID)
}
// removeClass safely removes a class from an HTML node
@@ -367,7 +305,7 @@ func (e *ContentEngine) extractOriginalTemplate(node *html.Node) string {
return buf.String()
}
// extractCleanTemplate extracts a clean template without data-content-id attributes and with placeholder content
// 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)
@@ -406,7 +344,7 @@ func (e *ContentEngine) extractCleanTemplate(node *html.Node) string {
func (e *ContentEngine) removeAttribute(n *html.Node, key string) {
for i, attr := range n.Attr {
if attr.Key == key {
n.Attr = append(n.Attr[:i], n.Attr[i+1:]...)
n.Attr = slices.Delete(n.Attr, i, i+1)
break
}
}
@@ -605,7 +543,7 @@ func (e *ContentEngine) reconstructCollectionItems(collectionNode *html.Node, co
// Inject data-item-id attribute for collection item identification
if structuralChild.Type == html.ElementNode {
e.setAttribute(structuralChild, "data-item-id", item.ItemID)
SetAttribute(structuralChild, "data-item-id", item.ItemID)
}
collectionNode.AppendChild(structuralChild)
@@ -665,7 +603,7 @@ func (e *ContentEngine) generateStructuralTemplateFromChild(childElement *html.N
if n.Type == html.ElementNode && e.hasClass(n, "insertr") {
if entryIndex < len(contentEntries) {
// Set the data-content-id attribute
e.setAttribute(n, "data-content-id", contentEntries[entryIndex].ID)
SetAttribute(n, "data-content-id", contentEntries[entryIndex].ID)
// Clear content - this will be hydrated during reconstruction
for child := n.FirstChild; child != nil; {

View File

@@ -9,7 +9,6 @@ import (
"github.com/insertr/insertr/internal/config"
"github.com/insertr/insertr/internal/db"
"golang.org/x/net/html"
"slices"
)
// Injector handles content injection into HTML elements
@@ -168,8 +167,8 @@ func (i *Injector) findElementByTag(node *html.Node, tag string) *html.Node {
// AddContentAttributes adds necessary data attributes and insertr class for editor functionality
func (i *Injector) AddContentAttributes(node *html.Node, contentID string, contentType string) {
i.setAttribute(node, "data-content-id", contentID)
i.addClass(node, "insertr")
SetAttribute(node, "data-content-id", contentID)
AddClass(node, "insertr")
}
// InjectEditorAssets adds editor JavaScript to HTML document and injects demo gate if needed
@@ -200,63 +199,6 @@ func (i *Injector) findHeadElement(node *html.Node) *html.Node {
return nil
}
// setAttribute safely sets an attribute on an HTML node
func (i *Injector) setAttribute(node *html.Node, key, value string) {
// Remove existing attribute if present
for idx, attr := range node.Attr {
if attr.Key == key {
node.Attr = slices.Delete(node.Attr, idx, idx+1)
break
}
}
// Add new attribute
node.Attr = append(node.Attr, html.Attribute{
Key: key,
Val: value,
})
}
// addClass safely adds a class to an HTML node
func (i *Injector) 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
if slices.Contains(classes, 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,
})
}
}
// Element represents a parsed HTML element with metadata
type Element struct {
Node *html.Node

View File

@@ -32,6 +32,112 @@ func GetAttribute(node *html.Node, key string) string {
return ""
}
// SetAttribute sets an attribute on an HTML node
func SetAttribute(node *html.Node, key, value string) {
// Check for existing attribute and update in place
for i, attr := range node.Attr {
if attr.Key == key {
node.Attr[i].Val = value
return
}
}
// Add new attribute if not found
node.Attr = append(node.Attr, html.Attribute{
Key: key,
Val: value,
})
}
// AddClass safely adds a class to an HTML node
func 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
if slices.Contains(classes, className) {
return
}
// 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 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 = slices.Delete(node.Attr, classIndex, classIndex+1)
} else {
// Update class attribute with remaining classes
node.Attr[classIndex].Val = strings.Join(newClasses, " ")
}
}
// HasClass checks if a node has a specific class
func HasClass(node *html.Node, className string) bool {
for _, attr := range node.Attr {
if attr.Key == "class" {
classes := strings.Fields(attr.Val)
if slices.Contains(classes, className) {
return true
}
}
}
return false
}
// Inline formatting elements that are safe for editing
var inlineFormattingTags = map[string]bool{
"strong": true,