refactor: Extract UI into dedicated control panel module
- Create InsertrControlPanel class for unified UI management - Separate business logic from presentation layer - Remove DOM manipulation from auth.js and editor.js - Add comprehensive CSS for status indicators and editing effects - Implement consistent kebab-case file naming - Add event-driven communication between core and UI layers UI Architecture: - Unified control panel with status indicator and action buttons - Color-coded status dots (gray/blue/green for visitor/auth/editing) - Professional editing hover effects with tooltips - Responsive design for mobile devices - Proper z-index and accessibility management Business Logic: - Pure auth.js focused on authentication state and OAuth flows - Pure editor.js focused on content editing workflow - Event emitters for state changes between modules - Clean separation of concerns and testable architecture
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
/**
|
||||
* InsertrAuth - Authentication and state management
|
||||
* Handles user authentication, edit mode, and visual state changes
|
||||
* Pure business logic - no DOM manipulation or UI concerns
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Manage authentication state
|
||||
* - Handle OAuth flows
|
||||
* - Validate permissions
|
||||
* - Emit state change events
|
||||
*/
|
||||
export class InsertrAuth {
|
||||
constructor(options = {}) {
|
||||
@@ -20,7 +26,33 @@ export class InsertrAuth {
|
||||
isAuthenticating: false
|
||||
};
|
||||
|
||||
this.statusIndicator = null;
|
||||
// Event listeners for state changes
|
||||
this.listeners = {
|
||||
stateChange: [],
|
||||
authChange: [],
|
||||
editModeChange: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Event emitter methods
|
||||
*/
|
||||
on(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
off(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
|
||||
}
|
||||
}
|
||||
|
||||
emit(event, data) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach(callback => callback(data));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,7 +60,6 @@ export class InsertrAuth {
|
||||
*/
|
||||
init() {
|
||||
console.log('🔧 Insertr: Scanning for editor gates');
|
||||
|
||||
this.setupEditorGates();
|
||||
}
|
||||
|
||||
@@ -42,11 +73,6 @@ export class InsertrAuth {
|
||||
|
||||
console.log('🔐 Initializing Insertr Editing System');
|
||||
|
||||
this.createAuthControls();
|
||||
this.setupAuthenticationControls();
|
||||
this.createStatusIndicator();
|
||||
this.updateBodyClasses();
|
||||
|
||||
// Auto-enable edit mode after OAuth
|
||||
this.state.editMode = true;
|
||||
this.state.isInitialized = true;
|
||||
@@ -56,10 +82,10 @@ export class InsertrAuth {
|
||||
window.Insertr.startEditor();
|
||||
}
|
||||
|
||||
this.updateButtonStates();
|
||||
this.updateStatusIndicator();
|
||||
// Emit state change for UI to update
|
||||
this.emitStateChange();
|
||||
|
||||
console.log('📱 Editing system active - Controls in bottom-right corner');
|
||||
console.log('📱 Editing system active');
|
||||
console.log('✏️ Edit mode enabled - Click elements to edit');
|
||||
}
|
||||
|
||||
@@ -76,9 +102,6 @@ export class InsertrAuth {
|
||||
|
||||
console.log(`🚪 Found ${gates.length} editor gate(s)`);
|
||||
|
||||
// Add gate styles
|
||||
this.addGateStyles();
|
||||
|
||||
gates.forEach((gate, index) => {
|
||||
// Store original text for later restoration
|
||||
if (!gate.hasAttribute('data-original-text')) {
|
||||
@@ -90,7 +113,7 @@ export class InsertrAuth {
|
||||
this.handleGateClick(gate, index);
|
||||
});
|
||||
|
||||
// Add subtle styling to indicate it's clickable
|
||||
// Add minimal styling to indicate it's clickable
|
||||
gate.style.cursor = 'pointer';
|
||||
});
|
||||
}
|
||||
@@ -121,7 +144,7 @@ export class InsertrAuth {
|
||||
// Initialize full editing system
|
||||
this.initializeFullSystem();
|
||||
|
||||
// Conditionally hide gates based on options
|
||||
// Handle gate visibility based on options
|
||||
if (this.options.hideGatesAfterAuth) {
|
||||
this.hideAllGates();
|
||||
} else {
|
||||
@@ -198,47 +221,6 @@ export class InsertrAuth {
|
||||
console.log('🚪 Editor gates restored to original state');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create authentication control buttons (bottom-right positioned)
|
||||
*/
|
||||
createAuthControls() {
|
||||
// Check if controls already exist
|
||||
if (document.getElementById('insertr-auth-controls')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controlsHtml = `
|
||||
<div id="insertr-auth-controls" class="insertr-auth-controls">
|
||||
<button id="insertr-auth-toggle" class="insertr-auth-btn">Login as Client</button>
|
||||
<button id="insertr-edit-toggle" class="insertr-auth-btn" style="display: none;">Edit Mode: Off</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add controls to page
|
||||
document.body.insertAdjacentHTML('beforeend', controlsHtml);
|
||||
|
||||
// Add styles for controls
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -255,9 +237,7 @@ export class InsertrAuth {
|
||||
this.state.editMode = false;
|
||||
}
|
||||
|
||||
this.updateBodyClasses();
|
||||
this.updateButtonStates();
|
||||
this.updateStatusIndicator();
|
||||
this.emitStateChange();
|
||||
|
||||
console.log(this.state.isAuthenticated
|
||||
? '✅ Authenticated as Demo User'
|
||||
@@ -281,9 +261,7 @@ export class InsertrAuth {
|
||||
this.state.activeEditor = null;
|
||||
}
|
||||
|
||||
this.updateBodyClasses();
|
||||
this.updateButtonStates();
|
||||
this.updateStatusIndicator();
|
||||
this.emitStateChange();
|
||||
|
||||
console.log(this.state.editMode
|
||||
? '✏️ Edit mode ON - Click elements to edit'
|
||||
@@ -291,83 +269,18 @@ export class InsertrAuth {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update body CSS classes based on authentication state
|
||||
* Emit state change events for UI updates
|
||||
*/
|
||||
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' : ''}`;
|
||||
}
|
||||
|
||||
// Update enhance button visibility
|
||||
this.updateEnhanceButtonVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create status indicator
|
||||
*/
|
||||
createStatusIndicator() {
|
||||
// Check if already exists
|
||||
if (document.getElementById('insertr-status')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const statusHtml = `
|
||||
<div id="insertr-status-controls" class="insertr-status-controls">
|
||||
<div id="insertr-status" class="insertr-status">
|
||||
<div class="insertr-status-content">
|
||||
<span class="insertr-status-text">Visitor Mode</span>
|
||||
<span class="insertr-status-dot"></span>
|
||||
</div>
|
||||
</div>
|
||||
<button id="insertr-enhance-btn" class="insertr-auth-btn" style="display: none;" title="Enhance files with latest content">🔄 Enhance</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', statusHtml);
|
||||
this.statusIndicator = document.getElementById('insertr-status');
|
||||
this.setupEnhanceButton();
|
||||
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';
|
||||
}
|
||||
|
||||
emitStateChange() {
|
||||
const stateData = {
|
||||
isAuthenticated: this.state.isAuthenticated,
|
||||
editMode: this.state.editMode,
|
||||
currentUser: this.state.currentUser
|
||||
};
|
||||
|
||||
this.emit('stateChange', stateData);
|
||||
this.emit('authChange', { isAuthenticated: this.state.isAuthenticated, user: this.state.currentUser });
|
||||
this.emit('editModeChange', { editMode: this.state.editMode });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,34 +304,6 @@ export class InsertrAuth {
|
||||
return this.state.currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add minimal styles for editor gates
|
||||
*/
|
||||
addGateStyles() {
|
||||
const styles = `
|
||||
.insertr-gate {
|
||||
transition: opacity 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.insertr-gate:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Optional: Hide gates when authenticated (only if hideGatesAfterAuth option is true) */
|
||||
body.insertr-hide-gates .insertr-gate {
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const styleSheet = document.createElement('style');
|
||||
styleSheet.type = 'text/css';
|
||||
styleSheet.innerHTML = styles;
|
||||
document.head.appendChild(styleSheet);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* OAuth integration placeholder
|
||||
* In production, this would handle real OAuth flows
|
||||
@@ -437,99 +322,44 @@ export class InsertrAuth {
|
||||
role: 'editor'
|
||||
};
|
||||
|
||||
this.updateBodyClasses();
|
||||
this.updateButtonStates();
|
||||
this.updateStatusIndicator();
|
||||
this.emitStateChange();
|
||||
|
||||
console.log('✅ OAuth authentication successful');
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup enhance button functionality
|
||||
* Validate if user has permission for specific action
|
||||
*/
|
||||
setupEnhanceButton() {
|
||||
const enhanceBtn = document.getElementById('insertr-enhance-btn');
|
||||
if (!enhanceBtn) return;
|
||||
hasPermission(action) {
|
||||
if (!this.isAuthenticated()) return false;
|
||||
|
||||
const user = this.getCurrentUser();
|
||||
if (!user) return false;
|
||||
|
||||
enhanceBtn.addEventListener('click', async () => {
|
||||
await this.enhanceFiles();
|
||||
});
|
||||
|
||||
// Show enhance button only in development/authenticated mode
|
||||
this.updateEnhanceButtonVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update enhance button visibility based on authentication state
|
||||
*/
|
||||
updateEnhanceButtonVisibility() {
|
||||
const enhanceBtn = document.getElementById('insertr-enhance-btn');
|
||||
if (!enhanceBtn) return;
|
||||
|
||||
// Show enhance button when authenticated (indicates dev mode)
|
||||
if (this.state.isAuthenticated) {
|
||||
enhanceBtn.style.display = 'inline-block';
|
||||
} else {
|
||||
enhanceBtn.style.display = 'none';
|
||||
// Simple role-based permissions
|
||||
switch (action) {
|
||||
case 'edit':
|
||||
return ['admin', 'editor'].includes(user.role);
|
||||
case 'enhance':
|
||||
return ['admin'].includes(user.role);
|
||||
case 'manage':
|
||||
return user.role === 'admin';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger manual file enhancement
|
||||
* Get authentication state snapshot
|
||||
*/
|
||||
async enhanceFiles() {
|
||||
const enhanceBtn = document.getElementById('insertr-enhance-btn');
|
||||
if (!enhanceBtn) return;
|
||||
|
||||
// Get site ID from window context or configuration
|
||||
const siteId = window.insertrConfig?.siteId || this.options.siteId || 'demo';
|
||||
|
||||
try {
|
||||
// Show loading state
|
||||
enhanceBtn.textContent = '⏳ Enhancing...';
|
||||
enhanceBtn.disabled = true;
|
||||
|
||||
// Smart server detection for enhance API (same logic as ApiClient)
|
||||
const isDevelopment = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
|
||||
const enhanceUrl = isDevelopment
|
||||
? `http://localhost:8080/api/enhance?site_id=${siteId}` // Development: separate API server
|
||||
: `/api/enhance?site_id=${siteId}`; // Production: same-origin API
|
||||
|
||||
// Call enhance API
|
||||
const response = await fetch(enhanceUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.state.currentUser?.token || 'mock-token'}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Enhancement failed: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('✅ Files enhanced successfully:', result);
|
||||
|
||||
// Show success state briefly
|
||||
enhanceBtn.textContent = '✅ Enhanced!';
|
||||
|
||||
// Reset button after success (no page reload needed)
|
||||
setTimeout(() => {
|
||||
enhanceBtn.textContent = '🔄 Enhance';
|
||||
enhanceBtn.disabled = false;
|
||||
}, 2000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Enhancement failed:', error);
|
||||
enhanceBtn.textContent = '❌ Failed';
|
||||
|
||||
// Reset button after error
|
||||
setTimeout(() => {
|
||||
enhanceBtn.textContent = '🔄 Enhance';
|
||||
enhanceBtn.disabled = false;
|
||||
}, 2000);
|
||||
}
|
||||
getState() {
|
||||
return {
|
||||
isAuthenticated: this.state.isAuthenticated,
|
||||
editMode: this.state.editMode,
|
||||
currentUser: this.state.currentUser,
|
||||
isInitialized: this.state.isInitialized,
|
||||
isAuthenticating: this.state.isAuthenticating
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
import { InsertrFormRenderer } from '../ui/form-renderer.js';
|
||||
|
||||
/**
|
||||
* InsertrEditor - Visual editing functionality
|
||||
* InsertrEditor - Content editing workflow and business logic
|
||||
* Pure business logic - no visual styling or DOM manipulation
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Manage editing workflow
|
||||
* - Handle content persistence
|
||||
* - Coordinate with form renderer
|
||||
* - Emit editing events
|
||||
*/
|
||||
export class InsertrEditor {
|
||||
constructor(core, auth, apiClient, options = {}) {
|
||||
@@ -11,6 +18,34 @@ export class InsertrEditor {
|
||||
this.options = options;
|
||||
this.isActive = false;
|
||||
this.formRenderer = new InsertrFormRenderer(apiClient);
|
||||
|
||||
// Event listeners for mode changes
|
||||
this.listeners = {
|
||||
modeChange: [],
|
||||
editStart: [],
|
||||
editEnd: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Event emitter methods
|
||||
*/
|
||||
on(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
off(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
|
||||
}
|
||||
}
|
||||
|
||||
emit(event, data) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach(callback => callback(data));
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
@@ -19,37 +54,23 @@ export class InsertrEditor {
|
||||
console.log('🚀 Starting Insertr Editor');
|
||||
this.isActive = true;
|
||||
|
||||
|
||||
|
||||
// Initialize all enhanced elements
|
||||
// Initialize all enhanced elements for editing
|
||||
const elements = this.core.getAllElements();
|
||||
console.log(`📝 Found ${elements.length} editable elements`);
|
||||
|
||||
elements.forEach(meta => this.initializeElement(meta));
|
||||
|
||||
// Emit mode change event for UI updates
|
||||
this.emit('modeChange', { isActive: this.isActive });
|
||||
}
|
||||
|
||||
initializeElement(meta) {
|
||||
const { element, contentId, contentType } = meta;
|
||||
const { element } = meta;
|
||||
|
||||
// Add visual indicators
|
||||
element.style.cursor = 'pointer';
|
||||
element.style.position = 'relative';
|
||||
|
||||
// Add interaction handlers
|
||||
this.addHoverEffects(element);
|
||||
// Add click handler for editing
|
||||
this.addClickHandler(element, meta);
|
||||
}
|
||||
|
||||
addHoverEffects(element) {
|
||||
element.addEventListener('mouseenter', () => {
|
||||
element.classList.add('insertr-editing-hover');
|
||||
});
|
||||
|
||||
element.addEventListener('mouseleave', () => {
|
||||
element.classList.remove('insertr-editing-hover');
|
||||
});
|
||||
}
|
||||
|
||||
addClickHandler(element, meta) {
|
||||
element.addEventListener('click', (e) => {
|
||||
// Only allow editing if authenticated and in edit mode
|
||||
|
||||
Reference in New Issue
Block a user