consolidation
This commit is contained in:
parent
bd7f93167c
commit
bb26d9a80d
@ -145,14 +145,6 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
let selectedPhase = '';
|
let selectedPhase = '';
|
||||||
let isTagCloudExpanded = false;
|
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
|
// Initialize tag cloud state
|
||||||
function initTagCloud() {
|
function initTagCloud() {
|
||||||
const visibleCount = 22;
|
const visibleCount = 22;
|
||||||
|
@ -832,5 +832,4 @@ domains.forEach((domain: any) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setupContributionButtonAuth();
|
|
||||||
</script>
|
</script>
|
6
src/env.d.ts
vendored
6
src/env.d.ts
vendored
@ -23,6 +23,12 @@ declare global {
|
|||||||
createToolSlug: (toolName: string) => string;
|
createToolSlug: (toolName: string) => string;
|
||||||
findToolByIdentifier: (tools: any[], identifier: string) => any | undefined;
|
findToolByIdentifier: (tools: any[], identifier: string) => any | undefined;
|
||||||
isToolHosted: (tool: any) => boolean;
|
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>
|
<title>{title} - CC24-Guide</title>
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
<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>
|
<script>
|
||||||
// Initialize theme immediately to prevent flash
|
// 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>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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