- Rebuild JavaScript library with delayed control panel initialization - Update server assets to include latest UI behavior changes - Ensure built assets reflect invisible UI for regular visitors The control panel now only appears after gate activation, maintaining the invisible CMS principle for end users.
284 lines
7.8 KiB
Go
284 lines
7.8 KiB
Go
package content
|
|
|
|
import (
|
|
"fmt"
|
|
"golang.org/x/net/html"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/insertr/insertr/internal/engine"
|
|
)
|
|
|
|
// EnhancementConfig configures the enhancement pipeline
|
|
type EnhancementConfig struct {
|
|
Discovery DiscoveryConfig
|
|
ContentInjection bool
|
|
GenerateIDs bool
|
|
}
|
|
|
|
// DiscoveryConfig configures element discovery
|
|
type DiscoveryConfig struct {
|
|
Enabled bool
|
|
Aggressive bool
|
|
Containers bool
|
|
Individual bool
|
|
}
|
|
|
|
// Enhancer combines discovery, ID generation, and content injection in unified pipeline
|
|
type Enhancer struct {
|
|
engine *engine.ContentEngine
|
|
discoverer *Discoverer
|
|
config EnhancementConfig
|
|
siteID string
|
|
}
|
|
|
|
// NewEnhancer creates a new HTML enhancer with unified pipeline
|
|
func NewEnhancer(client engine.ContentClient, siteID string, config EnhancementConfig) *Enhancer {
|
|
return &Enhancer{
|
|
engine: engine.NewContentEngine(client),
|
|
discoverer: NewDiscoverer(),
|
|
config: config,
|
|
siteID: siteID,
|
|
}
|
|
}
|
|
|
|
// NewEnhancerWithAuth creates a new HTML enhancer with auth provider
|
|
func NewEnhancerWithAuth(client engine.ContentClient, siteID string, config EnhancementConfig, authProvider *engine.AuthProvider) *Enhancer {
|
|
return &Enhancer{
|
|
engine: engine.NewContentEngineWithAuth(client, authProvider),
|
|
discoverer: NewDiscoverer(),
|
|
config: config,
|
|
siteID: siteID,
|
|
}
|
|
}
|
|
|
|
// NewDefaultEnhancer creates an enhancer with default configuration
|
|
func NewDefaultEnhancer(client engine.ContentClient, siteID string) *Enhancer {
|
|
defaultConfig := EnhancementConfig{
|
|
Discovery: DiscoveryConfig{
|
|
Enabled: true,
|
|
Aggressive: false,
|
|
Containers: true,
|
|
Individual: true,
|
|
},
|
|
ContentInjection: true,
|
|
GenerateIDs: true,
|
|
}
|
|
return NewEnhancer(client, siteID, defaultConfig)
|
|
}
|
|
|
|
// EnhanceFile processes a single HTML file through the complete pipeline
|
|
func (e *Enhancer) EnhanceFile(inputPath, outputPath string) error {
|
|
// Read HTML file
|
|
htmlContent, err := os.ReadFile(inputPath)
|
|
if err != nil {
|
|
return fmt.Errorf("reading file %s: %w", inputPath, err)
|
|
}
|
|
|
|
// Process through unified pipeline
|
|
processedHTML, err := e.processHTML(htmlContent, filepath.Base(inputPath))
|
|
if err != nil {
|
|
return fmt.Errorf("processing HTML %s: %w", inputPath, err)
|
|
}
|
|
|
|
// Create output directory
|
|
if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
|
|
return fmt.Errorf("creating output directory: %w", err)
|
|
}
|
|
|
|
// Write processed HTML
|
|
return os.WriteFile(outputPath, processedHTML, 0644)
|
|
}
|
|
|
|
// EnhanceDirectory processes all files in a directory through the unified pipeline
|
|
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())
|
|
}
|
|
|
|
// Process HTML files through enhancement pipeline
|
|
if strings.HasSuffix(strings.ToLower(path), ".html") {
|
|
return e.EnhanceFile(path, outputPath)
|
|
}
|
|
|
|
// Copy non-HTML files as-is
|
|
return e.copyFile(path, outputPath)
|
|
})
|
|
}
|
|
|
|
// processHTML implements the unified enhancement pipeline
|
|
func (e *Enhancer) processHTML(htmlContent []byte, filePath string) ([]byte, error) {
|
|
var processedHTML []byte = htmlContent
|
|
|
|
// Phase 1: Element Discovery (if enabled)
|
|
if e.config.Discovery.Enabled {
|
|
discoveredHTML, err := e.discoverElements(processedHTML, filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("element discovery: %w", err)
|
|
}
|
|
processedHTML = discoveredHTML
|
|
}
|
|
|
|
// Phase 2 & 3: ID Generation + Content Injection (via engine)
|
|
if e.config.GenerateIDs || e.config.ContentInjection {
|
|
enhancedHTML, err := e.enhanceWithEngine(processedHTML, filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("engine enhancement: %w", err)
|
|
}
|
|
processedHTML = enhancedHTML
|
|
}
|
|
|
|
return processedHTML, nil
|
|
}
|
|
|
|
// discoverElements adds insertr classes to viable elements
|
|
func (e *Enhancer) discoverElements(htmlContent []byte, filePath string) ([]byte, error) {
|
|
// Parse HTML
|
|
doc, err := html.Parse(strings.NewReader(string(htmlContent)))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing HTML: %w", err)
|
|
}
|
|
|
|
// Find and mark viable elements
|
|
result := &FileDiscoveryResult{Document: doc}
|
|
e.discoverer.discoverNode(doc, result, e.config.Discovery.Aggressive)
|
|
|
|
// Render back to HTML
|
|
var buf strings.Builder
|
|
if err := html.Render(&buf, doc); err != nil {
|
|
return nil, fmt.Errorf("rendering HTML: %w", err)
|
|
}
|
|
|
|
return []byte(buf.String()), nil
|
|
}
|
|
|
|
// enhanceWithEngine uses the unified engine for ID generation and content injection
|
|
func (e *Enhancer) enhanceWithEngine(htmlContent []byte, filePath string) ([]byte, error) {
|
|
// Determine processing mode
|
|
var mode engine.ProcessMode
|
|
if e.config.ContentInjection {
|
|
mode = engine.Enhancement // ID generation + content injection
|
|
} else {
|
|
mode = engine.IDGeneration // ID generation only
|
|
}
|
|
|
|
// Process with engine
|
|
result, err := e.engine.ProcessContent(engine.ContentInput{
|
|
HTML: htmlContent,
|
|
FilePath: filePath,
|
|
SiteID: e.siteID,
|
|
Mode: mode,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("engine processing: %w", err)
|
|
}
|
|
|
|
// Render enhanced document
|
|
var buf strings.Builder
|
|
if err := html.Render(&buf, result.Document); err != nil {
|
|
return nil, fmt.Errorf("rendering enhanced HTML: %w", err)
|
|
}
|
|
|
|
return []byte(buf.String()), nil
|
|
}
|
|
|
|
// SetSiteID sets the site ID for the enhancer
|
|
func (e *Enhancer) SetSiteID(siteID string) {
|
|
e.siteID = siteID
|
|
}
|
|
|
|
// EnhanceInPlace performs in-place enhancement of static site files
|
|
func (e *Enhancer) EnhanceInPlace(sitePath string, siteID string) error {
|
|
// Use the provided siteID (derivation should happen at CLI level)
|
|
e.siteID = siteID
|
|
|
|
// Use EnhanceDirectory with same input and output (in-place)
|
|
return e.EnhanceDirectory(sitePath, sitePath)
|
|
}
|
|
|
|
// DeriveOrValidateSiteID automatically derives site_id for demo paths or validates for production
|
|
func DeriveOrValidateSiteID(sitePath string, configSiteID string) string {
|
|
// Check if this is a demo path
|
|
if strings.Contains(sitePath, "/demos/") || strings.Contains(sitePath, "./demos/") {
|
|
return deriveDemoSiteID(sitePath)
|
|
}
|
|
|
|
// For non-demo paths, return the configured site_id
|
|
// Validation of non-demo site_id will be handled at the CLI level
|
|
return configSiteID
|
|
}
|
|
|
|
// deriveDemoSiteID extracts site_id from demo directory structure
|
|
func deriveDemoSiteID(sitePath string) string {
|
|
// Convert to absolute path and clean it
|
|
absPath, err := filepath.Abs(sitePath)
|
|
if err != nil {
|
|
absPath = sitePath
|
|
}
|
|
absPath = filepath.Clean(absPath)
|
|
|
|
// Flattened structure - just use directory name after demos/
|
|
// demos/default -> "default"
|
|
// demos/simple -> "simple"
|
|
// demos/dan-eden-portfolio -> "dan-eden-portfolio"
|
|
|
|
parts := strings.Split(absPath, string(filepath.Separator))
|
|
|
|
// Find the demos directory index
|
|
demosIndex := -1
|
|
for i, part := range parts {
|
|
if part == "demos" {
|
|
demosIndex = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if demosIndex == -1 || demosIndex >= len(parts)-1 {
|
|
// Fallback if demos not found in path
|
|
return "default"
|
|
}
|
|
|
|
// Get the segment after demos/ and clean it
|
|
dirName := parts[demosIndex+1]
|
|
dirName = strings.TrimSuffix(dirName, "_enhanced")
|
|
|
|
return dirName
|
|
}
|
|
|
|
// 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)
|
|
}
|