package content import ( "fmt" "os" "path/filepath" "strings" "golang.org/x/net/html" "github.com/insertr/insertr/internal/parser" ) // Enhancer combines parsing and content injection type Enhancer struct { parser *parser.Parser injector *Injector } // NewEnhancer creates a new HTML enhancer func NewEnhancer(client ContentClient, siteID string) *Enhancer { return &Enhancer{ parser: parser.New(), injector: NewInjector(client, siteID), } } // EnhanceFile processes an HTML file and injects content func (e *Enhancer) EnhanceFile(inputPath, outputPath string) error { // Use parser to get elements from file result, err := e.parser.ParseDirectory(filepath.Dir(inputPath)) if err != nil { return fmt.Errorf("parsing file: %w", err) } // Filter elements for this specific file var fileElements []parser.Element inputBaseName := filepath.Base(inputPath) for _, elem := range result.Elements { elemBaseName := filepath.Base(elem.FilePath) if elemBaseName == inputBaseName { fileElements = append(fileElements, elem) } } if len(fileElements) == 0 { // No insertr elements found, copy file as-is return e.copyFile(inputPath, outputPath) } // Read and parse HTML for modification htmlContent, err := os.ReadFile(inputPath) if err != nil { return fmt.Errorf("reading file %s: %w", inputPath, err) } doc, err := html.Parse(strings.NewReader(string(htmlContent))) if err != nil { return fmt.Errorf("parsing HTML: %w", err) } // Find and inject content for each element for _, elem := range fileElements { // Find the node in the parsed document // Note: This is a simplified approach - in production we'd need more robust node matching if err := e.injectElementContent(doc, elem); err != nil { fmt.Printf("⚠️ Warning: failed to inject content for %s: %v\n", elem.ContentID, err) } } // Inject editor assets for development libraryScript := GetLibraryScript(false) // Use non-minified for development debugging e.injector.InjectEditorAssets(doc, true, libraryScript) // Write enhanced HTML if err := e.writeHTML(doc, outputPath); err != nil { return fmt.Errorf("writing enhanced HTML: %w", err) } fmt.Printf("✅ Enhanced: %s → %s (%d elements)\n", filepath.Base(inputPath), filepath.Base(outputPath), len(fileElements)) return nil } // injectElementContent finds and injects content for a specific element func (e *Enhancer) injectElementContent(doc *html.Node, elem parser.Element) error { // Fetch content from database contentItem, err := e.injector.client.GetContent(e.injector.siteID, elem.ContentID) if err != nil { return fmt.Errorf("fetching content: %w", err) } // Find nodes with insertr class and inject content e.findAndInjectNodes(doc, elem, contentItem) return nil } // 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 } // 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 are now provided by the parser package // EnhanceDirectory processes all HTML files in a directory func (e *Enhancer) EnhanceDirectory(inputDir, outputDir string) error { // Create output directory if err := os.MkdirAll(outputDir, 0755); err != nil { return fmt.Errorf("creating output directory: %w", err) } // Walk input directory return filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // Calculate relative path and output path relPath, err := filepath.Rel(inputDir, path) if err != nil { return err } outputPath := filepath.Join(outputDir, relPath) // Handle directories if info.IsDir() { return os.MkdirAll(outputPath, info.Mode()) } // Handle HTML files if strings.HasSuffix(strings.ToLower(path), ".html") { return e.EnhanceFile(path, outputPath) } // Copy other files as-is return e.copyFile(path, outputPath) }) } // copyFile copies a file from src to dst func (e *Enhancer) copyFile(src, dst string) error { // Create directory for destination if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { return err } // Read source data, err := os.ReadFile(src) if err != nil { return err } // Write destination return os.WriteFile(dst, data, 0644) } // writeHTML writes an HTML document to a file func (e *Enhancer) writeHTML(doc *html.Node, outputPath string) error { // Create directory for output if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil { return err } // Create output file file, err := os.Create(outputPath) if err != nil { return err } defer file.Close() // Write HTML return html.Render(file, doc) } // EnhanceInPlace performs in-place enhancement of static site files func (e *Enhancer) EnhanceInPlace(sitePath string, siteID string) error { // Update the injector with the correct siteID e.injector.siteID = siteID // Use existing parser logic to discover elements result, err := e.parser.ParseDirectory(sitePath) if err != nil { return fmt.Errorf("parsing directory: %w", err) } if len(result.Elements) == 0 { fmt.Printf("📄 No insertr elements found in %s\n", sitePath) return nil } // Group elements by file for efficient processing fileElements := make(map[string][]parser.Element) for _, elem := range result.Elements { fileElements[elem.FilePath] = append(fileElements[elem.FilePath], elem) } // Process each file in-place enhancedCount := 0 for filePath, elements := range fileElements { if err := e.enhanceFileInPlace(filePath, elements); err != nil { fmt.Printf("⚠️ Failed to enhance %s: %v\n", filepath.Base(filePath), err) } else { enhancedCount++ } } fmt.Printf("✅ Enhanced %d files with %d elements in site %s\n", enhancedCount, len(result.Elements), siteID) return nil } // enhanceFileInPlace modifies an HTML file in-place with database content func (e *Enhancer) enhanceFileInPlace(filePath string, elements []parser.Element) error { // Read original file htmlContent, err := os.ReadFile(filePath) if err != nil { return fmt.Errorf("reading file: %w", err) } // Parse HTML doc, err := html.Parse(strings.NewReader(string(htmlContent))) if err != nil { return fmt.Errorf("parsing HTML: %w", err) } // Convert parser elements to injector format with content IDs elementIDs := make([]ElementWithID, 0, len(elements)) for _, elem := range elements { // Find the corresponding node in the parsed document node := e.findNodeInDocument(doc, elem) if node != nil { elementIDs = append(elementIDs, ElementWithID{ Element: &Element{ Node: node, Type: string(elem.Type), Tag: elem.Tag, }, ContentID: elem.ContentID, }) } } // Use existing bulk injection logic for efficiency if len(elementIDs) > 0 { if err := e.injector.InjectBulkContent(elementIDs); err != nil { return fmt.Errorf("injecting content: %w", err) } } // Write enhanced HTML back to the same file (in-place update) return e.writeHTML(doc, filePath) } // 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 { // Use parser's sophisticated matching logic return parser.FindElementInDocument(doc, elem) } // All element matching functions are now provided by the parser package