diff --git a/internal/engine/engine.go b/internal/engine/engine.go index 0a4c1d9..59fd7ac 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -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; { diff --git a/internal/engine/injector.go b/internal/engine/injector.go index 7ee5190..c150389 100644 --- a/internal/engine/injector.go +++ b/internal/engine/injector.go @@ -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 diff --git a/internal/engine/utils.go b/internal/engine/utils.go index e94a6b6..399c245 100644 --- a/internal/engine/utils.go +++ b/internal/engine/utils.go @@ -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,