package content import ( "fmt" "log" "strings" "golang.org/x/net/html" ) // Injector handles content injection into HTML elements type Injector struct { client ContentClient siteID string mdProcessor *MarkdownProcessor } // NewInjector creates a new content injector func NewInjector(client ContentClient, siteID string) *Injector { return &Injector{ client: client, siteID: siteID, mdProcessor: NewMarkdownProcessor(), } } // 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 - converts markdown to HTML func (i *Injector) injectMarkdownContent(node *html.Node, content string) { if content == "" { i.injectTextContent(node, "") return } // Convert markdown to HTML using server processor htmlContent, err := i.mdProcessor.ToHTML(content) if err != nil { log.Printf("⚠️ Markdown conversion failed for content '%s': %v, falling back to text", content, err) i.injectTextContent(node, content) return } // Inject the HTML content i.injectHTMLContent(node, htmlContent) } // 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) } // injectHTMLContent safely injects HTML content into a DOM node func (i *Injector) injectHTMLContent(node *html.Node, htmlContent string) { // Clear existing content i.clearNode(node) if htmlContent == "" { return } // Wrap content to create valid HTML document for parsing wrappedHTML := "