From 1bf597266e58c21d84be132a7911b63216a6cb1f Mon Sep 17 00:00:00 2001 From: Joakim Date: Wed, 17 Sep 2025 14:39:34 +0200 Subject: [PATCH] Implement hybrid CSS architecture to fix white-on-white modal issue on sites with CSS resets - Migrate from inline CSS to external insertr.css with cascade layer architecture - Add CSS CDN serving capability (ServeInsertrCSS handler and /insertr.css route) - Implement hybrid approach: @layer insertr for modern browsers + html body selectors for legacy browsers - Remove scattered inline CSS from JavaScript modules for better maintainability - Solve form element spacing conflicts with aggressive site CSS resets like '* {margin:0; padding:0}' - Enable proper CSS caching and separation of concerns --- cmd/serve.go | 16 +- internal/api/handlers.go | 27 ++ internal/api/middleware.go | 1 + internal/engine/injector.go | 47 ++- justfile | 2 +- lib/package.json | 4 +- lib/src/core/auth.js | 131 +------ lib/src/core/editor.js | 203 +---------- lib/src/index.js | 35 ++ lib/src/styles/insertr.css | 662 ++++++++++++++++++++++++++++++++++++ lib/src/ui/form-renderer.js | 351 ------------------- 11 files changed, 785 insertions(+), 694 deletions(-) create mode 100644 lib/src/styles/insertr.css diff --git a/cmd/serve.go b/cmd/serve.go index beb7b51..e29ddfc 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -6,6 +6,7 @@ import ( "net/http" "os" "os/signal" + "strings" "syscall" "github.com/go-chi/chi/v5" @@ -143,6 +144,7 @@ func runServe(cmd *cobra.Command, args []string) { // Static library serving (for demo sites) router.Get("/insertr.js", contentHandler.ServeInsertrJS) + router.Get("/insertr.css", contentHandler.ServeInsertrCSS) // API routes router.Route("/api", func(apiRouter chi.Router) { @@ -163,11 +165,21 @@ func runServe(cmd *cobra.Command, args []string) { }) // Static site serving - serve registered sites at /sites/{site_id} - // This fixes the MIME type issues with Chi's FileServer + // Custom file server that fixes CSS MIME types for siteID, siteConfig := range siteManager.GetAllSites() { log.Printf("📁 Serving site %s from %s at /sites/%s/", siteID, siteConfig.Path, siteID) + + // Create custom file server with MIME type fixing fileServer := http.FileServer(http.Dir(siteConfig.Path)) - router.Handle("/sites/"+siteID+"/*", http.StripPrefix("/sites/"+siteID+"/", fileServer)) + customHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Fix MIME type for CSS files (including extensionless ones in css/ directory) + if strings.Contains(r.URL.Path, "/css/") { + w.Header().Set("Content-Type", "text/css; charset=utf-8") + } + fileServer.ServeHTTP(w, r) + }) + + router.Handle("/sites/"+siteID+"/*", http.StripPrefix("/sites/"+siteID+"/", customHandler)) } // Start server diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 306e8c8..dd9877c 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -731,3 +731,30 @@ func (h *ContentHandler) ServeInsertrJS(w http.ResponseWriter, r *http.Request) // Copy file contents to response io.Copy(w, file) } + +// ServeInsertrCSS handles GET /insertr.css - serves the insertr CSS stylesheet +func (h *ContentHandler) ServeInsertrCSS(w http.ResponseWriter, r *http.Request) { + // Path to the built insertr.css file + cssPath := "lib/dist/insertr.css" + + // Check if file exists + if _, err := os.Stat(cssPath); os.IsNotExist(err) { + http.Error(w, "insertr.css not found - run 'just build-lib' to build the library", http.StatusNotFound) + return + } + + // Open and serve the file + file, err := os.Open(cssPath) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to open insertr.css: %v", err), http.StatusInternalServerError) + return + } + defer file.Close() + + // Set appropriate headers + w.Header().Set("Content-Type", "text/css") + w.Header().Set("Cache-Control", "no-cache") // For development + + // Copy file contents to response + io.Copy(w, file) +} diff --git a/internal/api/middleware.go b/internal/api/middleware.go index dc4d9fa..25327f5 100644 --- a/internal/api/middleware.go +++ b/internal/api/middleware.go @@ -61,6 +61,7 @@ func ContentTypeMiddleware(next http.Handler) http.Handler { if r.URL.Path != "/" && !strings.HasPrefix(r.URL.Path, "/sites/") && !strings.HasPrefix(r.URL.Path, "/insertr.js") && + !strings.HasPrefix(r.URL.Path, "/insertr.css") && (r.Method == "GET" || r.Method == "POST" || r.Method == "PUT") { w.Header().Set("Content-Type", "application/json") } diff --git a/internal/engine/injector.go b/internal/engine/injector.go index ed22e3b..95c2c18 100644 --- a/internal/engine/injector.go +++ b/internal/engine/injector.go @@ -364,19 +364,20 @@ func (i *Injector) InjectEditorScript(doc *html.Node) { return } - // Create script element that loads insertr.js from our server with site configuration - scriptHTML := fmt.Sprintf(``, i.siteID) + // Create CSS and script elements that load from our server with site configuration + insertrHTML := fmt.Sprintf(` +`, i.siteID) - // Parse and inject the script - scriptDoc, err := html.Parse(strings.NewReader(scriptHTML)) + // Parse and inject the CSS and script elements + insertrDoc, err := html.Parse(strings.NewReader(insertrHTML)) if err != nil { log.Printf("Error parsing editor script HTML: %v", err) return } - // Extract and inject all script elements - if err := i.injectAllScriptElements(scriptDoc, headNode); err != nil { - log.Printf("Error injecting script elements: %v", err) + // Extract and inject all CSS and script elements + if err := i.injectAllHeadElements(insertrDoc, headNode); err != nil { + log.Printf("Error injecting CSS and script elements: %v", err) return } @@ -415,6 +416,38 @@ func (i *Injector) findAllScriptElements(node *html.Node) []*html.Node { return scripts } +// injectAllHeadElements finds and injects all head elements (link, script) from parsed HTML +func (i *Injector) injectAllHeadElements(doc *html.Node, targetNode *html.Node) error { + elements := i.findAllHeadElements(doc) + + for _, element := range elements { + // Remove from original parent + if element.Parent != nil { + element.Parent.RemoveChild(element) + } + // Add to target node + targetNode.AppendChild(element) + } + + return nil +} + +// findAllHeadElements recursively finds all link and script elements +func (i *Injector) findAllHeadElements(node *html.Node) []*html.Node { + var elements []*html.Node + + if node.Type == html.ElementNode && (node.Data == "script" || node.Data == "link") { + elements = append(elements, node) + } + + for child := node.FirstChild; child != nil; child = child.NextSibling { + childElements := i.findAllHeadElements(child) + elements = append(elements, childElements...) + } + + return elements +} + // hasInsertrGate checks if document has .insertr-gate elements func (i *Injector) hasInsertrGate(node *html.Node) bool { if node.Type == html.ElementNode { diff --git a/justfile b/justfile index e26ecc7..d7ba1e1 100644 --- a/justfile +++ b/justfile @@ -69,7 +69,7 @@ check: build: npm run build -# Build only the JavaScript library +# Build only the JavaScript library (includes CSS) build-lib: npm run build:lib diff --git a/lib/package.json b/lib/package.json index afba4c8..b14a733 100644 --- a/lib/package.json +++ b/lib/package.json @@ -10,7 +10,9 @@ "src/" ], "scripts": { - "build": "rollup -c", + "build": "npm run build:js && npm run build:css", + "build:js": "rollup -c", + "build:css": "cp src/styles/insertr.css dist/insertr.css", "build:only": "rollup -c", "watch": "rollup -c -w", "dev": "rollup -c -w" diff --git a/lib/src/core/auth.js b/lib/src/core/auth.js index 4ba2022..237ae0a 100644 --- a/lib/src/core/auth.js +++ b/lib/src/core/auth.js @@ -218,7 +218,7 @@ export class InsertrAuth { document.body.insertAdjacentHTML('beforeend', controlsHtml); // Add styles for controls - this.addControlStyles(); + } /** @@ -417,136 +417,7 @@ export class InsertrAuth { document.head.appendChild(styleSheet); } - /** - * Add styles for authentication controls - */ - addControlStyles() { - const styles = ` - .insertr-auth-controls { - position: fixed; - bottom: 20px; - right: 20px; - z-index: 9999; - display: flex; - flex-direction: column; - gap: 8px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - } - .insertr-auth-btn { - background: #4f46e5; - color: white; - border: none; - padding: 8px 16px; - border-radius: 6px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); - } - - .insertr-auth-btn:hover { - background: #4338ca; - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(0,0,0,0.15); - } - - .insertr-auth-btn.insertr-authenticated { - background: #059669; - } - - .insertr-auth-btn.insertr-authenticated:hover { - background: #047857; - } - - .insertr-auth-btn.insertr-edit-active { - background: #dc2626; - } - - .insertr-auth-btn.insertr-edit-active:hover { - background: #b91c1c; - } - - .insertr-status-controls { - position: fixed; - bottom: 20px; - left: 20px; - z-index: 9999; - display: flex; - flex-direction: column; - gap: 8px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - } - - .insertr-status { - background: white; - border: 1px solid #e5e7eb; - border-radius: 8px; - padding: 8px 12px; - box-shadow: 0 4px 12px rgba(0,0,0,0.1); - max-width: 200px; - } - - .insertr-status-content { - display: flex; - align-items: center; - gap: 8px; - } - - .insertr-status-text { - font-size: 12px; - font-weight: 500; - color: #374151; - } - - .insertr-status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: #9ca3af; - } - - .insertr-status-dot.insertr-status-visitor { - background: #9ca3af; - } - - .insertr-status-dot.insertr-status-authenticated { - background: #059669; - } - - .insertr-status-dot.insertr-status-editing { - background: #dc2626; - animation: insertr-pulse 2s infinite; - } - - @keyframes insertr-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } - } - - /* Hide editing interface when not in edit mode */ - body:not(.insertr-edit-mode) .insertr:hover::after { - display: none !important; - } - - /* Only show editing features when in edit mode */ - .insertr-authenticated.insertr-edit-mode .insertr { - cursor: pointer; - } - - .insertr-authenticated.insertr-edit-mode .insertr:hover { - outline: 2px dashed #007cba !important; - outline-offset: 2px !important; - background-color: rgba(0, 124, 186, 0.05) !important; - } - `; - - const styleSheet = document.createElement('style'); - styleSheet.type = 'text/css'; - styleSheet.innerHTML = styles; - document.head.appendChild(styleSheet); - } /** * OAuth integration placeholder diff --git a/lib/src/core/editor.js b/lib/src/core/editor.js index 7161c73..76a96f0 100644 --- a/lib/src/core/editor.js +++ b/lib/src/core/editor.js @@ -19,8 +19,7 @@ export class InsertrEditor { console.log('🚀 Starting Insertr Editor'); this.isActive = true; - // Add editor styles - this.addEditorStyles(); + // Initialize all enhanced elements const elements = this.core.getAllElements(); @@ -144,205 +143,5 @@ export class InsertrEditor { } - addEditorStyles() { - const styles = ` - .insertr-editing-hover { - outline: 2px dashed #007cba !important; - outline-offset: 2px !important; - background-color: rgba(0, 124, 186, 0.05) !important; - } - - .insertr:hover::after { - content: "✏️ " attr(data-content-type); - position: absolute; - top: -25px; - left: 0; - background: #007cba; - color: white; - padding: 2px 6px; - font-size: 11px; - border-radius: 3px; - white-space: nowrap; - z-index: 1000; - font-family: monospace; - } - /* Version History Modal Styles */ - .insertr-version-modal { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 10001; - } - - .insertr-version-backdrop { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - padding: 20px; - } - - .insertr-version-content-modal { - background: white; - border-radius: 8px; - max-width: 600px; - width: 100%; - max-height: 80vh; - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); - display: flex; - flex-direction: column; - } - - .insertr-version-header { - padding: 20px 20px 0; - border-bottom: 1px solid #eee; - display: flex; - justify-content: space-between; - align-items: center; - flex-shrink: 0; - } - - .insertr-version-header h3 { - margin: 0 0 20px; - color: #333; - font-size: 18px; - } - - .insertr-btn-close { - background: none; - border: none; - font-size: 24px; - cursor: pointer; - color: #666; - padding: 0; - width: 30px; - height: 30px; - display: flex; - align-items: center; - justify-content: center; - } - - .insertr-btn-close:hover { - color: #333; - } - - .insertr-version-list { - overflow-y: auto; - padding: 20px; - flex: 1; - } - - .insertr-version-item { - border: 1px solid #e1e5e9; - border-radius: 6px; - padding: 16px; - margin-bottom: 12px; - background: #f8f9fa; - } - - .insertr-version-meta { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 8px; - font-size: 13px; - } - - .insertr-version-label { - font-weight: 600; - color: #0969da; - } - - .insertr-version-date { - color: #656d76; - } - - .insertr-version-user { - color: #656d76; - } - - .insertr-version-content { - margin-bottom: 12px; - padding: 8px; - background: white; - border-radius: 4px; - font-family: monospace; - font-size: 14px; - color: #24292f; - white-space: pre-wrap; - } - - .insertr-version-actions { - display: flex; - gap: 8px; - } - - .insertr-btn-restore { - background: #0969da; - color: white; - border: none; - padding: 6px 12px; - border-radius: 4px; - cursor: pointer; - font-size: 12px; - font-weight: 500; - } - - .insertr-btn-restore:hover { - background: #0860ca; - } - - .insertr-btn-view-diff { - background: #f6f8fa; - color: #24292f; - border: 1px solid #d1d9e0; - padding: 6px 12px; - border-radius: 4px; - cursor: pointer; - font-size: 12px; - font-weight: 500; - } - - .insertr-btn-view-diff:hover { - background: #f3f4f6; - } - - .insertr-version-empty { - text-align: center; - color: #656d76; - font-style: italic; - padding: 40px 20px; - } - - /* History Button in Form */ - .insertr-btn-history { - background: #6f42c1; - color: white; - border: none; - padding: 8px 16px; - border-radius: 4px; - cursor: pointer; - font-size: 14px; - font-weight: 500; - margin-left: auto; - } - - .insertr-btn-history:hover { - background: #5a359a; - } - `; - - const styleSheet = document.createElement('style'); - styleSheet.type = 'text/css'; - styleSheet.innerHTML = styles; - document.head.appendChild(styleSheet); - } } \ No newline at end of file diff --git a/lib/src/index.js b/lib/src/index.js index aee8c13..d40c78b 100644 --- a/lib/src/index.js +++ b/lib/src/index.js @@ -20,6 +20,9 @@ window.Insertr = { init(options = {}) { console.log('🔧 Insertr v1.0.0 initializing... (Hot Reload Ready)'); + // Load CSS first + this.loadStyles(options); + this.core = new InsertrCore(options); this.auth = new InsertrAuth(options); this.apiClient = new ApiClient(options); @@ -35,6 +38,38 @@ window.Insertr = { return this; }, + // Load Insertr CSS styles + loadStyles(options = {}) { + // Check if styles are already loaded + if (document.querySelector('link[href*="insertr.css"]') || document.getElementById('insertr-styles')) { + return; + } + + // Determine CSS path + let cssPath = options.cssPath; + if (!cssPath) { + // Auto-detect based on script location + const insertrScript = document.querySelector('script[data-insertr-injected]'); + if (insertrScript && insertrScript.src) { + // Replace insertr.min.js with insertr.css + cssPath = insertrScript.src.replace(/insertr(\.min)?\.js$/, 'insertr.css'); + } else { + // Fallback to relative path + cssPath = 'lib/dist/insertr.css'; + } + } + + // Create and load CSS link + const link = document.createElement('link'); + link.id = 'insertr-styles'; + link.rel = 'stylesheet'; + link.href = cssPath; + link.onload = () => console.log('✅ Insertr CSS loaded'); + link.onerror = () => console.warn('⚠️ Failed to load Insertr CSS from:', cssPath); + + document.head.appendChild(link); + }, + // Start the system - only creates the minimal trigger start() { if (this.auth) { diff --git a/lib/src/styles/insertr.css b/lib/src/styles/insertr.css new file mode 100644 index 0000000..f8f39b6 --- /dev/null +++ b/lib/src/styles/insertr.css @@ -0,0 +1,662 @@ +/** + * Insertr - The Tailwind of CMS + * Unified CSS Styles with Cascade Layer Architecture + */ + +@layer insertr { + /* ================================================================= + CSS CUSTOM PROPERTIES - MODERN THEMING SYSTEM + ================================================================= */ + + :root { + /* Color System */ + --insertr-primary: #4f46e5; + --insertr-primary-hover: #4338ca; + --insertr-primary-active: #3730a3; + + --insertr-success: #10b981; + --insertr-success-hover: #059669; + + --insertr-danger: #dc2626; + --insertr-danger-hover: #b91c1c; + + --insertr-warning: #f59e0b; + --insertr-warning-hover: #d97706; + + --insertr-info: #3b82f6; + --insertr-info-hover: #2563eb; + + --insertr-purple: #6f42c1; + --insertr-purple-hover: #5a359a; + + /* Text Colors */ + --insertr-text-primary: #1f2937; + --insertr-text-secondary: #374151; + --insertr-text-muted: #6b7280; + --insertr-text-inverse: #ffffff; + + /* Background Colors */ + --insertr-bg-primary: #ffffff; + --insertr-bg-secondary: #f8fafc; + --insertr-bg-muted: #f3f4f6; + --insertr-bg-overlay: rgba(0, 0, 0, 0.5); + + /* Border Colors */ + --insertr-border-light: #e5e7eb; + --insertr-border-medium: #d1d5db; + --insertr-border-dark: #374151; + + /* Component Colors */ + --insertr-form-border: var(--insertr-border-medium); + --insertr-form-border-focus: var(--insertr-info); + + /* Shadows */ + --insertr-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1); + --insertr-shadow-md: 0 4px 8px rgba(0, 0, 0, 0.15); + --insertr-shadow-lg: 0 8px 25px rgba(0, 0, 0, 0.15); + --insertr-shadow-xl: 0 10px 25px rgba(0, 0, 0, 0.2); + + /* Spacing */ + --insertr-radius-sm: 4px; + --insertr-radius-md: 6px; + --insertr-radius-lg: 8px; + + /* Z-Index Scale */ + --insertr-z-tooltip: 1000; + --insertr-z-overlay: 9999; + --insertr-z-modal: 10000; + --insertr-z-modal-backdrop: 10001; + } + + /* Dark Theme Override (when parent site uses dark theme) */ + [data-theme="dark"] { + --insertr-text-primary: #f9fafb; + --insertr-text-secondary: #e5e7eb; + --insertr-text-muted: #9ca3af; + --insertr-bg-primary: #1f2937; + --insertr-bg-secondary: #374151; + --insertr-bg-muted: #4b5563; + --insertr-border-light: #4b5563; + --insertr-border-medium: #6b7280; + } + + /* ================================================================= + FOUNDATION & SPECIFIC TARGETING + ================================================================= + + HYBRID CSS ARCHITECTURE: + + This CSS uses a hybrid approach for maximum browser compatibility: + + 1. MODERN BROWSERS: @layer insertr provides cascade layer isolation + - Layered styles automatically win over unlayered styles + - Clean separation regardless of specificity + - Future-proof CSS architecture + + 2. LEGACY BROWSERS: Higher specificity fallback selectors + - html body .insertr-* selectors (0,0,3,0) beat universal * (0,0,0,1) + - Works in all browsers back to IE6 + - Ensures consistent behavior across environments + + This ensures our form elements maintain proper spacing and styling + even on sites with aggressive CSS resets like "* {margin:0; padding:0}" + ================================================================= */ + + /* Minimal gate foundation */ + .insertr-gate { + cursor: pointer; + user-select: none; + } + + /* UI component protection - specific targeting only */ + .insertr-edit-form, + .insertr-edit-form * { + color: var(--insertr-text-primary); + } + + .insertr-auth-controls, + .insertr-auth-controls * { + color: var(--insertr-text-primary); + } + + .insertr-version-modal, + .insertr-version-modal * { + color: var(--insertr-text-primary); + } + + /* Component-specific color overrides */ + .insertr-btn-save, + .insertr-btn-cancel, + .insertr-btn-history, + .insertr-auth-btn { + color: var(--insertr-text-inverse); + } + + .insertr-form-help { + color: var(--insertr-text-muted); + } + + .insertr-status-text { + color: var(--insertr-text-secondary); + } + + /* ================================================================= + AUTHENTICATION CONTROLS + ================================================================= */ + + .insertr-auth-controls { + position: fixed; + bottom: 20px; + right: 20px; + z-index: var(--insertr-z-overlay); + display: flex; + flex-direction: column; + gap: 8px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + } + + .insertr-auth-btn { + background: var(--insertr-primary); + color: var(--insertr-text-inverse); + border: none; + padding: 8px 16px; + border-radius: var(--insertr-radius-md); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + box-shadow: var(--insertr-shadow-sm); + } + + .insertr-auth-btn:hover { + background: var(--insertr-primary-hover); + transform: translateY(-1px); + box-shadow: var(--insertr-shadow-md); + } + + .insertr-auth-btn.insertr-authenticated { + background: var(--insertr-success); + } + + .insertr-auth-btn.insertr-authenticated:hover { + background: var(--insertr-success-hover); + } + + .insertr-auth-btn.insertr-edit-active { + background: var(--insertr-danger); + } + + .insertr-auth-btn.insertr-edit-active:hover { + background: var(--insertr-danger-hover); + } + + .insertr-status-controls { + position: fixed; + bottom: 20px; + left: 20px; + z-index: var(--insertr-z-overlay); + display: flex; + flex-direction: column; + gap: 8px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + } + + .insertr-status { + background: var(--insertr-bg-primary); + border: 1px solid var(--insertr-border-light); + border-radius: var(--insertr-radius-lg); + padding: 8px 12px; + box-shadow: var(--insertr-shadow-md); + max-width: 200px; + } + + .insertr-status-content { + display: flex; + align-items: center; + gap: 8px; + } + + .insertr-status-text { + font-size: 12px; + font-weight: 500; + color: var(--insertr-text-secondary); + } + + .insertr-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--insertr-text-muted); + } + + .insertr-status-dot.insertr-status-visitor { + background: var(--insertr-text-muted); + } + + .insertr-status-dot.insertr-status-authenticated { + background: var(--insertr-success); + } + + .insertr-status-dot.insertr-status-editing { + background: var(--insertr-danger); + animation: insertr-pulse 2s infinite; + } + + @keyframes insertr-pulse { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + } + + .insertr-authenticated.insertr-edit-mode .insertr { + cursor: pointer; + } + + .insertr-authenticated.insertr-edit-mode .insertr:hover { + outline: 2px dashed #007cba !important; + outline-offset: 2px !important; + background-color: rgba(0, 124, 186, 0.05) !important; + } + + /* ================================================================= + EDITOR STYLES + ================================================================= */ + + .insertr-editing-hover { + outline: 2px dashed var(--insertr-info) !important; + outline-offset: 2px !important; + background-color: color-mix(in srgb, var(--insertr-info) 5%, transparent) !important; + } + + .insertr:hover::after { + content: "✏️ " attr(data-content-type); + position: absolute; + top: -25px; + left: 0; + background: #007cba; + color: white; + padding: 2px 6px; + font-size: 11px; + border-radius: 3px; + white-space: nowrap; + z-index: 1000; + font-family: monospace; + } + + /* ================================================================= + MODAL AND FORM STYLES + ================================================================= */ + + /* Overlay and Form Container */ + .insertr-form-overlay { + position: absolute; + z-index: 10000; + } + + .insertr-edit-form { + background: var(--insertr-bg-primary); + border: 2px solid var(--insertr-info); + border-radius: var(--insertr-radius-lg); + padding: 1rem; + box-shadow: var(--insertr-shadow-lg); + width: 100%; + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + min-width: 600px; + max-width: 800px; + color: #1f2937; + } + + /* Form Header */ + .insertr-edit-form .insertr-form-header { + font-weight: 600; + color: var(--insertr-text-primary); + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--insertr-border-light); + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + /* Form Groups and Fields */ + .insertr-edit-form .insertr-form-group { + margin-bottom: 1rem; + } + + .insertr-edit-form .insertr-form-group:last-child { + margin-bottom: 0; + } + + /* Higher specificity fallback for browsers without cascade layer support */ + html body .insertr-edit-form .insertr-form-group { + margin-bottom: 1rem; + } + + html body .insertr-edit-form .insertr-form-group:last-child { + margin-bottom: 0; + } + + .insertr-form-label { + display: block; + font-weight: 600; + color: var(--insertr-text-secondary); + margin-bottom: 0.5rem; + font-size: 0.875rem; + } + + .insertr-edit-form .insertr-form-input, + .insertr-edit-form .insertr-form-textarea { + width: 100%; + padding: 0.75rem; + border: 1px solid var(--insertr-form-border); + border-radius: var(--insertr-radius-md); + font-family: inherit; + font-size: 1rem; + color: var(--insertr-text-primary); + background-color: var(--insertr-bg-primary); + transition: border-color 0.2s, box-shadow 0.2s; + box-sizing: border-box; + margin: 0; + } + + /* Higher specificity fallback for browsers without cascade layer support */ + html body .insertr-edit-form .insertr-form-input, + html body .insertr-edit-form .insertr-form-textarea { + padding: 0.75rem; + margin: 0; + } + + .insertr-form-input:focus, + .insertr-form-textarea:focus { + outline: none; + border-color: var(--insertr-form-border-focus); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--insertr-info) 10%, transparent); + } + + /* Markdown Editor Styling */ + .insertr-form-textarea { + min-height: 120px; + resize: vertical; + font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; + } + + .insertr-markdown-editor { + min-height: 200px; + font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; + font-size: 0.9rem; + line-height: 1.5; + color: var(--insertr-text-primary); + background-color: var(--insertr-bg-secondary); + } + + /* Form Actions */ + .insertr-edit-form .insertr-form-actions { + display: flex; + gap: 0.5rem; + justify-content: flex-end; + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--insertr-border-light); + } + + /* Higher specificity fallback for browsers without cascade layer support */ + html body .insertr-edit-form .insertr-form-actions { + margin-top: 1rem; + padding-top: 1rem; + } + + .insertr-edit-form .insertr-btn-save { + background: var(--insertr-success); + color: var(--insertr-text-inverse); + border: none; + padding: 0.5rem 1rem; + margin: 0; + border-radius: var(--insertr-radius-md); + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + } + + /* Higher specificity fallback for browsers without cascade layer support */ + html body .insertr-edit-form .insertr-btn-save { + padding: 0.5rem 1rem; + margin: 0; + } + + .insertr-btn-save:hover { + background: var(--insertr-success-hover); + transform: translateY(-1px); + box-shadow: var(--insertr-shadow-sm); + } + + .insertr-edit-form .insertr-btn-cancel { + background: var(--insertr-text-muted); + color: var(--insertr-text-inverse); + border: none; + padding: 0.5rem 1rem; + margin: 0; + border-radius: var(--insertr-radius-md); + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + } + + /* Higher specificity fallback for browsers without cascade layer support */ + html body .insertr-edit-form .insertr-btn-cancel { + padding: 0.5rem 1rem; + margin: 0; + } + + .insertr-btn-cancel:hover { + background: var(--insertr-text-secondary); + transform: translateY(-1px); + box-shadow: var(--insertr-shadow-sm); + } + + /* Preview Styling */ + .insertr-preview-active { + background-color: rgba(16, 185, 129, 0.1) !important; + border: 1px dashed #10b981 !important; + transition: all 0.2s ease; + } + + /* Form Help Text */ + .insertr-form-help { + font-size: 0.75rem; + color: #6b7280; + margin-top: 0.25rem; + font-style: italic; + } + + /* Responsive Design */ + @media (max-width: 768px) { + .insertr-edit-form { + min-width: 300px; + max-width: calc(100vw - 40px); + } + } + + /* ================================================================= + VERSION HISTORY MODAL STYLES + ================================================================= */ + + .insertr-version-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 10001; + } + + .insertr-version-backdrop { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + } + + .insertr-version-content-modal { + background: white; + border-radius: 8px; + max-width: 600px; + width: 100%; + max-height: 80vh; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); + display: flex; + flex-direction: column; + } + + .insertr-version-header { + padding: 20px 20px 0; + border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + align-items: center; + flex-shrink: 0; + } + + .insertr-version-header h3 { + margin: 0 0 20px; + color: #333; + font-size: 18px; + } + + .insertr-btn-close { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: #666; + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + } + + .insertr-btn-close:hover { + color: #333; + } + + .insertr-version-list { + overflow-y: auto; + padding: 20px; + flex: 1; + } + + .insertr-version-item { + border: 1px solid #e1e5e9; + border-radius: 6px; + padding: 16px; + margin-bottom: 12px; + background: #f8f9fa; + } + + .insertr-version-meta { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 8px; + font-size: 13px; + } + + .insertr-version-label { + font-weight: 600; + color: #0969da; + } + + .insertr-version-date { + color: #656d76; + } + + .insertr-version-user { + color: #656d76; + } + + .insertr-version-content { + margin-bottom: 12px; + padding: 8px; + background: white; + border-radius: 4px; + font-family: monospace; + font-size: 14px; + color: #24292f; + white-space: pre-wrap; + } + + .insertr-version-actions { + display: flex; + gap: 8px; + } + + .insertr-btn-restore { + background: #0969da; + color: white; + border: none; + padding: 6px 12px; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + } + + .insertr-btn-restore:hover { + background: #0860ca; + } + + .insertr-btn-view-diff { + background: #f6f8fa; + color: #24292f; + border: 1px solid #d1d9e0; + padding: 6px 12px; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + } + + .insertr-btn-view-diff:hover { + background: #f3f4f6; + } + + .insertr-version-empty { + text-align: center; + color: #656d76; + font-style: italic; + padding: 40px 20px; + } + + /* History Button in Form */ + .insertr-btn-history { + background: #6f42c1; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + margin-left: auto; + } + + .insertr-btn-history:hover { + background: #5a359a; + } +} diff --git a/lib/src/ui/form-renderer.js b/lib/src/ui/form-renderer.js index 923fc09..04f8210 100644 --- a/lib/src/ui/form-renderer.js +++ b/lib/src/ui/form-renderer.js @@ -8,7 +8,6 @@ export class InsertrFormRenderer { constructor(apiClient = null) { this.apiClient = apiClient; this.editor = new Editor(); - this.setupStyles(); } /** @@ -238,355 +237,5 @@ export class InsertrFormRenderer { return div.innerHTML; } - /** - * Setup form styles (consolidated and simplified) - */ - setupStyles() { - const styles = ` - /* Overlay and Form Container */ - .insertr-form-overlay { - position: absolute; - z-index: 10000; - } - .insertr-edit-form { - background: white; - border: 2px solid #007cba; - border-radius: 8px; - padding: 1rem; - box-shadow: 0 8px 25px rgba(0,0,0,0.15); - width: 100%; - box-sizing: border-box; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - min-width: 600px; - max-width: 800px; - } - - /* Form Header */ - .insertr-form-header { - font-weight: 600; - color: #1f2937; - margin-bottom: 1rem; - padding-bottom: 0.5rem; - border-bottom: 1px solid #e5e7eb; - font-size: 0.875rem; - text-transform: uppercase; - letter-spacing: 0.5px; - } - - /* Form Groups and Fields */ - .insertr-form-group { - margin-bottom: 1rem; - } - - .insertr-form-group:last-child { - margin-bottom: 0; - } - - .insertr-form-label { - display: block; - font-weight: 600; - color: #374151; - margin-bottom: 0.5rem; - font-size: 0.875rem; - } - - .insertr-form-input, - .insertr-form-textarea { - width: 100%; - padding: 0.75rem; - border: 1px solid #d1d5db; - border-radius: 6px; - font-family: inherit; - font-size: 1rem; - transition: border-color 0.2s, box-shadow 0.2s; - box-sizing: border-box; - } - - .insertr-form-input:focus, - .insertr-form-textarea:focus { - outline: none; - border-color: #007cba; - box-shadow: 0 0 0 3px rgba(0, 124, 186, 0.1); - } - - /* Markdown Editor Styling */ - .insertr-form-textarea { - min-height: 120px; - resize: vertical; - font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; - } - - .insertr-markdown-editor { - min-height: 200px; - font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; - font-size: 0.9rem; - line-height: 1.5; - background-color: #f8fafc; - } - - /* Form Actions */ - .insertr-form-actions { - display: flex; - gap: 0.5rem; - justify-content: flex-end; - margin-top: 1rem; - padding-top: 1rem; - border-top: 1px solid #e5e7eb; - } - - .insertr-btn-save { - background: #10b981; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 6px; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s; - font-size: 0.875rem; - } - - .insertr-btn-save:hover { - background: #059669; - } - - .insertr-btn-cancel { - background: #6b7280; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 6px; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s; - font-size: 0.875rem; - } - - .insertr-btn-cancel:hover { - background: #4b5563; - } - - .insertr-btn-history { - background: #6f42c1; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 6px; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s; - font-size: 0.875rem; - } - - .insertr-btn-history:hover { - background: #5a359a; - } - - .insertr-form-help { - font-size: 0.75rem; - color: #6b7280; - margin-top: 0.25rem; - } - - /* Live Preview Styles */ - .insertr-preview-active { - position: relative; - background: rgba(0, 124, 186, 0.05) !important; - outline: 2px solid #007cba !important; - outline-offset: 2px; - transition: all 0.3s ease; - } - - .insertr-preview-active::after { - content: "Preview"; - position: absolute; - top: -25px; - left: 0; - background: #007cba; - color: white; - padding: 2px 8px; - border-radius: 3px; - font-size: 0.75rem; - font-weight: 500; - z-index: 10001; - white-space: nowrap; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - } - - /* Responsive Design */ - @media (max-width: 768px) { - .insertr-edit-form { - min-width: 90vw; - max-width: 90vw; - } - - .insertr-preview-active::after { - top: -20px; - font-size: 0.7rem; - padding: 1px 6px; - } - } - - /* Version History Modal Styles */ - .insertr-version-modal { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 10001; - } - - .insertr-version-backdrop { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - align-items: center; - justify-content: center; - padding: 20px; - } - - .insertr-version-content-modal { - background: white; - border-radius: 8px; - max-width: 600px; - width: 100%; - max-height: 80vh; - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); - display: flex; - flex-direction: column; - } - - .insertr-version-header { - padding: 20px 20px 0; - border-bottom: 1px solid #eee; - display: flex; - justify-content: space-between; - align-items: center; - flex-shrink: 0; - } - - .insertr-version-header h3 { - margin: 0 0 20px; - color: #333; - font-size: 18px; - } - - .insertr-btn-close { - background: none; - border: none; - font-size: 24px; - cursor: pointer; - color: #666; - padding: 0; - width: 30px; - height: 30px; - display: flex; - align-items: center; - justify-content: center; - } - - .insertr-btn-close:hover { - color: #333; - } - - .insertr-version-list { - overflow-y: auto; - padding: 20px; - flex: 1; - } - - .insertr-version-item { - border: 1px solid #e1e5e9; - border-radius: 6px; - padding: 16px; - margin-bottom: 12px; - background: #f8f9fa; - } - - .insertr-version-meta { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 8px; - font-size: 13px; - } - - .insertr-version-label { - font-weight: 600; - color: #0969da; - } - - .insertr-version-date { - color: #656d76; - } - - .insertr-version-user { - color: #656d76; - } - - .insertr-version-content { - margin-bottom: 12px; - padding: 8px; - background: white; - border-radius: 4px; - font-family: monospace; - font-size: 14px; - color: #24292f; - white-space: pre-wrap; - } - - .insertr-version-actions { - display: flex; - gap: 8px; - } - - .insertr-btn-restore { - background: #0969da; - color: white; - border: none; - padding: 6px 12px; - border-radius: 4px; - cursor: pointer; - font-size: 12px; - font-weight: 500; - } - - .insertr-btn-restore:hover { - background: #0860ca; - } - - .insertr-btn-view-diff { - background: #f6f8fa; - color: #24292f; - border: 1px solid #d1d9e0; - padding: 6px 12px; - border-radius: 4px; - cursor: pointer; - font-size: 12px; - font-weight: 500; - } - - .insertr-btn-view-diff:hover { - background: #f3f4f6; - } - - .insertr-version-empty { - text-align: center; - color: #656d76; - font-style: italic; - padding: 40px 20px; - } - `; - - const styleSheet = document.createElement('style'); - styleSheet.type = 'text/css'; - styleSheet.innerHTML = styles; - document.head.appendChild(styleSheet); - } } \ No newline at end of file