package content import ( "fmt" "strings" "golang.org/x/net/html" ) // Injector handles content injection into HTML elements type Injector struct { client ContentClient siteID string } // NewInjector creates a new content injector func NewInjector(client ContentClient, siteID string) *Injector { return &Injector{ client: client, siteID: siteID, } } // InjectContent replaces element content with database values and adds content IDs func (i *Injector) InjectContent(element *Element, contentID string) error { // Fetch content from database/API contentItem, err := i.client.GetContent(i.siteID, contentID) if err != nil { return fmt.Errorf("fetching content for %s: %w", contentID, err) } // If no content found, keep original content but add data attributes if contentItem == nil { i.addContentAttributes(element.Node, contentID, element.Type) return nil } // Replace element content based on type switch element.Type { case "text": i.injectTextContent(element.Node, contentItem.Value) case "markdown": i.injectMarkdownContent(element.Node, contentItem.Value) case "link": i.injectLinkContent(element.Node, contentItem.Value) default: i.injectTextContent(element.Node, contentItem.Value) } // Add data attributes for editor functionality i.addContentAttributes(element.Node, contentID, element.Type) return nil } // InjectBulkContent efficiently injects multiple content items func (i *Injector) InjectBulkContent(elements []ElementWithID) error { // Extract content IDs for bulk fetch contentIDs := make([]string, len(elements)) for idx, elem := range elements { contentIDs[idx] = elem.ContentID } // Bulk fetch content contentMap, err := i.client.GetBulkContent(i.siteID, contentIDs) if err != nil { return fmt.Errorf("bulk fetching content: %w", err) } // Inject each element for _, elem := range elements { contentItem, exists := contentMap[elem.ContentID] // Add content attributes regardless i.addContentAttributes(elem.Element.Node, elem.ContentID, elem.Element.Type) if !exists { // Keep original content if not found in database continue } // Replace content based on type switch elem.Element.Type { case "text": i.injectTextContent(elem.Element.Node, contentItem.Value) case "markdown": i.injectMarkdownContent(elem.Element.Node, contentItem.Value) case "link": i.injectLinkContent(elem.Element.Node, contentItem.Value) default: i.injectTextContent(elem.Element.Node, contentItem.Value) } } return nil } // injectTextContent replaces text content in an element func (i *Injector) injectTextContent(node *html.Node, content string) { // Remove all child nodes for child := node.FirstChild; child != nil; { next := child.NextSibling node.RemoveChild(child) child = next } // Add new text content textNode := &html.Node{ Type: html.TextNode, Data: content, } node.AppendChild(textNode) } // injectMarkdownContent handles markdown content (for now, just as text) func (i *Injector) injectMarkdownContent(node *html.Node, content string) { // For now, treat markdown as text content // TODO: Implement markdown to HTML conversion i.injectTextContent(node, content) } // injectLinkContent handles link/button content with URL extraction func (i *Injector) injectLinkContent(node *html.Node, content string) { // For now, just inject the text content // TODO: Parse content for URL and text components i.injectTextContent(node, content) } // addContentAttributes adds necessary data attributes for editor functionality func (i *Injector) addContentAttributes(node *html.Node, contentID string, contentType string) { i.setAttribute(node, "data-content-id", contentID) i.setAttribute(node, "data-content-type", contentType) i.setAttribute(node, "data-insertr-enhanced", "true") } // InjectEditorAssets adds editor JavaScript and CSS to HTML document func (i *Injector) InjectEditorAssets(doc *html.Node, isDevelopment bool, libraryScript string) { if !isDevelopment { return // Only inject in development mode for now } // Find the head element head := i.findHeadElement(doc) if head == nil { return } // Add inline script with embedded library // Note: Using html.TextNode for scripts can cause issues with HTML entity encoding // Instead, we'll insert the script tag as raw HTML scriptHTML := fmt.Sprintf(``, libraryScript) // Parse the script HTML and append to head scriptNodes, err := html.ParseFragment(strings.NewReader(scriptHTML), head) if err == nil && len(scriptNodes) > 0 { for _, node := range scriptNodes { head.AppendChild(node) } } } // findHeadElement finds the element in the document func (i *Injector) findHeadElement(node *html.Node) *html.Node { if node.Type == html.ElementNode && node.Data == "head" { return node } for child := node.FirstChild; child != nil; child = child.NextSibling { if result := i.findHeadElement(child); result != nil { return result } } 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 = append(node.Attr[:idx], node.Attr[idx+1:]...) break } } // Add new attribute node.Attr = append(node.Attr, html.Attribute{ Key: key, Val: value, }) } // Element represents a parsed HTML element with metadata type Element struct { Node *html.Node Type string Tag string Classes []string Content string } // ElementWithID combines an element with its generated content ID type ElementWithID struct { Element *Element ContentID string }