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 = ` +
+
+ Visitor Mode + +
+
+ `; + + 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=`
${t.label}
`;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 \n
\n Supports Markdown formatting (bold, italic, links, etc.)\n
\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=`
${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}(); 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 = ` +
+
+ Visitor Mode + +
+
+ `; + + 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' };