From 6fef293df3e4aa8558927013b533f956f28ba489 Mon Sep 17 00:00:00 2001 From: Joakim Date: Thu, 4 Sep 2025 18:42:30 +0200 Subject: [PATCH] feat: implement flexible editor gate system - Replace automatic auth controls with developer-placed .insertr-gate elements - Add OAuth-ready authentication flow with mock implementation - Support any HTML element as gate with custom styling - Implement proper gate restoration after authentication - Move auth controls to bottom-right corner for better UX - Add editor gates to demo pages (footer link and styled button) - Maintain gates visible by default with hideGatesAfterAuth option - Prevent duplicate authentication attempts with loading states This enables small business owners to access editor via discrete footer links or custom-styled elements placed anywhere by developers. --- demo-site/about.html | 2 +- demo-site/index.html | 2 +- insertr-cli/pkg/content/assets/insertr.js | 235 ++++++++++++++++-- insertr-cli/pkg/content/assets/insertr.min.js | 2 +- insertr-cli/pkg/content/enhancer.go | 2 +- insertr-cli/pkg/content/injector.go | 25 +- lib/src/core/auth.js | 208 +++++++++++++++- lib/src/index.js | 27 +- 8 files changed, 454 insertions(+), 49 deletions(-) diff --git a/demo-site/about.html b/demo-site/about.html index 06bb94f..28b2ea0 100644 --- a/demo-site/about.html +++ b/demo-site/about.html @@ -100,7 +100,7 @@ diff --git a/demo-site/index.html b/demo-site/index.html index b57b284..29496f0 100644 --- a/demo-site/index.html +++ b/demo-site/index.html @@ -76,7 +76,7 @@ diff --git a/insertr-cli/pkg/content/assets/insertr.js b/insertr-cli/pkg/content/assets/insertr.js index e6ccb38..0712e7f 100644 --- a/insertr-cli/pkg/content/assets/insertr.js +++ b/insertr-cli/pkg/content/assets/insertr.js @@ -639,7 +639,7 @@ var Insertr = (function () { constructor(options = {}) { this.options = { mockAuth: options.mockAuth !== false, // Enable mock auth by default - autoCreateControls: options.autoCreateControls !== false, + hideGatesAfterAuth: options.hideGatesAfterAuth === true, // Keep gates visible by default ...options }; @@ -648,31 +648,191 @@ var Insertr = (function () { isAuthenticated: false, editMode: false, currentUser: null, - activeEditor: null + activeEditor: null, + isInitialized: false, + isAuthenticating: false }; this.statusIndicator = null; } /** - * Initialize authentication system + * Initialize gate system (called on page load) */ init() { - console.log('🔐 Initializing Insertr Authentication'); + console.log('🔧 Insertr: Scanning for editor gates'); - if (this.options.autoCreateControls) { - this.createAuthControls(); + this.setupEditorGates(); + } + + /** + * Initialize full editing system (called after successful OAuth) + */ + initializeFullSystem() { + if (this.state.isInitialized) { + return; // Already initialized } + + console.log('🔐 Initializing Insertr Editing System'); + this.createAuthControls(); this.setupAuthenticationControls(); this.createStatusIndicator(); this.updateBodyClasses(); - console.log('📱 Auth controls ready - Look for buttons in top-right corner'); + // Auto-enable edit mode after OAuth + this.state.editMode = true; + this.state.isInitialized = true; + + // Start the editor system + if (window.Insertr && window.Insertr.startEditor) { + window.Insertr.startEditor(); + } + + this.updateButtonStates(); + this.updateStatusIndicator(); + + console.log('📱 Editing system active - Controls in bottom-right corner'); + console.log('âœī¸ Edit mode enabled - Click elements to edit'); } /** - * Create authentication control buttons if they don't exist + * Setup editor gate click handlers for any .insertr-gate elements + */ + setupEditorGates() { + const gates = document.querySelectorAll('.insertr-gate'); + + if (gates.length === 0) { + console.log('â„šī¸ No .insertr-gate elements found - editor access disabled'); + return; + } + + console.log(`đŸšĒ Found ${gates.length} editor gate(s)`); + + // Add gate styles + this.addGateStyles(); + + gates.forEach((gate, index) => { + // Store original text for later restoration + if (!gate.hasAttribute('data-original-text')) { + gate.setAttribute('data-original-text', gate.textContent); + } + + gate.addEventListener('click', (e) => { + e.preventDefault(); + this.handleGateClick(gate, index); + }); + + // Add subtle styling to indicate it's clickable + gate.style.cursor = 'pointer'; + }); + } + + /** + * Handle click on an editor gate element + */ + async handleGateClick(gateElement, gateIndex) { + // Prevent multiple simultaneous authentication attempts + if (this.state.isAuthenticating) { + console.log('âŗ Authentication already in progress...'); + return; + } + + console.log(`🚀 Editor gate activated (gate ${gateIndex + 1})`); + this.state.isAuthenticating = true; + + // Store original text and show loading state + const originalText = gateElement.textContent; + gateElement.setAttribute('data-original-text', originalText); + gateElement.textContent = 'âŗ Signing in...'; + gateElement.style.pointerEvents = 'none'; + + try { + // Perform OAuth authentication + await this.performOAuthFlow(); + + // Initialize full editing system + this.initializeFullSystem(); + + // Conditionally hide gates based on options + if (this.options.hideGatesAfterAuth) { + this.hideAllGates(); + } else { + this.updateGateState(); + } + + } catch (error) { + console.error('❌ Authentication failed:', error); + + // Restore clicked gate to original state + const originalText = gateElement.getAttribute('data-original-text'); + if (originalText) { + gateElement.textContent = originalText; + } + gateElement.style.pointerEvents = ''; + } finally { + this.state.isAuthenticating = false; + } + } + + /** + * Perform OAuth authentication flow + */ + async performOAuthFlow() { + // In development, simulate OAuth flow + if (this.options.mockAuth) { + console.log('🔐 Mock OAuth: Simulating authentication...'); + + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Set authenticated state + this.state.isAuthenticated = true; + this.state.currentUser = { + name: 'Site Owner', + email: 'owner@example.com', + role: 'admin' + }; + + console.log('✅ Mock OAuth: Authentication successful'); + return; + } + + // TODO: In production, implement real OAuth flow + // This would redirect to OAuth provider, handle callback, etc. + throw new Error('Production OAuth not implemented yet'); + } + + /** + * Hide all editor gates after successful authentication (optional) + */ + hideAllGates() { + document.body.classList.add('insertr-hide-gates'); + console.log('đŸšĒ Editor gates hidden (hideGatesAfterAuth enabled)'); + } + + /** + * Update gate state after authentication (restore normal appearance) + */ + updateGateState() { + const gates = document.querySelectorAll('.insertr-gate'); + gates.forEach(gate => { + // Restore original text if it was saved + const originalText = gate.getAttribute('data-original-text'); + if (originalText) { + gate.textContent = originalText; + } + + // Restore interactive state + gate.style.pointerEvents = ''; + gate.style.opacity = ''; + }); + + console.log('đŸšĒ Editor gates restored to original state'); + } + + /** + * Create authentication control buttons (bottom-right positioned) */ createAuthControls() { // Check if controls already exist @@ -853,6 +1013,32 @@ var Insertr = (function () { return this.state.currentUser; } + /** + * Add minimal styles for editor gates + */ + addGateStyles() { + const styles = ` + .insertr-gate { + transition: opacity 0.2s ease; + user-select: none; + } + + .insertr-gate:hover { + opacity: 0.7; + } + + /* Optional: Hide gates when authenticated (only if hideGatesAfterAuth option is true) */ + body.insertr-hide-gates .insertr-gate { + display: none !important; + } + `; + + const styleSheet = document.createElement('style'); + styleSheet.type = 'text/css'; + styleSheet.innerHTML = styles; + document.head.appendChild(styleSheet); + } + /** * Add styles for authentication controls */ @@ -860,11 +1046,12 @@ var Insertr = (function () { const styles = ` .insertr-auth-controls { position: fixed; - top: 20px; + bottom: 20px; right: 20px; z-index: 9999; display: flex; - gap: 10px; + flex-direction: column; + gap: 8px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } @@ -914,6 +1101,7 @@ var Insertr = (function () { padding: 8px 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + max-width: 200px; } .insertr-status-content { @@ -1034,12 +1222,17 @@ var Insertr = (function () { return this; }, - // Start the editor and authentication + // Start the system - only creates the minimal trigger start() { if (this.auth) { - this.auth.init(); + this.auth.init(); // Creates footer trigger only } - if (this.editor) { + // Note: Editor is NOT started here, only when trigger is clicked + }, + + // Start the full editor system (called when trigger is activated) + startEditor() { + if (this.editor && !this.editor.isActive) { this.editor.start(); } }, @@ -1071,9 +1264,19 @@ var Insertr = (function () { version: '1.0.0' }; - // Auto-initialize in development mode - if (document.querySelector('[data-insertr-enhanced]')) { - window.Insertr.init(); + // Auto-initialize in development mode with proper DOM ready handling + function autoInitialize() { + if (document.querySelector('[data-insertr-enhanced="true"]')) { + window.Insertr.init(); + } + } + + // Run auto-initialization when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', autoInitialize); + } else { + // DOM is already ready + autoInitialize(); } var index = window.Insertr; diff --git a/insertr-cli/pkg/content/assets/insertr.min.js b/insertr-cli/pkg/content/assets/insertr.min.js index 2f98850..7b8caee 100644 --- a/insertr-cli/pkg/content/assets/insertr.min.js +++ b/insertr-cli/pkg/content/assets/insertr.min.js @@ -1 +1 @@ -var Insertr=function(){"use strict";class t{constructor(t={}){this.options={apiEndpoint:t.apiEndpoint||"/api/content",siteId:t.siteId||"default",...t}}findEnhancedElements(){return document.querySelectorAll('[data-insertr-enhanced="true"]')}getElementMetadata(t){return{contentId:t.getAttribute("data-content-id"),contentType:t.getAttribute("data-content-type"),element:t}}getAllElements(){const t=this.findEnhancedElements();return Array.from(t).map(t=>this.getElementMetadata(t))}}class e{constructor(){this.currentOverlay=null,this.setupStyles()}showEditForm(t,e,n,r){this.closeForm();const{element:i,contentId:o,contentType:s}=t,a=this.getFieldConfig(i,s),d=this.createEditForm(o,a,e),l=this.createOverlay(d);this.positionForm(i,l),this.setupFormHandlers(d,l,{onSave:n,onCancel:r}),document.body.appendChild(l),this.currentOverlay=l;const c=d.querySelector("input, textarea");return c&&setTimeout(()=>c.focus(),100),l}closeForm(){this.currentOverlay&&(this.currentOverlay.remove(),this.currentOverlay=null)}getFieldConfig(t,e){const n=t.tagName.toLowerCase(),r=Array.from(t.classList);let i={h1:{type:"text",label:"Headline",maxLength:60,placeholder:"Enter headline..."},h2:{type:"text",label:"Subheading",maxLength:80,placeholder:"Enter subheading..."},h3:{type:"text",label:"Section Title",maxLength:100,placeholder:"Enter title..."},h4:{type:"text",label:"Title",maxLength:100,placeholder:"Enter title..."},h5:{type:"text",label:"Title",maxLength:100,placeholder:"Enter title..."},h6:{type:"text",label:"Title",maxLength:100,placeholder:"Enter title..."},p:{type:"textarea",label:"Paragraph",rows:3,placeholder:"Enter paragraph text..."},a:{type:"link",label:"Link",placeholder:"Enter link text...",includeUrl:!0},span:{type:"text",label:"Text",placeholder:"Enter text..."},button:{type:"text",label:"Button Text",placeholder:"Enter button text..."}}[n]||{type:"text",label:"Text",placeholder:"Enter text..."};return r.includes("lead")&&(i={...i,label:"Lead Paragraph",rows:4,placeholder:"Enter lead paragraph..."}),"markdown"===e&&(i={...i,type:"markdown",label:"Markdown Content",rows:8}),i}createEditForm(t,e,n){const r=document.createElement("div");r.className="insertr-edit-form";let i=`
${e.label}
`;return"markdown"===e.type?i+=this.createMarkdownField(e,n):"link"===e.type&&e.includeUrl?i+=this.createLinkField(e,n):"textarea"===e.type?i+=this.createTextareaField(e,n):i+=this.createTextField(e,n),i+='\n
\n \n \n
\n ',r.innerHTML=i,r}createMarkdownField(t,e){return`\n
\n \n
\n Supports Markdown formatting (bold, italic, links, etc.)\n
\n
\n `}createLinkField(t,e){const n="object"==typeof e?e.text||"":e,r="object"==typeof e&&e.url||"";return`\n
\n \n \n
\n
\n \n \n
\n `}createTextareaField(t,e){const n="object"==typeof e?e.text||"":e;return`\n
\n \n
\n `}createTextField(t,e){const n="object"==typeof e?e.text||"":e;return`\n
\n \n
\n `}createOverlay(t){const e=document.createElement("div");return e.className="insertr-form-overlay",e.appendChild(t),e}positionForm(t,e){const n=t.getBoundingClientRect(),r=e.querySelector(".insertr-edit-form"),i=window.innerWidth;let o;o=i<768?Math.min(i-40,350):Math.min(Math.max(n.width,300),500),r.style.width=`${o}px`;const s=n.bottom+window.scrollY+10,a=Math.max(20,n.left+window.scrollX);e.style.position="absolute",e.style.top=`${s}px`,e.style.left=`${a}px`,e.style.zIndex="10000"}setupFormHandlers(t,e,{onSave:n,onCancel:r}){const i=t.querySelector(".insertr-btn-save"),o=t.querySelector(".insertr-btn-cancel");i&&i.addEventListener("click",()=>{const e=this.extractFormData(t);n(e)}),o&&o.addEventListener("click",()=>{r(),this.closeForm()});const s=t=>{"Escape"===t.key&&(r(),this.closeForm(),document.removeEventListener("keydown",s))};document.addEventListener("keydown",s),e.addEventListener("click",t=>{t.target===e&&(r(),this.closeForm())})}extractFormData(t){const e={},n=t.querySelector('input[name="text"]'),r=t.querySelector('input[name="url"]'),i=t.querySelector('input[name="content"], textarea[name="content"]');return n&&r?(e.text=n.value,e.url=r.value):i&&(e.text=i.value),e}escapeHtml(t){if("string"!=typeof t)return"";const e=document.createElement("div");return e.textContent=t,e.innerHTML}setupStyles(){const t=document.createElement("style");t.type="text/css",t.innerHTML="\n .insertr-form-overlay {\n position: absolute;\n z-index: 10000;\n }\n\n .insertr-edit-form {\n background: white;\n border: 2px solid #007cba;\n border-radius: 8px;\n padding: 1rem;\n box-shadow: 0 8px 25px rgba(0,0,0,0.15);\n width: 100%;\n box-sizing: border-box;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n .insertr-form-header {\n font-weight: 600;\n color: #1f2937;\n margin-bottom: 1rem;\n padding-bottom: 0.5rem;\n border-bottom: 1px solid #e5e7eb;\n font-size: 0.875rem;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .insertr-form-group {\n margin-bottom: 1rem;\n }\n\n .insertr-form-group:last-child {\n margin-bottom: 0;\n }\n\n .insertr-form-label {\n display: block;\n font-weight: 600;\n color: #374151;\n margin-bottom: 0.5rem;\n font-size: 0.875rem;\n }\n\n .insertr-form-input, \n .insertr-form-textarea {\n width: 100%;\n padding: 0.75rem;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n font-family: inherit;\n font-size: 1rem;\n transition: border-color 0.2s, box-shadow 0.2s;\n box-sizing: border-box;\n }\n\n .insertr-form-input:focus,\n .insertr-form-textarea:focus {\n outline: none;\n border-color: #007cba;\n box-shadow: 0 0 0 3px rgba(0, 124, 186, 0.1);\n }\n\n .insertr-form-textarea {\n min-height: 120px;\n resize: vertical;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;\n }\n\n .insertr-markdown-editor {\n min-height: 200px;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;\n font-size: 0.9rem;\n line-height: 1.5;\n background-color: #f8fafc;\n }\n\n .insertr-form-actions {\n display: flex;\n gap: 0.5rem;\n justify-content: flex-end;\n margin-top: 1rem;\n padding-top: 1rem;\n border-top: 1px solid #e5e7eb;\n }\n\n .insertr-btn-save {\n background: #10b981;\n color: white;\n border: none;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n font-weight: 500;\n cursor: pointer;\n transition: background-color 0.2s;\n font-size: 0.875rem;\n }\n\n .insertr-btn-save:hover {\n background: #059669;\n }\n\n .insertr-btn-cancel {\n background: #6b7280;\n color: white;\n border: none;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n font-weight: 500;\n cursor: pointer;\n transition: background-color 0.2s;\n font-size: 0.875rem;\n }\n\n .insertr-btn-cancel:hover {\n background: #4b5563;\n }\n\n .insertr-form-help {\n font-size: 0.75rem;\n color: #6b7280;\n margin-top: 0.25rem;\n }\n ",document.head.appendChild(t)}}class n{constructor(t,n,r={}){this.core=t,this.auth=n,this.options=r,this.isActive=!1,this.formRenderer=new e}start(){if(this.isActive)return;console.log("🚀 Starting Insertr Editor"),this.isActive=!0,this.addEditorStyles();const t=this.core.getAllElements();console.log(`📝 Found ${t.length} editable elements`),t.forEach(t=>this.initializeElement(t))}initializeElement(t){const{element:e,contentId:n,contentType:r}=t;e.style.cursor="pointer",e.style.position="relative",this.addHoverEffects(e),this.addClickHandler(e,t)}addHoverEffects(t){t.addEventListener("mouseenter",()=>{t.classList.add("insertr-editing-hover")}),t.addEventListener("mouseleave",()=>{t.classList.remove("insertr-editing-hover")})}addClickHandler(t,e){t.addEventListener("click",t=>{this.auth.isAuthenticated()&&this.auth.isEditMode()&&(t.preventDefault(),this.openEditor(e))})}openEditor(t){const{element:e}=t,n=this.extractCurrentContent(e);this.formRenderer.showEditForm(t,n,e=>this.handleSave(t,e),()=>this.handleCancel(t))}extractCurrentContent(t){return"a"===t.tagName.toLowerCase()?{text:t.textContent.trim(),url:t.getAttribute("href")||""}:t.textContent.trim()}handleSave(t,e){console.log("💾 Saving content:",t.contentId,e),this.updateElementContent(t.element,e),this.formRenderer.closeForm(),console.log("✅ Content saved:",t.contentId,e)}handleCancel(t){console.log("❌ Edit cancelled:",t.contentId)}updateElementContent(t,e){"a"===t.tagName.toLowerCase()?(void 0!==e.text&&(t.textContent=e.text),void 0!==e.url&&t.setAttribute("href",e.url)):t.textContent=e.text||""}addEditorStyles(){const t=document.createElement("style");t.type="text/css",t.innerHTML='\n .insertr-editing-hover {\n outline: 2px dashed #007cba !important;\n outline-offset: 2px !important;\n background-color: rgba(0, 124, 186, 0.05) !important;\n }\n \n [data-insertr-enhanced="true"]:hover::after {\n content: "âœī¸ " attr(data-content-type);\n position: absolute;\n top: -25px;\n left: 0;\n background: #007cba;\n color: white;\n padding: 2px 6px;\n font-size: 11px;\n border-radius: 3px;\n white-space: nowrap;\n z-index: 1000;\n font-family: monospace;\n }\n ',document.head.appendChild(t)}}class r{constructor(t={}){this.options={mockAuth:!1!==t.mockAuth,autoCreateControls:!1!==t.autoCreateControls,...t},this.state={isAuthenticated:!1,editMode:!1,currentUser:null,activeEditor:null},this.statusIndicator=null}init(){console.log("🔐 Initializing Insertr Authentication"),this.options.autoCreateControls&&this.createAuthControls(),this.setupAuthenticationControls(),this.createStatusIndicator(),this.updateBodyClasses(),console.log("📱 Auth controls ready - Look for buttons in top-right corner")}createAuthControls(){if(document.getElementById("insertr-auth-controls"))return;document.body.insertAdjacentHTML("beforeend",'\n
\n \n \n
\n '),this.addControlStyles()}setupAuthenticationControls(){const t=document.getElementById("insertr-auth-toggle"),e=document.getElementById("insertr-edit-toggle");t&&t.addEventListener("click",()=>this.toggleAuthentication()),e&&e.addEventListener("click",()=>this.toggleEditMode())}toggleAuthentication(){this.state.isAuthenticated=!this.state.isAuthenticated,this.state.currentUser=this.state.isAuthenticated?{name:"Demo User",email:"demo@example.com",role:"editor"}:null,this.state.isAuthenticated||(this.state.editMode=!1),this.updateBodyClasses(),this.updateButtonStates(),this.updateStatusIndicator(),console.log(this.state.isAuthenticated?"✅ Authenticated as Demo User":"❌ Logged out")}toggleEditMode(){this.state.isAuthenticated?(this.state.editMode=!this.state.editMode,!this.state.editMode&&this.state.activeEditor&&(this.state.activeEditor=null),this.updateBodyClasses(),this.updateButtonStates(),this.updateStatusIndicator(),console.log(this.state.editMode?"âœī¸ Edit mode ON - Click elements to edit":"👀 Edit mode OFF - Read-only view")):console.warn("❌ Cannot enable edit mode - not authenticated")}updateBodyClasses(){document.body.classList.toggle("insertr-authenticated",this.state.isAuthenticated),document.body.classList.toggle("insertr-edit-mode",this.state.editMode)}updateButtonStates(){const t=document.getElementById("insertr-auth-toggle"),e=document.getElementById("insertr-edit-toggle");t&&(t.textContent=this.state.isAuthenticated?"Logout":"Login as Client",t.className="insertr-auth-btn "+(this.state.isAuthenticated?"insertr-authenticated":"")),e&&(e.style.display=this.state.isAuthenticated?"inline-block":"none",e.textContent="Edit Mode: "+(this.state.editMode?"On":"Off"),e.className="insertr-auth-btn "+(this.state.editMode?"insertr-edit-active":""))}createStatusIndicator(){if(document.getElementById("insertr-status"))return;document.body.insertAdjacentHTML("beforeend",'\n
\n
\n Visitor Mode\n \n
\n
\n '),this.statusIndicator=document.getElementById("insertr-status"),this.updateStatusIndicator()}updateStatusIndicator(){const t=document.querySelector(".insertr-status-text"),e=document.querySelector(".insertr-status-dot");t&&e&&(this.state.isAuthenticated?this.state.editMode?(t.textContent="Editing",e.className="insertr-status-dot insertr-status-editing"):(t.textContent="Authenticated",e.className="insertr-status-dot insertr-status-authenticated"):(t.textContent="Visitor Mode",e.className="insertr-status-dot insertr-status-visitor"))}isAuthenticated(){return this.state.isAuthenticated}isEditMode(){return this.state.editMode}getCurrentUser(){return this.state.currentUser}addControlStyles(){const t=document.createElement("style");t.type="text/css",t.innerHTML="\n .insertr-auth-controls {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 9999;\n display: flex;\n gap: 10px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n .insertr-auth-btn {\n background: #4f46e5;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 6px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n\n .insertr-auth-btn:hover {\n background: #4338ca;\n transform: translateY(-1px);\n box-shadow: 0 4px 8px rgba(0,0,0,0.15);\n }\n\n .insertr-auth-btn.insertr-authenticated {\n background: #059669;\n }\n\n .insertr-auth-btn.insertr-authenticated:hover {\n background: #047857;\n }\n\n .insertr-auth-btn.insertr-edit-active {\n background: #dc2626;\n }\n\n .insertr-auth-btn.insertr-edit-active:hover {\n background: #b91c1c;\n }\n\n .insertr-status {\n position: fixed;\n bottom: 20px;\n left: 20px;\n z-index: 9999;\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n padding: 8px 12px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.1);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n .insertr-status-content {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .insertr-status-text {\n font-size: 12px;\n font-weight: 500;\n color: #374151;\n }\n\n .insertr-status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #9ca3af;\n }\n\n .insertr-status-dot.insertr-status-visitor {\n background: #9ca3af;\n }\n\n .insertr-status-dot.insertr-status-authenticated {\n background: #059669;\n }\n\n .insertr-status-dot.insertr-status-editing {\n background: #dc2626;\n animation: insertr-pulse 2s infinite;\n }\n\n @keyframes insertr-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n\n /* Hide editing interface when not in edit mode */\n body:not(.insertr-edit-mode) [data-insertr-enhanced]:hover::after {\n display: none !important;\n }\n\n /* Only show editing features when in edit mode */\n .insertr-authenticated.insertr-edit-mode [data-insertr-enhanced] {\n cursor: pointer;\n }\n\n .insertr-authenticated.insertr-edit-mode [data-insertr-enhanced]:hover {\n outline: 2px dashed #007cba !important;\n outline-offset: 2px !important;\n background-color: rgba(0, 124, 186, 0.05) !important;\n }\n ",document.head.appendChild(t)}async authenticateWithOAuth(t="google"){console.log(`🔐 Mock OAuth login with ${t}`),setTimeout(()=>{this.state.isAuthenticated=!0,this.state.currentUser={name:"OAuth User",email:"user@example.com",provider:t,role:"editor"},this.updateBodyClasses(),this.updateButtonStates(),this.updateStatusIndicator(),console.log("✅ OAuth authentication successful")},1e3)}}return window.Insertr={core:null,editor:null,auth:null,init(e={}){return console.log("🔧 Insertr v1.0.0 initializing... (Hot Reload Ready)"),this.core=new t(e),this.auth=new r(e),this.editor=new n(this.core,this.auth,e),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>this.start()):this.start(),this},start(){this.auth&&this.auth.init(),this.editor&&this.editor.start()},login(){return this.auth?this.auth.toggleAuthentication():null},logout(){this.auth&&this.auth.isAuthenticated()&&this.auth.toggleAuthentication()},toggleEditMode(){return this.auth?this.auth.toggleEditMode():null},isAuthenticated(){return!!this.auth&&this.auth.isAuthenticated()},isEditMode(){return!!this.auth&&this.auth.isEditMode()},version:"1.0.0"},document.querySelector("[data-insertr-enhanced]")&&window.Insertr.init(),window.Insertr}(); +var Insertr=function(){"use strict";class t{constructor(t={}){this.options={apiEndpoint:t.apiEndpoint||"/api/content",siteId:t.siteId||"default",...t}}findEnhancedElements(){return document.querySelectorAll('[data-insertr-enhanced="true"]')}getElementMetadata(t){return{contentId:t.getAttribute("data-content-id"),contentType:t.getAttribute("data-content-type"),element:t}}getAllElements(){const t=this.findEnhancedElements();return Array.from(t).map(t=>this.getElementMetadata(t))}}class e{constructor(){this.currentOverlay=null,this.setupStyles()}showEditForm(t,e,n,i){this.closeForm();const{element:r,contentId:o,contentType:s}=t,a=this.getFieldConfig(r,s),d=this.createEditForm(o,a,e),l=this.createOverlay(d);this.positionForm(r,l),this.setupFormHandlers(d,l,{onSave:n,onCancel:i}),document.body.appendChild(l),this.currentOverlay=l;const c=d.querySelector("input, textarea");return c&&setTimeout(()=>c.focus(),100),l}closeForm(){this.currentOverlay&&(this.currentOverlay.remove(),this.currentOverlay=null)}getFieldConfig(t,e){const n=t.tagName.toLowerCase(),i=Array.from(t.classList);let r={h1:{type:"text",label:"Headline",maxLength:60,placeholder:"Enter headline..."},h2:{type:"text",label:"Subheading",maxLength:80,placeholder:"Enter subheading..."},h3:{type:"text",label:"Section Title",maxLength:100,placeholder:"Enter title..."},h4:{type:"text",label:"Title",maxLength:100,placeholder:"Enter title..."},h5:{type:"text",label:"Title",maxLength:100,placeholder:"Enter title..."},h6:{type:"text",label:"Title",maxLength:100,placeholder:"Enter title..."},p:{type:"textarea",label:"Paragraph",rows:3,placeholder:"Enter paragraph text..."},a:{type:"link",label:"Link",placeholder:"Enter link text...",includeUrl:!0},span:{type:"text",label:"Text",placeholder:"Enter text..."},button:{type:"text",label:"Button Text",placeholder:"Enter button text..."}}[n]||{type:"text",label:"Text",placeholder:"Enter text..."};return i.includes("lead")&&(r={...r,label:"Lead Paragraph",rows:4,placeholder:"Enter lead paragraph..."}),"markdown"===e&&(r={...r,type:"markdown",label:"Markdown Content",rows:8}),r}createEditForm(t,e,n){const i=document.createElement("div");i.className="insertr-edit-form";let r=`
${e.label}
`;return"markdown"===e.type?r+=this.createMarkdownField(e,n):"link"===e.type&&e.includeUrl?r+=this.createLinkField(e,n):"textarea"===e.type?r+=this.createTextareaField(e,n):r+=this.createTextField(e,n),r+='\n
\n \n \n
\n ',i.innerHTML=r,i}createMarkdownField(t,e){return`\n
\n \n
\n Supports Markdown formatting (bold, italic, links, etc.)\n
\n
\n `}createLinkField(t,e){const n="object"==typeof e?e.text||"":e,i="object"==typeof e&&e.url||"";return`\n
\n \n \n
\n
\n \n \n
\n `}createTextareaField(t,e){const n="object"==typeof e?e.text||"":e;return`\n
\n \n
\n `}createTextField(t,e){const n="object"==typeof e?e.text||"":e;return`\n
\n \n
\n `}createOverlay(t){const e=document.createElement("div");return e.className="insertr-form-overlay",e.appendChild(t),e}positionForm(t,e){const n=t.getBoundingClientRect(),i=e.querySelector(".insertr-edit-form"),r=window.innerWidth;let o;o=r<768?Math.min(r-40,350):Math.min(Math.max(n.width,300),500),i.style.width=`${o}px`;const s=n.bottom+window.scrollY+10,a=Math.max(20,n.left+window.scrollX);e.style.position="absolute",e.style.top=`${s}px`,e.style.left=`${a}px`,e.style.zIndex="10000"}setupFormHandlers(t,e,{onSave:n,onCancel:i}){const r=t.querySelector(".insertr-btn-save"),o=t.querySelector(".insertr-btn-cancel");r&&r.addEventListener("click",()=>{const e=this.extractFormData(t);n(e)}),o&&o.addEventListener("click",()=>{i(),this.closeForm()});const s=t=>{"Escape"===t.key&&(i(),this.closeForm(),document.removeEventListener("keydown",s))};document.addEventListener("keydown",s),e.addEventListener("click",t=>{t.target===e&&(i(),this.closeForm())})}extractFormData(t){const e={},n=t.querySelector('input[name="text"]'),i=t.querySelector('input[name="url"]'),r=t.querySelector('input[name="content"], textarea[name="content"]');return n&&i?(e.text=n.value,e.url=i.value):r&&(e.text=r.value),e}escapeHtml(t){if("string"!=typeof t)return"";const e=document.createElement("div");return e.textContent=t,e.innerHTML}setupStyles(){const t=document.createElement("style");t.type="text/css",t.innerHTML="\n .insertr-form-overlay {\n position: absolute;\n z-index: 10000;\n }\n\n .insertr-edit-form {\n background: white;\n border: 2px solid #007cba;\n border-radius: 8px;\n padding: 1rem;\n box-shadow: 0 8px 25px rgba(0,0,0,0.15);\n width: 100%;\n box-sizing: border-box;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n .insertr-form-header {\n font-weight: 600;\n color: #1f2937;\n margin-bottom: 1rem;\n padding-bottom: 0.5rem;\n border-bottom: 1px solid #e5e7eb;\n font-size: 0.875rem;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .insertr-form-group {\n margin-bottom: 1rem;\n }\n\n .insertr-form-group:last-child {\n margin-bottom: 0;\n }\n\n .insertr-form-label {\n display: block;\n font-weight: 600;\n color: #374151;\n margin-bottom: 0.5rem;\n font-size: 0.875rem;\n }\n\n .insertr-form-input, \n .insertr-form-textarea {\n width: 100%;\n padding: 0.75rem;\n border: 1px solid #d1d5db;\n border-radius: 6px;\n font-family: inherit;\n font-size: 1rem;\n transition: border-color 0.2s, box-shadow 0.2s;\n box-sizing: border-box;\n }\n\n .insertr-form-input:focus,\n .insertr-form-textarea:focus {\n outline: none;\n border-color: #007cba;\n box-shadow: 0 0 0 3px rgba(0, 124, 186, 0.1);\n }\n\n .insertr-form-textarea {\n min-height: 120px;\n resize: vertical;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;\n }\n\n .insertr-markdown-editor {\n min-height: 200px;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;\n font-size: 0.9rem;\n line-height: 1.5;\n background-color: #f8fafc;\n }\n\n .insertr-form-actions {\n display: flex;\n gap: 0.5rem;\n justify-content: flex-end;\n margin-top: 1rem;\n padding-top: 1rem;\n border-top: 1px solid #e5e7eb;\n }\n\n .insertr-btn-save {\n background: #10b981;\n color: white;\n border: none;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n font-weight: 500;\n cursor: pointer;\n transition: background-color 0.2s;\n font-size: 0.875rem;\n }\n\n .insertr-btn-save:hover {\n background: #059669;\n }\n\n .insertr-btn-cancel {\n background: #6b7280;\n color: white;\n border: none;\n padding: 0.5rem 1rem;\n border-radius: 6px;\n font-weight: 500;\n cursor: pointer;\n transition: background-color 0.2s;\n font-size: 0.875rem;\n }\n\n .insertr-btn-cancel:hover {\n background: #4b5563;\n }\n\n .insertr-form-help {\n font-size: 0.75rem;\n color: #6b7280;\n margin-top: 0.25rem;\n }\n ",document.head.appendChild(t)}}class n{constructor(t,n,i={}){this.core=t,this.auth=n,this.options=i,this.isActive=!1,this.formRenderer=new e}start(){if(this.isActive)return;console.log("🚀 Starting Insertr Editor"),this.isActive=!0,this.addEditorStyles();const t=this.core.getAllElements();console.log(`📝 Found ${t.length} editable elements`),t.forEach(t=>this.initializeElement(t))}initializeElement(t){const{element:e,contentId:n,contentType:i}=t;e.style.cursor="pointer",e.style.position="relative",this.addHoverEffects(e),this.addClickHandler(e,t)}addHoverEffects(t){t.addEventListener("mouseenter",()=>{t.classList.add("insertr-editing-hover")}),t.addEventListener("mouseleave",()=>{t.classList.remove("insertr-editing-hover")})}addClickHandler(t,e){t.addEventListener("click",t=>{this.auth.isAuthenticated()&&this.auth.isEditMode()&&(t.preventDefault(),this.openEditor(e))})}openEditor(t){const{element:e}=t,n=this.extractCurrentContent(e);this.formRenderer.showEditForm(t,n,e=>this.handleSave(t,e),()=>this.handleCancel(t))}extractCurrentContent(t){return"a"===t.tagName.toLowerCase()?{text:t.textContent.trim(),url:t.getAttribute("href")||""}:t.textContent.trim()}handleSave(t,e){console.log("💾 Saving content:",t.contentId,e),this.updateElementContent(t.element,e),this.formRenderer.closeForm(),console.log("✅ Content saved:",t.contentId,e)}handleCancel(t){console.log("❌ Edit cancelled:",t.contentId)}updateElementContent(t,e){"a"===t.tagName.toLowerCase()?(void 0!==e.text&&(t.textContent=e.text),void 0!==e.url&&t.setAttribute("href",e.url)):t.textContent=e.text||""}addEditorStyles(){const t=document.createElement("style");t.type="text/css",t.innerHTML='\n .insertr-editing-hover {\n outline: 2px dashed #007cba !important;\n outline-offset: 2px !important;\n background-color: rgba(0, 124, 186, 0.05) !important;\n }\n \n [data-insertr-enhanced="true"]:hover::after {\n content: "âœī¸ " attr(data-content-type);\n position: absolute;\n top: -25px;\n left: 0;\n background: #007cba;\n color: white;\n padding: 2px 6px;\n font-size: 11px;\n border-radius: 3px;\n white-space: nowrap;\n z-index: 1000;\n font-family: monospace;\n }\n ',document.head.appendChild(t)}}class i{constructor(t={}){this.options={mockAuth:!1!==t.mockAuth,hideGatesAfterAuth:!0===t.hideGatesAfterAuth,...t},this.state={isAuthenticated:!1,editMode:!1,currentUser:null,activeEditor:null,isInitialized:!1,isAuthenticating:!1},this.statusIndicator=null}init(){console.log("🔧 Insertr: Scanning for editor gates"),this.setupEditorGates()}initializeFullSystem(){this.state.isInitialized||(console.log("🔐 Initializing Insertr Editing System"),this.createAuthControls(),this.setupAuthenticationControls(),this.createStatusIndicator(),this.updateBodyClasses(),this.state.editMode=!0,this.state.isInitialized=!0,window.Insertr&&window.Insertr.startEditor&&window.Insertr.startEditor(),this.updateButtonStates(),this.updateStatusIndicator(),console.log("📱 Editing system active - Controls in bottom-right corner"),console.log("âœī¸ Edit mode enabled - Click elements to edit"))}setupEditorGates(){const t=document.querySelectorAll(".insertr-gate");0!==t.length?(console.log(`đŸšĒ Found ${t.length} editor gate(s)`),this.addGateStyles(),t.forEach((t,e)=>{t.hasAttribute("data-original-text")||t.setAttribute("data-original-text",t.textContent),t.addEventListener("click",n=>{n.preventDefault(),this.handleGateClick(t,e)}),t.style.cursor="pointer"})):console.log("â„šī¸ No .insertr-gate elements found - editor access disabled")}async handleGateClick(t,e){if(this.state.isAuthenticating)return void console.log("âŗ Authentication already in progress...");console.log(`🚀 Editor gate activated (gate ${e+1})`),this.state.isAuthenticating=!0;const n=t.textContent;t.setAttribute("data-original-text",n),t.textContent="âŗ Signing in...",t.style.pointerEvents="none";try{await this.performOAuthFlow(),this.initializeFullSystem(),this.options.hideGatesAfterAuth?this.hideAllGates():this.updateGateState()}catch(e){console.error("❌ Authentication failed:",e);const n=t.getAttribute("data-original-text");n&&(t.textContent=n),t.style.pointerEvents=""}finally{this.state.isAuthenticating=!1}}async performOAuthFlow(){if(this.options.mockAuth)return console.log("🔐 Mock OAuth: Simulating authentication..."),await new Promise(t=>setTimeout(t,1e3)),this.state.isAuthenticated=!0,this.state.currentUser={name:"Site Owner",email:"owner@example.com",role:"admin"},void console.log("✅ Mock OAuth: Authentication successful");throw new Error("Production OAuth not implemented yet")}hideAllGates(){document.body.classList.add("insertr-hide-gates"),console.log("đŸšĒ Editor gates hidden (hideGatesAfterAuth enabled)")}updateGateState(){document.querySelectorAll(".insertr-gate").forEach(t=>{const e=t.getAttribute("data-original-text");e&&(t.textContent=e),t.style.pointerEvents="",t.style.opacity=""}),console.log("đŸšĒ Editor gates restored to original state")}createAuthControls(){if(document.getElementById("insertr-auth-controls"))return;document.body.insertAdjacentHTML("beforeend",'\n
\n \n \n
\n '),this.addControlStyles()}setupAuthenticationControls(){const t=document.getElementById("insertr-auth-toggle"),e=document.getElementById("insertr-edit-toggle");t&&t.addEventListener("click",()=>this.toggleAuthentication()),e&&e.addEventListener("click",()=>this.toggleEditMode())}toggleAuthentication(){this.state.isAuthenticated=!this.state.isAuthenticated,this.state.currentUser=this.state.isAuthenticated?{name:"Demo User",email:"demo@example.com",role:"editor"}:null,this.state.isAuthenticated||(this.state.editMode=!1),this.updateBodyClasses(),this.updateButtonStates(),this.updateStatusIndicator(),console.log(this.state.isAuthenticated?"✅ Authenticated as Demo User":"❌ Logged out")}toggleEditMode(){this.state.isAuthenticated?(this.state.editMode=!this.state.editMode,!this.state.editMode&&this.state.activeEditor&&(this.state.activeEditor=null),this.updateBodyClasses(),this.updateButtonStates(),this.updateStatusIndicator(),console.log(this.state.editMode?"âœī¸ Edit mode ON - Click elements to edit":"👀 Edit mode OFF - Read-only view")):console.warn("❌ Cannot enable edit mode - not authenticated")}updateBodyClasses(){document.body.classList.toggle("insertr-authenticated",this.state.isAuthenticated),document.body.classList.toggle("insertr-edit-mode",this.state.editMode)}updateButtonStates(){const t=document.getElementById("insertr-auth-toggle"),e=document.getElementById("insertr-edit-toggle");t&&(t.textContent=this.state.isAuthenticated?"Logout":"Login as Client",t.className="insertr-auth-btn "+(this.state.isAuthenticated?"insertr-authenticated":"")),e&&(e.style.display=this.state.isAuthenticated?"inline-block":"none",e.textContent="Edit Mode: "+(this.state.editMode?"On":"Off"),e.className="insertr-auth-btn "+(this.state.editMode?"insertr-edit-active":""))}createStatusIndicator(){if(document.getElementById("insertr-status"))return;document.body.insertAdjacentHTML("beforeend",'\n
\n
\n Visitor Mode\n \n
\n
\n '),this.statusIndicator=document.getElementById("insertr-status"),this.updateStatusIndicator()}updateStatusIndicator(){const t=document.querySelector(".insertr-status-text"),e=document.querySelector(".insertr-status-dot");t&&e&&(this.state.isAuthenticated?this.state.editMode?(t.textContent="Editing",e.className="insertr-status-dot insertr-status-editing"):(t.textContent="Authenticated",e.className="insertr-status-dot insertr-status-authenticated"):(t.textContent="Visitor Mode",e.className="insertr-status-dot insertr-status-visitor"))}isAuthenticated(){return this.state.isAuthenticated}isEditMode(){return this.state.editMode}getCurrentUser(){return this.state.currentUser}addGateStyles(){const t=document.createElement("style");t.type="text/css",t.innerHTML="\n .insertr-gate {\n transition: opacity 0.2s ease;\n user-select: none;\n }\n\n .insertr-gate:hover {\n opacity: 0.7;\n }\n\n /* Optional: Hide gates when authenticated (only if hideGatesAfterAuth option is true) */\n body.insertr-hide-gates .insertr-gate {\n display: none !important;\n }\n ",document.head.appendChild(t)}addControlStyles(){const t=document.createElement("style");t.type="text/css",t.innerHTML="\n .insertr-auth-controls {\n position: fixed;\n bottom: 20px;\n right: 20px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n gap: 8px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n .insertr-auth-btn {\n background: #4f46e5;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 6px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n }\n\n .insertr-auth-btn:hover {\n background: #4338ca;\n transform: translateY(-1px);\n box-shadow: 0 4px 8px rgba(0,0,0,0.15);\n }\n\n .insertr-auth-btn.insertr-authenticated {\n background: #059669;\n }\n\n .insertr-auth-btn.insertr-authenticated:hover {\n background: #047857;\n }\n\n .insertr-auth-btn.insertr-edit-active {\n background: #dc2626;\n }\n\n .insertr-auth-btn.insertr-edit-active:hover {\n background: #b91c1c;\n }\n\n .insertr-status {\n position: fixed;\n bottom: 20px;\n left: 20px;\n z-index: 9999;\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n padding: 8px 12px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.1);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n max-width: 200px;\n }\n\n .insertr-status-content {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .insertr-status-text {\n font-size: 12px;\n font-weight: 500;\n color: #374151;\n }\n\n .insertr-status-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #9ca3af;\n }\n\n .insertr-status-dot.insertr-status-visitor {\n background: #9ca3af;\n }\n\n .insertr-status-dot.insertr-status-authenticated {\n background: #059669;\n }\n\n .insertr-status-dot.insertr-status-editing {\n background: #dc2626;\n animation: insertr-pulse 2s infinite;\n }\n\n @keyframes insertr-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n\n /* Hide editing interface when not in edit mode */\n body:not(.insertr-edit-mode) [data-insertr-enhanced]:hover::after {\n display: none !important;\n }\n\n /* Only show editing features when in edit mode */\n .insertr-authenticated.insertr-edit-mode [data-insertr-enhanced] {\n cursor: pointer;\n }\n\n .insertr-authenticated.insertr-edit-mode [data-insertr-enhanced]:hover {\n outline: 2px dashed #007cba !important;\n outline-offset: 2px !important;\n background-color: rgba(0, 124, 186, 0.05) !important;\n }\n ",document.head.appendChild(t)}async authenticateWithOAuth(t="google"){console.log(`🔐 Mock OAuth login with ${t}`),setTimeout(()=>{this.state.isAuthenticated=!0,this.state.currentUser={name:"OAuth User",email:"user@example.com",provider:t,role:"editor"},this.updateBodyClasses(),this.updateButtonStates(),this.updateStatusIndicator(),console.log("✅ OAuth authentication successful")},1e3)}}function r(){document.querySelector('[data-insertr-enhanced="true"]')&&window.Insertr.init()}return window.Insertr={core:null,editor:null,auth:null,init(e={}){return console.log("🔧 Insertr v1.0.0 initializing... (Hot Reload Ready)"),this.core=new t(e),this.auth=new i(e),this.editor=new n(this.core,this.auth,e),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>this.start()):this.start(),this},start(){this.auth&&this.auth.init()},startEditor(){this.editor&&!this.editor.isActive&&this.editor.start()},login(){return this.auth?this.auth.toggleAuthentication():null},logout(){this.auth&&this.auth.isAuthenticated()&&this.auth.toggleAuthentication()},toggleEditMode(){return this.auth?this.auth.toggleEditMode():null},isAuthenticated(){return!!this.auth&&this.auth.isAuthenticated()},isEditMode(){return!!this.auth&&this.auth.isEditMode()},version:"1.0.0"},"loading"===document.readyState?document.addEventListener("DOMContentLoaded",r):r(),window.Insertr}(); diff --git a/insertr-cli/pkg/content/enhancer.go b/insertr-cli/pkg/content/enhancer.go index 6c6815e..1de83ff 100644 --- a/insertr-cli/pkg/content/enhancer.go +++ b/insertr-cli/pkg/content/enhancer.go @@ -69,7 +69,7 @@ func (e *Enhancer) EnhanceFile(inputPath, outputPath string) error { } // Inject editor assets for development - libraryScript := GetLibraryScript(true) // Use minified for better performance + libraryScript := GetLibraryScript(false) // Use non-minified for development debugging e.injector.InjectEditorAssets(doc, true, libraryScript) // Write enhanced HTML diff --git a/insertr-cli/pkg/content/injector.go b/insertr-cli/pkg/content/injector.go index dfb2433..bbb55e6 100644 --- a/insertr-cli/pkg/content/injector.go +++ b/insertr-cli/pkg/content/injector.go @@ -2,6 +2,7 @@ package content import ( "fmt" + "strings" "golang.org/x/net/html" ) @@ -145,21 +146,19 @@ func (i *Injector) InjectEditorAssets(doc *html.Node, isDevelopment bool, librar } // Add inline script with embedded library - script := &html.Node{ - Type: html.ElementNode, - Data: "script", - Attr: []html.Attribute{ - {Key: "type", Val: "text/javascript"}, - }, - } + // 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) - // Add the library content as text node - textNode := &html.Node{ - Type: html.TextNode, - Data: 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) + } } - script.AppendChild(textNode) - head.AppendChild(script) } // findHeadElement finds the element in the document diff --git a/lib/src/core/auth.js b/lib/src/core/auth.js index b2c26e9..9ec3e0a 100644 --- a/lib/src/core/auth.js +++ b/lib/src/core/auth.js @@ -6,7 +6,7 @@ export class InsertrAuth { constructor(options = {}) { this.options = { mockAuth: options.mockAuth !== false, // Enable mock auth by default - autoCreateControls: options.autoCreateControls !== false, + hideGatesAfterAuth: options.hideGatesAfterAuth === true, // Keep gates visible by default ...options }; @@ -15,31 +15,191 @@ export class InsertrAuth { isAuthenticated: false, editMode: false, currentUser: null, - activeEditor: null + activeEditor: null, + isInitialized: false, + isAuthenticating: false }; this.statusIndicator = null; } /** - * Initialize authentication system + * Initialize gate system (called on page load) */ init() { - console.log('🔐 Initializing Insertr Authentication'); + console.log('🔧 Insertr: Scanning for editor gates'); - if (this.options.autoCreateControls) { - this.createAuthControls(); + this.setupEditorGates(); + } + + /** + * Initialize full editing system (called after successful OAuth) + */ + initializeFullSystem() { + if (this.state.isInitialized) { + return; // Already initialized } + + console.log('🔐 Initializing Insertr Editing System'); + this.createAuthControls(); this.setupAuthenticationControls(); this.createStatusIndicator(); this.updateBodyClasses(); - console.log('📱 Auth controls ready - Look for buttons in top-right corner'); + // Auto-enable edit mode after OAuth + this.state.editMode = true; + this.state.isInitialized = true; + + // Start the editor system + if (window.Insertr && window.Insertr.startEditor) { + window.Insertr.startEditor(); + } + + this.updateButtonStates(); + this.updateStatusIndicator(); + + console.log('📱 Editing system active - Controls in bottom-right corner'); + console.log('âœī¸ Edit mode enabled - Click elements to edit'); } /** - * Create authentication control buttons if they don't exist + * Setup editor gate click handlers for any .insertr-gate elements + */ + setupEditorGates() { + const gates = document.querySelectorAll('.insertr-gate'); + + if (gates.length === 0) { + console.log('â„šī¸ No .insertr-gate elements found - editor access disabled'); + return; + } + + console.log(`đŸšĒ Found ${gates.length} editor gate(s)`); + + // Add gate styles + this.addGateStyles(); + + gates.forEach((gate, index) => { + // Store original text for later restoration + if (!gate.hasAttribute('data-original-text')) { + gate.setAttribute('data-original-text', gate.textContent); + } + + gate.addEventListener('click', (e) => { + e.preventDefault(); + this.handleGateClick(gate, index); + }); + + // Add subtle styling to indicate it's clickable + gate.style.cursor = 'pointer'; + }); + } + + /** + * Handle click on an editor gate element + */ + async handleGateClick(gateElement, gateIndex) { + // Prevent multiple simultaneous authentication attempts + if (this.state.isAuthenticating) { + console.log('âŗ Authentication already in progress...'); + return; + } + + console.log(`🚀 Editor gate activated (gate ${gateIndex + 1})`); + this.state.isAuthenticating = true; + + // Store original text and show loading state + const originalText = gateElement.textContent; + gateElement.setAttribute('data-original-text', originalText); + gateElement.textContent = 'âŗ Signing in...'; + gateElement.style.pointerEvents = 'none'; + + try { + // Perform OAuth authentication + await this.performOAuthFlow(); + + // Initialize full editing system + this.initializeFullSystem(); + + // Conditionally hide gates based on options + if (this.options.hideGatesAfterAuth) { + this.hideAllGates(); + } else { + this.updateGateState(); + } + + } catch (error) { + console.error('❌ Authentication failed:', error); + + // Restore clicked gate to original state + const originalText = gateElement.getAttribute('data-original-text'); + if (originalText) { + gateElement.textContent = originalText; + } + gateElement.style.pointerEvents = ''; + } finally { + this.state.isAuthenticating = false; + } + } + + /** + * Perform OAuth authentication flow + */ + async performOAuthFlow() { + // In development, simulate OAuth flow + if (this.options.mockAuth) { + console.log('🔐 Mock OAuth: Simulating authentication...'); + + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Set authenticated state + this.state.isAuthenticated = true; + this.state.currentUser = { + name: 'Site Owner', + email: 'owner@example.com', + role: 'admin' + }; + + console.log('✅ Mock OAuth: Authentication successful'); + return; + } + + // TODO: In production, implement real OAuth flow + // This would redirect to OAuth provider, handle callback, etc. + throw new Error('Production OAuth not implemented yet'); + } + + /** + * Hide all editor gates after successful authentication (optional) + */ + hideAllGates() { + document.body.classList.add('insertr-hide-gates'); + console.log('đŸšĒ Editor gates hidden (hideGatesAfterAuth enabled)'); + } + + /** + * Update gate state after authentication (restore normal appearance) + */ + updateGateState() { + const gates = document.querySelectorAll('.insertr-gate'); + gates.forEach(gate => { + // Restore original text if it was saved + const originalText = gate.getAttribute('data-original-text'); + if (originalText) { + gate.textContent = originalText; + } + + // Restore interactive state + gate.style.pointerEvents = ''; + gate.style.opacity = ''; + }); + + console.log('đŸšĒ Editor gates restored to original state'); + } + + /** + * Create authentication control buttons (bottom-right positioned) */ createAuthControls() { // Check if controls already exist @@ -220,6 +380,32 @@ export class InsertrAuth { return this.state.currentUser; } + /** + * Add minimal styles for editor gates + */ + addGateStyles() { + const styles = ` + .insertr-gate { + transition: opacity 0.2s ease; + user-select: none; + } + + .insertr-gate:hover { + opacity: 0.7; + } + + /* Optional: Hide gates when authenticated (only if hideGatesAfterAuth option is true) */ + body.insertr-hide-gates .insertr-gate { + display: none !important; + } + `; + + const styleSheet = document.createElement('style'); + styleSheet.type = 'text/css'; + styleSheet.innerHTML = styles; + document.head.appendChild(styleSheet); + } + /** * Add styles for authentication controls */ @@ -227,11 +413,12 @@ export class InsertrAuth { const styles = ` .insertr-auth-controls { position: fixed; - top: 20px; + bottom: 20px; right: 20px; z-index: 9999; display: flex; - gap: 10px; + flex-direction: column; + gap: 8px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } @@ -281,6 +468,7 @@ export class InsertrAuth { padding: 8px 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + max-width: 200px; } .insertr-status-content { diff --git a/lib/src/index.js b/lib/src/index.js index d66a5be..995f78f 100644 --- a/lib/src/index.js +++ b/lib/src/index.js @@ -33,12 +33,17 @@ window.Insertr = { return this; }, - // Start the editor and authentication + // Start the system - only creates the minimal trigger start() { if (this.auth) { - this.auth.init(); + this.auth.init(); // Creates footer trigger only } - if (this.editor) { + // Note: Editor is NOT started here, only when trigger is clicked + }, + + // Start the full editor system (called when trigger is activated) + startEditor() { + if (this.editor && !this.editor.isActive) { this.editor.start(); } }, @@ -70,9 +75,19 @@ window.Insertr = { version: '1.0.0' }; -// Auto-initialize in development mode -if (document.querySelector('[data-insertr-enhanced]')) { - window.Insertr.init(); +// Auto-initialize in development mode with proper DOM ready handling +function autoInitialize() { + if (document.querySelector('[data-insertr-enhanced="true"]')) { + window.Insertr.init(); + } +} + +// Run auto-initialization when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', autoInitialize); +} else { + // DOM is already ready + autoInitialize(); } export default window.Insertr; \ No newline at end of file