consolidation
This commit is contained in:
		
							parent
							
								
									bd7f93167c
								
							
						
					
					
						commit
						bb26d9a80d
					
				@ -145,14 +145,6 @@ const sortedTags = Object.entries(tagFrequency)
 | 
			
		||||
    let selectedPhase = '';
 | 
			
		||||
    let isTagCloudExpanded = false;
 | 
			
		||||
    
 | 
			
		||||
    // Check authentication status and show/hide AI button
 | 
			
		||||
    async function initAIButton() {
 | 
			
		||||
      await showIfAuthenticated('#ai-view-toggle');
 | 
			
		||||
    }
 | 
			
		||||
        
 | 
			
		||||
    // Call auth check on page load
 | 
			
		||||
    initAIButton();
 | 
			
		||||
    
 | 
			
		||||
    // Initialize tag cloud state
 | 
			
		||||
    function initTagCloud() {
 | 
			
		||||
      const visibleCount = 22;
 | 
			
		||||
 | 
			
		||||
@ -832,5 +832,4 @@ domains.forEach((domain: any) => {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  setupContributionButtonAuth();
 | 
			
		||||
</script>
 | 
			
		||||
							
								
								
									
										6
									
								
								src/env.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								src/env.d.ts
									
									
									
									
										vendored
									
									
								
							@ -23,6 +23,12 @@ declare global {
 | 
			
		||||
    createToolSlug: (toolName: string) => string;
 | 
			
		||||
    findToolByIdentifier: (tools: any[], identifier: string) => any | undefined;
 | 
			
		||||
    isToolHosted: (tool: any) => boolean;
 | 
			
		||||
 | 
			
		||||
    // CONSOLIDATED: Auth utility functions (now in BaseLayout)
 | 
			
		||||
    checkClientAuth: () => Promise<{authenticated: boolean; authRequired: boolean; expires?: string}>;
 | 
			
		||||
    requireClientAuth: (callback?: () => void, returnUrl?: string) => Promise<boolean>;
 | 
			
		||||
    showIfAuthenticated: (selector: string) => Promise<void>;
 | 
			
		||||
    setupAuthButtons: (selector?: string) => void;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -20,18 +20,178 @@ const { title, description = 'CC24-Guide - A comprehensive directory of digital
 | 
			
		||||
  <title>{title} - CC24-Guide</title>
 | 
			
		||||
  <link rel="icon" type="image/x-icon" href="/favicon.ico">
 | 
			
		||||
  
 | 
			
		||||
  <!-- CONSOLIDATED: Load theme script -->
 | 
			
		||||
  <script src="/src/scripts/theme.js"></script>
 | 
			
		||||
  
 | 
			
		||||
  <!-- CONSOLIDATED: Load tool utilities (now from enhanced TypeScript file) -->
 | 
			
		||||
  <script src="/src/utils/toolHelpers.ts"></script>
 | 
			
		||||
  
 | 
			
		||||
  <!-- CONSOLIDATED: Load client-side auth utilities -->
 | 
			
		||||
  <script src="/src/scripts/client-auth.js"></script>
 | 
			
		||||
  
 | 
			
		||||
  <script>
 | 
			
		||||
    // Initialize theme immediately to prevent flash
 | 
			
		||||
    (window as any).themeUtils?.initTheme();
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
      // Theme management (consolidated from theme.js)
 | 
			
		||||
      const THEME_KEY = 'dfir-theme';
 | 
			
		||||
 | 
			
		||||
      function getSystemTheme() {
 | 
			
		||||
        return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function getStoredTheme() {
 | 
			
		||||
        return localStorage.getItem(THEME_KEY) || 'auto';
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function applyTheme(theme) {
 | 
			
		||||
        const effectiveTheme = theme === 'auto' ? getSystemTheme() : theme;
 | 
			
		||||
        document.documentElement.setAttribute('data-theme', effectiveTheme);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function updateThemeToggle(theme) {
 | 
			
		||||
        document.querySelectorAll('[data-theme-toggle]').forEach(button => {
 | 
			
		||||
          button.setAttribute('data-current-theme', theme);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function initTheme() {
 | 
			
		||||
        const storedTheme = getStoredTheme();
 | 
			
		||||
        applyTheme(storedTheme);
 | 
			
		||||
        updateThemeToggle(storedTheme);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function toggleTheme() {
 | 
			
		||||
        const current = getStoredTheme();
 | 
			
		||||
        const themes = ['light', 'dark', 'auto'];
 | 
			
		||||
        const currentIndex = themes.indexOf(current);
 | 
			
		||||
        const nextIndex = (currentIndex + 1) % themes.length;
 | 
			
		||||
        const nextTheme = themes[nextIndex];
 | 
			
		||||
        
 | 
			
		||||
        localStorage.setItem(THEME_KEY, nextTheme);
 | 
			
		||||
        applyTheme(nextTheme);
 | 
			
		||||
        updateThemeToggle(nextTheme);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Listen for system theme changes
 | 
			
		||||
      window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
 | 
			
		||||
        if (getStoredTheme() === 'auto') {
 | 
			
		||||
          applyTheme('auto');
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // Make theme utils available globally
 | 
			
		||||
      (window as any).themeUtils = {
 | 
			
		||||
        initTheme,
 | 
			
		||||
        toggleTheme,
 | 
			
		||||
        getStoredTheme
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      // Tool helper functions (consolidated from toolHelpers.ts)
 | 
			
		||||
      function createToolSlug(toolName) {
 | 
			
		||||
        if (!toolName || typeof toolName !== 'string') {
 | 
			
		||||
          console.warn('[toolHelpers] Invalid toolName provided to createToolSlug:', toolName);
 | 
			
		||||
          return '';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return toolName.toLowerCase()
 | 
			
		||||
          .replace(/[^a-z0-9\s-]/g, '')     // Remove special characters
 | 
			
		||||
          .replace(/\s+/g, '-')             // Replace spaces with hyphens
 | 
			
		||||
          .replace(/-+/g, '-')              // Remove duplicate hyphens
 | 
			
		||||
          .replace(/^-|-$/g, '');           // Remove leading/trailing hyphens
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function findToolByIdentifier(tools, identifier) {
 | 
			
		||||
        if (!identifier || !Array.isArray(tools)) return undefined;
 | 
			
		||||
        
 | 
			
		||||
        return tools.find(tool => 
 | 
			
		||||
          tool.name === identifier || 
 | 
			
		||||
          createToolSlug(tool.name) === identifier.toLowerCase()
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function isToolHosted(tool) {
 | 
			
		||||
        return tool.projectUrl !== undefined && 
 | 
			
		||||
               tool.projectUrl !== null && 
 | 
			
		||||
               tool.projectUrl !== "" && 
 | 
			
		||||
               tool.projectUrl.trim() !== "";
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Client-side auth functions (consolidated from client-auth.js)
 | 
			
		||||
      async function checkClientAuth() {
 | 
			
		||||
        try {
 | 
			
		||||
          const response = await fetch('/api/auth/status');
 | 
			
		||||
          const data = await response.json();
 | 
			
		||||
          return {
 | 
			
		||||
            authenticated: data.authenticated,
 | 
			
		||||
            authRequired: data.authRequired,
 | 
			
		||||
            expires: data.expires
 | 
			
		||||
          };
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
          console.error('Auth check failed:', error);
 | 
			
		||||
          return {
 | 
			
		||||
            authenticated: false,
 | 
			
		||||
            authRequired: true
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      async function requireClientAuth(callback, returnUrl) {
 | 
			
		||||
        const authStatus = await checkClientAuth();
 | 
			
		||||
        
 | 
			
		||||
        if (authStatus.authRequired && !authStatus.authenticated) {
 | 
			
		||||
          const targetUrl = returnUrl || window.location.href;
 | 
			
		||||
          window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`;
 | 
			
		||||
          return false;
 | 
			
		||||
        } else {
 | 
			
		||||
          if (typeof callback === 'function') {
 | 
			
		||||
            callback();
 | 
			
		||||
          }
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      async function showIfAuthenticated(selector) {
 | 
			
		||||
        const authStatus = await checkClientAuth();
 | 
			
		||||
        const element = document.querySelector(selector);
 | 
			
		||||
        
 | 
			
		||||
        if (element) {
 | 
			
		||||
          element.style.display = (!authStatus.authRequired || authStatus.authenticated) 
 | 
			
		||||
            ? 'inline-flex' 
 | 
			
		||||
            : 'none';
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function setupAuthButtons(selector = '[data-contribute-button]') {
 | 
			
		||||
        // Use event delegation on document for dynamic content support
 | 
			
		||||
        document.addEventListener('click', async (e) => {
 | 
			
		||||
          if (!e.target) return;
 | 
			
		||||
          
 | 
			
		||||
          const button = (e.target as Element).closest(selector);
 | 
			
		||||
          if (!button) return;
 | 
			
		||||
          
 | 
			
		||||
          e.preventDefault();
 | 
			
		||||
          
 | 
			
		||||
          console.log('[AUTH] Contribute button clicked:', button.getAttribute('data-contribute-button'));
 | 
			
		||||
          
 | 
			
		||||
          await requireClientAuth(() => {
 | 
			
		||||
            console.log('[AUTH] Navigation approved, redirecting to:', (button as HTMLAnchorElement).href);
 | 
			
		||||
            window.location.href = (button as HTMLAnchorElement).href;
 | 
			
		||||
          }, (button as HTMLAnchorElement).href);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Make functions available globally
 | 
			
		||||
      (window as any).createToolSlug = createToolSlug;
 | 
			
		||||
      (window as any).findToolByIdentifier = findToolByIdentifier;
 | 
			
		||||
      (window as any).isToolHosted = isToolHosted;
 | 
			
		||||
      (window as any).checkClientAuth = checkClientAuth;
 | 
			
		||||
      (window as any).requireClientAuth = requireClientAuth;
 | 
			
		||||
      (window as any).showIfAuthenticated = showIfAuthenticated;
 | 
			
		||||
      (window as any).setupAuthButtons = setupAuthButtons;
 | 
			
		||||
 | 
			
		||||
      // Initialize everything
 | 
			
		||||
      initTheme();
 | 
			
		||||
      setupAuthButtons('[data-contribute-button]');
 | 
			
		||||
      
 | 
			
		||||
      // Initialize AI button visibility (moved from ToolFilters.astro to avoid race condition)
 | 
			
		||||
      const initAIButton = async () => {
 | 
			
		||||
        await showIfAuthenticated('#ai-view-toggle');
 | 
			
		||||
      };
 | 
			
		||||
      initAIButton();
 | 
			
		||||
      
 | 
			
		||||
      console.log('[CONSOLIDATED] All utilities loaded and initialized');
 | 
			
		||||
    });
 | 
			
		||||
  </script>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
 | 
			
		||||
@ -1,86 +0,0 @@
 | 
			
		||||
// src/scripts/client-auth.js - CONSOLIDATED client-side auth utilities
 | 
			
		||||
// This file REPLACES auth-utils.js and any client-side auth functions
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check authentication status
 | 
			
		||||
 */
 | 
			
		||||
async function checkClientAuth() {
 | 
			
		||||
  try {
 | 
			
		||||
    const response = await fetch('/api/auth/status');
 | 
			
		||||
    const data = await response.json();
 | 
			
		||||
    return {
 | 
			
		||||
      authenticated: data.authenticated,
 | 
			
		||||
      authRequired: data.authRequired,
 | 
			
		||||
      expires: data.expires
 | 
			
		||||
    };
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error('Auth check failed:', error);
 | 
			
		||||
    return {
 | 
			
		||||
      authenticated: false,
 | 
			
		||||
      authRequired: true
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Redirect to login if not authenticated, otherwise execute callback
 | 
			
		||||
 */
 | 
			
		||||
async function requireClientAuth(callback, returnUrl) {
 | 
			
		||||
  const authStatus = await checkClientAuth();
 | 
			
		||||
  
 | 
			
		||||
  if (authStatus.authRequired && !authStatus.authenticated) {
 | 
			
		||||
    const targetUrl = returnUrl || window.location.href;
 | 
			
		||||
    window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`;
 | 
			
		||||
    return false;
 | 
			
		||||
  } else {
 | 
			
		||||
    if (typeof callback === 'function') {
 | 
			
		||||
      callback();
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Show/hide element based on authentication
 | 
			
		||||
 */
 | 
			
		||||
async function showIfAuthenticated(selector) {
 | 
			
		||||
  const authStatus = await checkClientAuth();
 | 
			
		||||
  const element = document.querySelector(selector);
 | 
			
		||||
  
 | 
			
		||||
  if (element) {
 | 
			
		||||
    element.style.display = (!authStatus.authRequired || authStatus.authenticated) 
 | 
			
		||||
      ? 'inline-flex' 
 | 
			
		||||
      : 'none';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
function setupAuthButtons(selector = '[data-contribute-button]') {
 | 
			
		||||
  // Use event delegation on document for dynamic content support
 | 
			
		||||
  document.addEventListener('click', async (e) => {
 | 
			
		||||
    const button = e.target.closest(selector);
 | 
			
		||||
    if (!button) return;
 | 
			
		||||
    
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    
 | 
			
		||||
    // Enhanced error handling and debugging
 | 
			
		||||
    console.log('[AUTH] Contribute button clicked:', button.getAttribute('data-contribute-button'));
 | 
			
		||||
    
 | 
			
		||||
    await requireClientAuth(() => {
 | 
			
		||||
      console.log('[AUTH] Navigation approved, redirecting to:', button.href);
 | 
			
		||||
      window.location.href = button.href;
 | 
			
		||||
    }, button.href);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Make functions available globally for dynamic content
 | 
			
		||||
window.checkClientAuth = checkClientAuth;
 | 
			
		||||
window.requireClientAuth = requireClientAuth;
 | 
			
		||||
window.showIfAuthenticated = showIfAuthenticated;
 | 
			
		||||
window.setupAuthButtons = setupAuthButtons;
 | 
			
		||||
 | 
			
		||||
// Auto-setup contribute buttons when DOM is ready
 | 
			
		||||
document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
  console.log('[AUTH] Setting up global auth handlers for contribute buttons');
 | 
			
		||||
  setupAuthButtons('[data-contribute-button]');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
console.log('Client auth utilities loaded');
 | 
			
		||||
@ -1,64 +0,0 @@
 | 
			
		||||
// Theme management
 | 
			
		||||
const THEME_KEY = 'dfir-theme';
 | 
			
		||||
 | 
			
		||||
// Get system preference
 | 
			
		||||
function getSystemTheme() {
 | 
			
		||||
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get stored theme or default to auto
 | 
			
		||||
function getStoredTheme() {
 | 
			
		||||
  return localStorage.getItem(THEME_KEY) || 'auto';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Apply theme to document
 | 
			
		||||
function applyTheme(theme) {
 | 
			
		||||
  const effectiveTheme = theme === 'auto' ? getSystemTheme() : theme;
 | 
			
		||||
  document.documentElement.setAttribute('data-theme', effectiveTheme);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Update theme toggle button state
 | 
			
		||||
function updateThemeToggle(theme) {
 | 
			
		||||
  document.querySelectorAll('[data-theme-toggle]').forEach(button => {
 | 
			
		||||
    button.setAttribute('data-current-theme', theme);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Initialize theme on page load
 | 
			
		||||
function initTheme() {
 | 
			
		||||
  const storedTheme = getStoredTheme();
 | 
			
		||||
  applyTheme(storedTheme);
 | 
			
		||||
  
 | 
			
		||||
  // Update theme toggle buttons immediately
 | 
			
		||||
  updateThemeToggle(storedTheme);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Handle theme toggle
 | 
			
		||||
function toggleTheme() {
 | 
			
		||||
  const current = getStoredTheme();
 | 
			
		||||
  const themes = ['light', 'dark', 'auto'];
 | 
			
		||||
  const currentIndex = themes.indexOf(current);
 | 
			
		||||
  const nextIndex = (currentIndex + 1) % themes.length;
 | 
			
		||||
  const nextTheme = themes[nextIndex];
 | 
			
		||||
  
 | 
			
		||||
  localStorage.setItem(THEME_KEY, nextTheme);
 | 
			
		||||
  applyTheme(nextTheme);
 | 
			
		||||
  updateThemeToggle(nextTheme);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Listen for system theme changes
 | 
			
		||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
 | 
			
		||||
  if (getStoredTheme() === 'auto') {
 | 
			
		||||
    applyTheme('auto');
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Initialize when DOM is ready (for safety)
 | 
			
		||||
document.addEventListener('DOMContentLoaded', initTheme);
 | 
			
		||||
 | 
			
		||||
// Export functions for use in Astro components
 | 
			
		||||
window.themeUtils = {
 | 
			
		||||
  initTheme,
 | 
			
		||||
  toggleTheme,
 | 
			
		||||
  getStoredTheme
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user