diff --git a/TODO.md b/TODO.md
index 2d44260..046d178 100644
--- a/TODO.md
+++ b/TODO.md
@@ -25,11 +25,20 @@ Bring the current library (`lib/`) up to feature parity with the archived protot
### 🔴 Phase 1: Critical Foundation (IMMEDIATE)
-#### 1.1 Authentication System
-- [ ] Add state management for authentication and edit mode
-- [ ] Implement body class management (`insertr-authenticated`, `insertr-edit-mode`)
-- [ ] Create authentication controls (login/logout toggle)
-- [ ] Add edit mode toggle (separate from authentication)
+#### 1.1 Authentication System ✅ **COMPLETED**
+- [x] Add state management for authentication and edit mode
+- [x] Implement body class management (`insertr-authenticated`, `insertr-edit-mode`)
+- [x] Create authentication controls (login/logout toggle)
+- [x] Add edit mode toggle (separate from authentication)
+
+**Implementation Details:**
+- Created `lib/src/core/auth.js` with complete state management
+- Auto-creates authentication controls in top-right corner if missing
+- Two-step process: Login → Enable Edit Mode → Click to edit
+- Visual state indicators: status badge (bottom-left) + body classes
+- Mock OAuth integration placeholder for production use
+- Protected editing: only authenticated users in edit mode can edit
+- Professional UI with status indicators and smooth transitions
#### 1.2 Professional Edit Forms ⭐ **HIGH IMPACT** ✅ **COMPLETED**
- [x] Replace prompt() with professional modal overlays
diff --git a/insertr-cli/pkg/content/assets/insertr.js b/insertr-cli/pkg/content/assets/insertr.js
index f181041..e6ccb38 100644
--- a/insertr-cli/pkg/content/assets/insertr.js
+++ b/insertr-cli/pkg/content/assets/insertr.js
@@ -482,8 +482,9 @@ var Insertr = (function () {
* InsertrEditor - Visual editing functionality
*/
class InsertrEditor {
- constructor(core, options = {}) {
+ constructor(core, auth, options = {}) {
this.core = core;
+ this.auth = auth;
this.options = options;
this.isActive = false;
this.formRenderer = new InsertrFormRenderer();
@@ -529,6 +530,11 @@ var Insertr = (function () {
addClickHandler(element, meta) {
element.addEventListener('click', (e) => {
+ // Only allow editing if authenticated and in edit mode
+ if (!this.auth.isAuthenticated() || !this.auth.isEditMode()) {
+ return; // Let normal click behavior happen
+ }
+
e.preventDefault();
this.openEditor(meta);
});
@@ -625,6 +631,378 @@ var Insertr = (function () {
}
}
+ /**
+ * InsertrAuth - Authentication and state management
+ * Handles user authentication, edit mode, and visual state changes
+ */
+ class InsertrAuth {
+ constructor(options = {}) {
+ this.options = {
+ mockAuth: options.mockAuth !== false, // Enable mock auth by default
+ autoCreateControls: options.autoCreateControls !== false,
+ ...options
+ };
+
+ // Authentication state
+ this.state = {
+ isAuthenticated: false,
+ editMode: false,
+ currentUser: null,
+ activeEditor: null
+ };
+
+ this.statusIndicator = null;
+ }
+
+ /**
+ * Initialize authentication system
+ */
+ init() {
+ console.log('🔐 Initializing Insertr Authentication');
+
+ if (this.options.autoCreateControls) {
+ this.createAuthControls();
+ }
+
+ this.setupAuthenticationControls();
+ this.createStatusIndicator();
+ this.updateBodyClasses();
+
+ console.log('📱 Auth controls ready - Look for buttons in top-right corner');
+ }
+
+ /**
+ * Create authentication control buttons if they don't exist
+ */
+ createAuthControls() {
+ // Check if controls already exist
+ if (document.getElementById('insertr-auth-controls')) {
+ return;
+ }
+
+ const controlsHtml = `
+
+
+
+
+ `;
+
+ // Add controls to page
+ document.body.insertAdjacentHTML('beforeend', controlsHtml);
+
+ // Add styles for controls
+ this.addControlStyles();
+ }
+
+ /**
+ * Setup event listeners for authentication controls
+ */
+ setupAuthenticationControls() {
+ const authToggle = document.getElementById('insertr-auth-toggle');
+ const editToggle = document.getElementById('insertr-edit-toggle');
+
+ if (authToggle) {
+ authToggle.addEventListener('click', () => this.toggleAuthentication());
+ }
+
+ if (editToggle) {
+ editToggle.addEventListener('click', () => this.toggleEditMode());
+ }
+ }
+
+ /**
+ * Toggle authentication state
+ */
+ toggleAuthentication() {
+ this.state.isAuthenticated = !this.state.isAuthenticated;
+ this.state.currentUser = this.state.isAuthenticated ? {
+ name: 'Demo User',
+ email: 'demo@example.com',
+ role: 'editor'
+ } : null;
+
+ // Reset edit mode when logging out
+ if (!this.state.isAuthenticated) {
+ this.state.editMode = false;
+ }
+
+ this.updateBodyClasses();
+ this.updateButtonStates();
+ this.updateStatusIndicator();
+
+ console.log(this.state.isAuthenticated
+ ? '✅ Authenticated as Demo User'
+ : '❌ Logged out');
+ }
+
+ /**
+ * Toggle edit mode (only when authenticated)
+ */
+ toggleEditMode() {
+ if (!this.state.isAuthenticated) {
+ console.warn('❌ Cannot enable edit mode - not authenticated');
+ return;
+ }
+
+ this.state.editMode = !this.state.editMode;
+
+ // Cancel any active editing when turning off edit mode
+ if (!this.state.editMode && this.state.activeEditor) {
+ // This would be handled by the main editor
+ 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');
+ }
+
+ /**
+ * Update body CSS classes based on authentication state
+ */
+ updateBodyClasses() {
+ document.body.classList.toggle('insertr-authenticated', this.state.isAuthenticated);
+ document.body.classList.toggle('insertr-edit-mode', this.state.editMode);
+ }
+
+ /**
+ * Update button text and visibility
+ */
+ updateButtonStates() {
+ const authBtn = document.getElementById('insertr-auth-toggle');
+ const editBtn = document.getElementById('insertr-edit-toggle');
+
+ if (authBtn) {
+ authBtn.textContent = this.state.isAuthenticated ? 'Logout' : 'Login as Client';
+ authBtn.className = `insertr-auth-btn ${this.state.isAuthenticated ? 'insertr-authenticated' : ''}`;
+ }
+
+ if (editBtn) {
+ editBtn.style.display = this.state.isAuthenticated ? 'inline-block' : 'none';
+ editBtn.textContent = `Edit Mode: ${this.state.editMode ? 'On' : 'Off'}`;
+ editBtn.className = `insertr-auth-btn ${this.state.editMode ? 'insertr-edit-active' : ''}`;
+ }
+ }
+
+ /**
+ * Create status indicator
+ */
+ createStatusIndicator() {
+ // Check if already exists
+ if (document.getElementById('insertr-status')) {
+ return;
+ }
+
+ const statusHtml = `
+
+ `;
+
+ document.body.insertAdjacentHTML('beforeend', statusHtml);
+ this.statusIndicator = document.getElementById('insertr-status');
+ this.updateStatusIndicator();
+ }
+
+ /**
+ * Update status indicator text and style
+ */
+ updateStatusIndicator() {
+ const statusText = document.querySelector('.insertr-status-text');
+ const statusDot = document.querySelector('.insertr-status-dot');
+
+ if (!statusText || !statusDot) return;
+
+ if (!this.state.isAuthenticated) {
+ statusText.textContent = 'Visitor Mode';
+ statusDot.className = 'insertr-status-dot insertr-status-visitor';
+ } else if (this.state.editMode) {
+ statusText.textContent = 'Editing';
+ statusDot.className = 'insertr-status-dot insertr-status-editing';
+ } else {
+ statusText.textContent = 'Authenticated';
+ statusDot.className = 'insertr-status-dot insertr-status-authenticated';
+ }
+ }
+
+ /**
+ * Check if user is authenticated
+ */
+ isAuthenticated() {
+ return this.state.isAuthenticated;
+ }
+
+ /**
+ * Check if edit mode is enabled
+ */
+ isEditMode() {
+ return this.state.editMode;
+ }
+
+ /**
+ * Get current user info
+ */
+ getCurrentUser() {
+ return this.state.currentUser;
+ }
+
+ /**
+ * Add styles for authentication controls
+ */
+ addControlStyles() {
+ const styles = `
+ .insertr-auth-controls {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ z-index: 9999;
+ display: flex;
+ gap: 10px;
+ 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 {
+ position: fixed;
+ bottom: 20px;
+ left: 20px;
+ z-index: 9999;
+ background: white;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ padding: 8px 12px;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ }
+
+ .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) [data-insertr-enhanced]:hover::after {
+ display: none !important;
+ }
+
+ /* Only show editing features when in edit mode */
+ .insertr-authenticated.insertr-edit-mode [data-insertr-enhanced] {
+ cursor: pointer;
+ }
+
+ .insertr-authenticated.insertr-edit-mode [data-insertr-enhanced]: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
+ * In production, this would handle real OAuth flows
+ */
+ async authenticateWithOAuth(provider = 'google') {
+ // Mock OAuth flow for now
+ console.log(`🔐 Mock OAuth login with ${provider}`);
+
+ // Simulate OAuth callback
+ setTimeout(() => {
+ this.state.isAuthenticated = true;
+ this.state.currentUser = {
+ name: 'OAuth User',
+ email: 'user@example.com',
+ provider: provider,
+ role: 'editor'
+ };
+
+ this.updateBodyClasses();
+ this.updateButtonStates();
+ this.updateStatusIndicator();
+
+ console.log('✅ OAuth authentication successful');
+ }, 1000);
+ }
+ }
+
/**
* Insertr - The Tailwind of CMS
* Main library entry point
@@ -636,13 +1014,15 @@ var Insertr = (function () {
// Core functionality
core: null,
editor: null,
+ auth: null,
// Initialize the library
init(options = {}) {
console.log('🔧 Insertr v1.0.0 initializing... (Hot Reload Ready)');
this.core = new InsertrCore(options);
- this.editor = new InsertrEditor(this.core, options);
+ this.auth = new InsertrAuth(options);
+ this.editor = new InsertrEditor(this.core, this.auth, options);
// Auto-initialize if DOM is ready
if (document.readyState === 'loading') {
@@ -654,13 +1034,39 @@ var Insertr = (function () {
return this;
},
- // Start the editor
+ // Start the editor and authentication
start() {
+ if (this.auth) {
+ this.auth.init();
+ }
if (this.editor) {
this.editor.start();
}
},
+ // Public API methods
+ login() {
+ return this.auth ? this.auth.toggleAuthentication() : null;
+ },
+
+ logout() {
+ if (this.auth && this.auth.isAuthenticated()) {
+ this.auth.toggleAuthentication();
+ }
+ },
+
+ toggleEditMode() {
+ return this.auth ? this.auth.toggleEditMode() : null;
+ },
+
+ isAuthenticated() {
+ return this.auth ? this.auth.isAuthenticated() : false;
+ },
+
+ isEditMode() {
+ return this.auth ? this.auth.isEditMode() : false;
+ },
+
// Version info
version: '1.0.0'
};
diff --git a/insertr-cli/pkg/content/assets/insertr.min.js b/insertr-cli/pkg/content/assets/insertr.min.js
index ce174f6..2f98850 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 e{constructor(e={}){this.options={apiEndpoint:e.apiEndpoint||"/api/content",siteId:e.siteId||"default",...e}}findEnhancedElements(){return document.querySelectorAll('[data-insertr-enhanced="true"]')}getElementMetadata(e){return{contentId:e.getAttribute("data-content-id"),contentType:e.getAttribute("data-content-type"),element:e}}getAllElements(){const e=this.findEnhancedElements();return Array.from(e).map(e=>this.getElementMetadata(e))}}class t{constructor(){this.currentOverlay=null,this.setupStyles()}showEditForm(e,t,n,r){this.closeForm();const{element:o,contentId:a,contentType:i}=e,s=this.getFieldConfig(o,i),l=this.createEditForm(a,s,t),d=this.createOverlay(l);this.positionForm(o,d),this.setupFormHandlers(l,d,{onSave:n,onCancel:r}),document.body.appendChild(d),this.currentOverlay=d;const c=l.querySelector("input, textarea");return c&&setTimeout(()=>c.focus(),100),d}closeForm(){this.currentOverlay&&(this.currentOverlay.remove(),this.currentOverlay=null)}getFieldConfig(e,t){const n=e.tagName.toLowerCase(),r=Array.from(e.classList);let o={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")&&(o={...o,label:"Lead Paragraph",rows:4,placeholder:"Enter lead paragraph..."}),"markdown"===t&&(o={...o,type:"markdown",label:"Markdown Content",rows:8}),o}createEditForm(e,t,n){const r=document.createElement("div");r.className="insertr-edit-form";let o=``;return"markdown"===t.type?o+=this.createMarkdownField(t,n):"link"===t.type&&t.includeUrl?o+=this.createLinkField(t,n):"textarea"===t.type?o+=this.createTextareaField(t,n):o+=this.createTextField(t,n),o+='\n \n \n \n
\n ',r.innerHTML=o,r}createMarkdownField(e,t){return`\n \n `}createLinkField(e,t){const n="object"==typeof t?t.text||"":t,r="object"==typeof t&&t.url||"";return`\n \n \n \n
\n \n \n \n
\n `}createTextareaField(e,t){const n="object"==typeof t?t.text||"":t;return`\n \n \n
\n `}createTextField(e,t){const n="object"==typeof t?t.text||"":t;return`\n \n \n
\n `}createOverlay(e){const t=document.createElement("div");return t.className="insertr-form-overlay",t.appendChild(e),t}positionForm(e,t){const n=e.getBoundingClientRect(),r=t.querySelector(".insertr-edit-form"),o=window.innerWidth;let a;a=o<768?Math.min(o-40,350):Math.min(Math.max(n.width,300),500),r.style.width=`${a}px`;const i=n.bottom+window.scrollY+10,s=Math.max(20,n.left+window.scrollX);t.style.position="absolute",t.style.top=`${i}px`,t.style.left=`${s}px`,t.style.zIndex="10000"}setupFormHandlers(e,t,{onSave:n,onCancel:r}){const o=e.querySelector(".insertr-btn-save"),a=e.querySelector(".insertr-btn-cancel");o&&o.addEventListener("click",()=>{const t=this.extractFormData(e);n(t)}),a&&a.addEventListener("click",()=>{r(),this.closeForm()});const i=e=>{"Escape"===e.key&&(r(),this.closeForm(),document.removeEventListener("keydown",i))};document.addEventListener("keydown",i),t.addEventListener("click",e=>{e.target===t&&(r(),this.closeForm())})}extractFormData(e){const t={},n=e.querySelector('input[name="text"]'),r=e.querySelector('input[name="url"]'),o=e.querySelector('input[name="content"], textarea[name="content"]');return n&&r?(t.text=n.value,t.url=r.value):o&&(t.text=o.value),t}escapeHtml(e){if("string"!=typeof e)return"";const t=document.createElement("div");return t.textContent=e,t.innerHTML}setupStyles(){const e=document.createElement("style");e.type="text/css",e.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(e)}}class n{constructor(e,n={}){this.core=e,this.options=n,this.isActive=!1,this.formRenderer=new t}start(){if(this.isActive)return;console.log("🚀 Starting Insertr Editor"),this.isActive=!0,this.addEditorStyles();const e=this.core.getAllElements();console.log(`📝 Found ${e.length} editable elements`),e.forEach(e=>this.initializeElement(e))}initializeElement(e){const{element:t,contentId:n,contentType:r}=e;t.style.cursor="pointer",t.style.position="relative",this.addHoverEffects(t),this.addClickHandler(t,e)}addHoverEffects(e){e.addEventListener("mouseenter",()=>{e.classList.add("insertr-editing-hover")}),e.addEventListener("mouseleave",()=>{e.classList.remove("insertr-editing-hover")})}addClickHandler(e,t){e.addEventListener("click",e=>{e.preventDefault(),this.openEditor(t)})}openEditor(e){const{element:t}=e,n=this.extractCurrentContent(t);this.formRenderer.showEditForm(e,n,t=>this.handleSave(e,t),()=>this.handleCancel(e))}extractCurrentContent(e){return"a"===e.tagName.toLowerCase()?{text:e.textContent.trim(),url:e.getAttribute("href")||""}:e.textContent.trim()}handleSave(e,t){console.log("💾 Saving content:",e.contentId,t),this.updateElementContent(e.element,t),this.formRenderer.closeForm(),console.log("✅ Content saved:",e.contentId,t)}handleCancel(e){console.log("❌ Edit cancelled:",e.contentId)}updateElementContent(e,t){"a"===e.tagName.toLowerCase()?(void 0!==t.text&&(e.textContent=t.text),void 0!==t.url&&e.setAttribute("href",t.url)):e.textContent=t.text||""}addEditorStyles(){const e=document.createElement("style");e.type="text/css",e.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(e)}}return window.Insertr={core:null,editor:null,init(t={}){return console.log("🔧 Insertr v1.0.0 initializing... (Hot Reload Ready)"),this.core=new e(t),this.editor=new n(this.core,t),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>this.start()):this.start(),this},start(){this.editor&&this.editor.start()},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,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=``;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 `}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}();
diff --git a/lib/src/core/auth.js b/lib/src/core/auth.js
new file mode 100644
index 0000000..b2c26e9
--- /dev/null
+++ b/lib/src/core/auth.js
@@ -0,0 +1,371 @@
+/**
+ * InsertrAuth - Authentication and state management
+ * Handles user authentication, edit mode, and visual state changes
+ */
+export class InsertrAuth {
+ constructor(options = {}) {
+ this.options = {
+ mockAuth: options.mockAuth !== false, // Enable mock auth by default
+ autoCreateControls: options.autoCreateControls !== false,
+ ...options
+ };
+
+ // Authentication state
+ this.state = {
+ isAuthenticated: false,
+ editMode: false,
+ currentUser: null,
+ activeEditor: null
+ };
+
+ this.statusIndicator = null;
+ }
+
+ /**
+ * Initialize authentication system
+ */
+ init() {
+ console.log('🔐 Initializing Insertr Authentication');
+
+ if (this.options.autoCreateControls) {
+ this.createAuthControls();
+ }
+
+ this.setupAuthenticationControls();
+ this.createStatusIndicator();
+ this.updateBodyClasses();
+
+ console.log('📱 Auth controls ready - Look for buttons in top-right corner');
+ }
+
+ /**
+ * Create authentication control buttons if they don't exist
+ */
+ createAuthControls() {
+ // Check if controls already exist
+ if (document.getElementById('insertr-auth-controls')) {
+ return;
+ }
+
+ const controlsHtml = `
+
+
+
+
+ `;
+
+ // Add controls to page
+ document.body.insertAdjacentHTML('beforeend', controlsHtml);
+
+ // Add styles for controls
+ this.addControlStyles();
+ }
+
+ /**
+ * Setup event listeners for authentication controls
+ */
+ setupAuthenticationControls() {
+ const authToggle = document.getElementById('insertr-auth-toggle');
+ const editToggle = document.getElementById('insertr-edit-toggle');
+
+ if (authToggle) {
+ authToggle.addEventListener('click', () => this.toggleAuthentication());
+ }
+
+ if (editToggle) {
+ editToggle.addEventListener('click', () => this.toggleEditMode());
+ }
+ }
+
+ /**
+ * Toggle authentication state
+ */
+ toggleAuthentication() {
+ this.state.isAuthenticated = !this.state.isAuthenticated;
+ this.state.currentUser = this.state.isAuthenticated ? {
+ name: 'Demo User',
+ email: 'demo@example.com',
+ role: 'editor'
+ } : null;
+
+ // Reset edit mode when logging out
+ if (!this.state.isAuthenticated) {
+ this.state.editMode = false;
+ }
+
+ this.updateBodyClasses();
+ this.updateButtonStates();
+ this.updateStatusIndicator();
+
+ console.log(this.state.isAuthenticated
+ ? '✅ Authenticated as Demo User'
+ : '❌ Logged out');
+ }
+
+ /**
+ * Toggle edit mode (only when authenticated)
+ */
+ toggleEditMode() {
+ if (!this.state.isAuthenticated) {
+ console.warn('❌ Cannot enable edit mode - not authenticated');
+ return;
+ }
+
+ this.state.editMode = !this.state.editMode;
+
+ // Cancel any active editing when turning off edit mode
+ if (!this.state.editMode && this.state.activeEditor) {
+ // This would be handled by the main editor
+ 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');
+ }
+
+ /**
+ * Update body CSS classes based on authentication state
+ */
+ updateBodyClasses() {
+ document.body.classList.toggle('insertr-authenticated', this.state.isAuthenticated);
+ document.body.classList.toggle('insertr-edit-mode', this.state.editMode);
+ }
+
+ /**
+ * Update button text and visibility
+ */
+ updateButtonStates() {
+ const authBtn = document.getElementById('insertr-auth-toggle');
+ const editBtn = document.getElementById('insertr-edit-toggle');
+
+ if (authBtn) {
+ authBtn.textContent = this.state.isAuthenticated ? 'Logout' : 'Login as Client';
+ authBtn.className = `insertr-auth-btn ${this.state.isAuthenticated ? 'insertr-authenticated' : ''}`;
+ }
+
+ if (editBtn) {
+ editBtn.style.display = this.state.isAuthenticated ? 'inline-block' : 'none';
+ editBtn.textContent = `Edit Mode: ${this.state.editMode ? 'On' : 'Off'}`;
+ editBtn.className = `insertr-auth-btn ${this.state.editMode ? 'insertr-edit-active' : ''}`;
+ }
+ }
+
+ /**
+ * Create status indicator
+ */
+ createStatusIndicator() {
+ // Check if already exists
+ if (document.getElementById('insertr-status')) {
+ return;
+ }
+
+ const statusHtml = `
+
+ `;
+
+ document.body.insertAdjacentHTML('beforeend', statusHtml);
+ this.statusIndicator = document.getElementById('insertr-status');
+ this.updateStatusIndicator();
+ }
+
+ /**
+ * Update status indicator text and style
+ */
+ updateStatusIndicator() {
+ const statusText = document.querySelector('.insertr-status-text');
+ const statusDot = document.querySelector('.insertr-status-dot');
+
+ if (!statusText || !statusDot) return;
+
+ if (!this.state.isAuthenticated) {
+ statusText.textContent = 'Visitor Mode';
+ statusDot.className = 'insertr-status-dot insertr-status-visitor';
+ } else if (this.state.editMode) {
+ statusText.textContent = 'Editing';
+ statusDot.className = 'insertr-status-dot insertr-status-editing';
+ } else {
+ statusText.textContent = 'Authenticated';
+ statusDot.className = 'insertr-status-dot insertr-status-authenticated';
+ }
+ }
+
+ /**
+ * Check if user is authenticated
+ */
+ isAuthenticated() {
+ return this.state.isAuthenticated;
+ }
+
+ /**
+ * Check if edit mode is enabled
+ */
+ isEditMode() {
+ return this.state.editMode;
+ }
+
+ /**
+ * Get current user info
+ */
+ getCurrentUser() {
+ return this.state.currentUser;
+ }
+
+ /**
+ * Add styles for authentication controls
+ */
+ addControlStyles() {
+ const styles = `
+ .insertr-auth-controls {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ z-index: 9999;
+ display: flex;
+ gap: 10px;
+ 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 {
+ position: fixed;
+ bottom: 20px;
+ left: 20px;
+ z-index: 9999;
+ background: white;
+ border: 1px solid #e5e7eb;
+ border-radius: 8px;
+ padding: 8px 12px;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ }
+
+ .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) [data-insertr-enhanced]:hover::after {
+ display: none !important;
+ }
+
+ /* Only show editing features when in edit mode */
+ .insertr-authenticated.insertr-edit-mode [data-insertr-enhanced] {
+ cursor: pointer;
+ }
+
+ .insertr-authenticated.insertr-edit-mode [data-insertr-enhanced]: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
+ * In production, this would handle real OAuth flows
+ */
+ async authenticateWithOAuth(provider = 'google') {
+ // Mock OAuth flow for now
+ console.log(`🔐 Mock OAuth login with ${provider}`);
+
+ // Simulate OAuth callback
+ setTimeout(() => {
+ this.state.isAuthenticated = true;
+ this.state.currentUser = {
+ name: 'OAuth User',
+ email: 'user@example.com',
+ provider: provider,
+ role: 'editor'
+ };
+
+ this.updateBodyClasses();
+ this.updateButtonStates();
+ this.updateStatusIndicator();
+
+ console.log('✅ OAuth authentication successful');
+ }, 1000);
+ }
+}
\ No newline at end of file
diff --git a/lib/src/core/editor.js b/lib/src/core/editor.js
index cced9da..5d945d2 100644
--- a/lib/src/core/editor.js
+++ b/lib/src/core/editor.js
@@ -4,8 +4,9 @@ import { InsertrFormRenderer } from '../ui/form-renderer.js';
* InsertrEditor - Visual editing functionality
*/
export class InsertrEditor {
- constructor(core, options = {}) {
+ constructor(core, auth, options = {}) {
this.core = core;
+ this.auth = auth;
this.options = options;
this.isActive = false;
this.formRenderer = new InsertrFormRenderer();
@@ -51,6 +52,11 @@ export class InsertrEditor {
addClickHandler(element, meta) {
element.addEventListener('click', (e) => {
+ // Only allow editing if authenticated and in edit mode
+ if (!this.auth.isAuthenticated() || !this.auth.isEditMode()) {
+ return; // Let normal click behavior happen
+ }
+
e.preventDefault();
this.openEditor(meta);
});
diff --git a/lib/src/index.js b/lib/src/index.js
index a971cd1..d66a5be 100644
--- a/lib/src/index.js
+++ b/lib/src/index.js
@@ -5,6 +5,7 @@
import { InsertrCore } from './core/insertr.js';
import { InsertrEditor } from './core/editor.js';
+import { InsertrAuth } from './core/auth.js';
import { ApiClient } from './core/api-client.js';
// Create global Insertr instance
@@ -12,13 +13,15 @@ window.Insertr = {
// Core functionality
core: null,
editor: null,
+ auth: null,
// Initialize the library
init(options = {}) {
console.log('🔧 Insertr v1.0.0 initializing... (Hot Reload Ready)');
this.core = new InsertrCore(options);
- this.editor = new InsertrEditor(this.core, options);
+ this.auth = new InsertrAuth(options);
+ this.editor = new InsertrEditor(this.core, this.auth, options);
// Auto-initialize if DOM is ready
if (document.readyState === 'loading') {
@@ -30,13 +33,39 @@ window.Insertr = {
return this;
},
- // Start the editor
+ // Start the editor and authentication
start() {
+ if (this.auth) {
+ this.auth.init();
+ }
if (this.editor) {
this.editor.start();
}
},
+ // Public API methods
+ login() {
+ return this.auth ? this.auth.toggleAuthentication() : null;
+ },
+
+ logout() {
+ if (this.auth && this.auth.isAuthenticated()) {
+ this.auth.toggleAuthentication();
+ }
+ },
+
+ toggleEditMode() {
+ return this.auth ? this.auth.toggleEditMode() : null;
+ },
+
+ isAuthenticated() {
+ return this.auth ? this.auth.isAuthenticated() : false;
+ },
+
+ isEditMode() {
+ return this.auth ? this.auth.isEditMode() : false;
+ },
+
// Version info
version: '1.0.0'
};