fix links
This commit is contained in:
		
							parent
							
								
									0eed65e623
								
							
						
					
					
						commit
						6c7d3528f7
					
				@ -20,215 +20,220 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
 | 
			
		||||
  <title>{title} - ForensicPathways</title>
 | 
			
		||||
  <link rel="icon" type="image/x-icon" href="/favicon.ico">
 | 
			
		||||
  
 | 
			
		||||
  <script>
 | 
			
		||||
    document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
      const THEME_KEY = 'dfir-theme';
 | 
			
		||||
<script>
 | 
			
		||||
  // Move utility functions OUTSIDE DOMContentLoaded to avoid race conditions
 | 
			
		||||
  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 getSystemTheme() {
 | 
			
		||||
        return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
 | 
			
		||||
      }
 | 
			
		||||
  function findToolByIdentifier(tools, identifier) {
 | 
			
		||||
    if (!identifier || !Array.isArray(tools)) return undefined;
 | 
			
		||||
    
 | 
			
		||||
    return tools.find(tool => 
 | 
			
		||||
      tool.name === identifier || 
 | 
			
		||||
      createToolSlug(tool.name) === identifier.toLowerCase()
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
      function getStoredTheme() {
 | 
			
		||||
        return localStorage.getItem(THEME_KEY) || 'auto';
 | 
			
		||||
      }
 | 
			
		||||
  function isToolHosted(tool) {
 | 
			
		||||
    return tool.projectUrl !== undefined && 
 | 
			
		||||
           tool.projectUrl !== null && 
 | 
			
		||||
           tool.projectUrl !== "" && 
 | 
			
		||||
           tool.projectUrl.trim() !== "";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
      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);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
 | 
			
		||||
        if (getStoredTheme() === 'auto') {
 | 
			
		||||
          applyTheme('auto');
 | 
			
		||||
        }
 | 
			
		||||
  // Consolidated scrolling utility - also moved outside DOMContentLoaded
 | 
			
		||||
  function scrollToElement(element, options = {}) {
 | 
			
		||||
    if (!element) return;
 | 
			
		||||
    
 | 
			
		||||
    // Calculate target position manually to avoid double-scroll
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      const headerHeight = document.querySelector('nav')?.offsetHeight || 80;
 | 
			
		||||
      const elementRect = element.getBoundingClientRect();
 | 
			
		||||
      const absoluteElementTop = elementRect.top + window.pageYOffset;
 | 
			
		||||
      const targetPosition = absoluteElementTop - headerHeight - 20; // Adjust this 20 as needed
 | 
			
		||||
      
 | 
			
		||||
      window.scrollTo({
 | 
			
		||||
        top: targetPosition,
 | 
			
		||||
        behavior: 'smooth'
 | 
			
		||||
      });
 | 
			
		||||
    }, 100);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
      (window as any).themeUtils = {
 | 
			
		||||
        initTheme,
 | 
			
		||||
        toggleTheme,
 | 
			
		||||
        getStoredTheme
 | 
			
		||||
      };
 | 
			
		||||
  // Convenience functions for common scroll targets
 | 
			
		||||
  function scrollToElementById(elementId, options = {}) {
 | 
			
		||||
    const element = document.getElementById(elementId);
 | 
			
		||||
    scrollToElement(element, options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
      // Consolidated scrolling utility
 | 
			
		||||
      (window as any).scrollToElement = function(element, options = {}) {
 | 
			
		||||
        if (!element) return;
 | 
			
		||||
        
 | 
			
		||||
        // Calculate target position manually to avoid double-scroll
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          const headerHeight = document.querySelector('nav')?.offsetHeight || 80;
 | 
			
		||||
          const elementRect = element.getBoundingClientRect();
 | 
			
		||||
          const absoluteElementTop = elementRect.top + window.pageYOffset;
 | 
			
		||||
          const targetPosition = absoluteElementTop - headerHeight - 20; // Adjust this 20 as needed
 | 
			
		||||
          
 | 
			
		||||
          window.scrollTo({
 | 
			
		||||
            top: targetPosition,
 | 
			
		||||
            behavior: 'smooth'
 | 
			
		||||
          });
 | 
			
		||||
        }, 100);
 | 
			
		||||
      };
 | 
			
		||||
  function scrollToElementBySelector(selector, options = {}) {
 | 
			
		||||
    const element = document.querySelector(selector);
 | 
			
		||||
    scrollToElement(element, options);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
      // Convenience functions for common scroll targets
 | 
			
		||||
      (window as any).scrollToElementById = function(elementId, options = {}) {
 | 
			
		||||
        const element = document.getElementById(elementId);
 | 
			
		||||
        (window as any).scrollToElement(element, options);
 | 
			
		||||
      };
 | 
			
		||||
  // Attach to window immediately - BEFORE DOMContentLoaded
 | 
			
		||||
  (window as any).createToolSlug = createToolSlug;
 | 
			
		||||
  (window as any).findToolByIdentifier = findToolByIdentifier;
 | 
			
		||||
  (window as any).isToolHosted = isToolHosted;
 | 
			
		||||
  (window as any).scrollToElement = scrollToElement;
 | 
			
		||||
  (window as any).scrollToElementById = scrollToElementById;
 | 
			
		||||
  (window as any).scrollToElementBySelector = scrollToElementBySelector;
 | 
			
		||||
 | 
			
		||||
      (window as any).scrollToElementBySelector = function(selector, options = {}) {
 | 
			
		||||
        const element = document.querySelector(selector);
 | 
			
		||||
        (window as any).scrollToElement(element, options);
 | 
			
		||||
      };
 | 
			
		||||
  document.addEventListener('DOMContentLoaded', () => {
 | 
			
		||||
    const THEME_KEY = 'dfir-theme';
 | 
			
		||||
 | 
			
		||||
      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 getSystemTheme() {
 | 
			
		||||
      return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      function findToolByIdentifier(tools, identifier) {
 | 
			
		||||
        if (!identifier || !Array.isArray(tools)) return undefined;
 | 
			
		||||
        
 | 
			
		||||
        return tools.find(tool => 
 | 
			
		||||
          tool.name === identifier || 
 | 
			
		||||
          createToolSlug(tool.name) === identifier.toLowerCase()
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    function getStoredTheme() {
 | 
			
		||||
      return localStorage.getItem(THEME_KEY) || 'auto';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      function isToolHosted(tool) {
 | 
			
		||||
        return tool.projectUrl !== undefined && 
 | 
			
		||||
               tool.projectUrl !== null && 
 | 
			
		||||
               tool.projectUrl !== "" && 
 | 
			
		||||
               tool.projectUrl.trim() !== "";
 | 
			
		||||
      }
 | 
			
		||||
    function applyTheme(theme) {
 | 
			
		||||
      const effectiveTheme = theme === 'auto' ? getSystemTheme() : theme;
 | 
			
		||||
      document.documentElement.setAttribute('data-theme', effectiveTheme);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      (window as any).createToolSlug = createToolSlug;
 | 
			
		||||
      (window as any).findToolByIdentifier = findToolByIdentifier;
 | 
			
		||||
      (window as any).isToolHosted = isToolHosted;
 | 
			
		||||
    function updateThemeToggle(theme) {
 | 
			
		||||
      document.querySelectorAll('[data-theme-toggle]').forEach(button => {
 | 
			
		||||
        button.setAttribute('data-current-theme', theme);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      async function checkClientAuth(context = 'general') {
 | 
			
		||||
        try {
 | 
			
		||||
          const response = await fetch('/api/auth/status');
 | 
			
		||||
          const data = await response.json();
 | 
			
		||||
          
 | 
			
		||||
          switch (context) {
 | 
			
		||||
            case 'contributions':
 | 
			
		||||
              return {
 | 
			
		||||
                authenticated: data.contributionAuthenticated,
 | 
			
		||||
                authRequired: data.contributionAuthRequired,
 | 
			
		||||
                expires: data.expires
 | 
			
		||||
              };
 | 
			
		||||
            case 'ai':
 | 
			
		||||
              return {
 | 
			
		||||
                authenticated: data.aiAuthenticated,
 | 
			
		||||
                authRequired: data.aiAuthRequired,
 | 
			
		||||
                expires: data.expires
 | 
			
		||||
              };
 | 
			
		||||
            default:
 | 
			
		||||
              return {
 | 
			
		||||
                authenticated: data.authenticated,
 | 
			
		||||
                authRequired: data.contributionAuthRequired || data.aiAuthRequired,
 | 
			
		||||
                expires: data.expires
 | 
			
		||||
              };
 | 
			
		||||
          }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
          console.error('Auth check failed:', error);
 | 
			
		||||
          return {
 | 
			
		||||
            authenticated: false,
 | 
			
		||||
            authRequired: true
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    function initTheme() {
 | 
			
		||||
      const storedTheme = getStoredTheme();
 | 
			
		||||
      applyTheme(storedTheme);
 | 
			
		||||
      updateThemeToggle(storedTheme);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
      async function requireClientAuth(callback, returnUrl, context = 'general') {
 | 
			
		||||
        const authStatus = await checkClientAuth(context);
 | 
			
		||||
        
 | 
			
		||||
        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, context = 'general') {
 | 
			
		||||
        const authStatus = await checkClientAuth(context);
 | 
			
		||||
        const element = document.querySelector(selector);
 | 
			
		||||
        
 | 
			
		||||
        if (element) {
 | 
			
		||||
          element.style.display = (!authStatus.authRequired || authStatus.authenticated) 
 | 
			
		||||
            ? 'inline-flex' 
 | 
			
		||||
            : 'none';
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function setupAuthButtons(selector = '[data-contribute-button]') {
 | 
			
		||||
        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, 'contributions');
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      (window as any).checkClientAuth = checkClientAuth;
 | 
			
		||||
      (window as any).requireClientAuth = requireClientAuth;
 | 
			
		||||
      (window as any).showIfAuthenticated = showIfAuthenticated;
 | 
			
		||||
      (window as any).setupAuthButtons = setupAuthButtons;
 | 
			
		||||
 | 
			
		||||
      initTheme();
 | 
			
		||||
      setupAuthButtons('[data-contribute-button]');
 | 
			
		||||
    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];
 | 
			
		||||
      
 | 
			
		||||
      const initAIButton = async () => {
 | 
			
		||||
        await showIfAuthenticated('#ai-view-toggle', 'ai');
 | 
			
		||||
      };
 | 
			
		||||
      initAIButton();
 | 
			
		||||
      
 | 
			
		||||
      console.log('[CONSOLIDATED] All utilities loaded and initialized');
 | 
			
		||||
      localStorage.setItem(THEME_KEY, nextTheme);
 | 
			
		||||
      applyTheme(nextTheme);
 | 
			
		||||
      updateThemeToggle(nextTheme);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
 | 
			
		||||
      if (getStoredTheme() === 'auto') {
 | 
			
		||||
        applyTheme('auto');
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  </script>
 | 
			
		||||
 | 
			
		||||
    (window as any).themeUtils = {
 | 
			
		||||
      initTheme,
 | 
			
		||||
      toggleTheme,
 | 
			
		||||
      getStoredTheme
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    async function checkClientAuth(context = 'general') {
 | 
			
		||||
      try {
 | 
			
		||||
        const response = await fetch('/api/auth/status');
 | 
			
		||||
        const data = await response.json();
 | 
			
		||||
        
 | 
			
		||||
        switch (context) {
 | 
			
		||||
          case 'contributions':
 | 
			
		||||
            return {
 | 
			
		||||
              authenticated: data.contributionAuthenticated,
 | 
			
		||||
              authRequired: data.contributionAuthRequired,
 | 
			
		||||
              expires: data.expires
 | 
			
		||||
            };
 | 
			
		||||
          case 'ai':
 | 
			
		||||
            return {
 | 
			
		||||
              authenticated: data.aiAuthenticated,
 | 
			
		||||
              authRequired: data.aiAuthRequired,
 | 
			
		||||
              expires: data.expires
 | 
			
		||||
            };
 | 
			
		||||
          default:
 | 
			
		||||
            return {
 | 
			
		||||
              authenticated: data.authenticated,
 | 
			
		||||
              authRequired: data.contributionAuthRequired || data.aiAuthRequired,
 | 
			
		||||
              expires: data.expires
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error('Auth check failed:', error);
 | 
			
		||||
        return {
 | 
			
		||||
          authenticated: false,
 | 
			
		||||
          authRequired: true
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async function requireClientAuth(callback, returnUrl, context = 'general') {
 | 
			
		||||
      const authStatus = await checkClientAuth(context);
 | 
			
		||||
      
 | 
			
		||||
      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, context = 'general') {
 | 
			
		||||
      const authStatus = await checkClientAuth(context);
 | 
			
		||||
      const element = document.querySelector(selector);
 | 
			
		||||
      
 | 
			
		||||
      if (element) {
 | 
			
		||||
        element.style.display = (!authStatus.authRequired || authStatus.authenticated) 
 | 
			
		||||
          ? 'inline-flex' 
 | 
			
		||||
          : 'none';
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function setupAuthButtons(selector = '[data-contribute-button]') {
 | 
			
		||||
      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, 'contributions');
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    (window as any).checkClientAuth = checkClientAuth;
 | 
			
		||||
    (window as any).requireClientAuth = requireClientAuth;
 | 
			
		||||
    (window as any).showIfAuthenticated = showIfAuthenticated;
 | 
			
		||||
    (window as any).setupAuthButtons = setupAuthButtons;
 | 
			
		||||
 | 
			
		||||
    initTheme();
 | 
			
		||||
    setupAuthButtons('[data-contribute-button]');
 | 
			
		||||
    
 | 
			
		||||
    const initAIButton = async () => {
 | 
			
		||||
      await showIfAuthenticated('#ai-view-toggle', 'ai');
 | 
			
		||||
    };
 | 
			
		||||
    initAIButton();
 | 
			
		||||
    
 | 
			
		||||
    console.log('[CONSOLIDATED] All utilities loaded and initialized');
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <Navigation />
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user