fix links

This commit is contained in:
overcuriousity 2025-07-27 21:18:11 +02:00
parent 0eed65e623
commit 6c7d3528f7

View File

@ -20,215 +20,220 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
<title>{title} - ForensicPathways</title> <title>{title} - ForensicPathways</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico"> <link rel="icon" type="image/x-icon" href="/favicon.ico">
<script> <script>
document.addEventListener('DOMContentLoaded', () => { // Move utility functions OUTSIDE DOMContentLoaded to avoid race conditions
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() { function findToolByIdentifier(tools, identifier) {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; if (!identifier || !Array.isArray(tools)) return undefined;
}
return tools.find(tool =>
tool.name === identifier ||
createToolSlug(tool.name) === identifier.toLowerCase()
);
}
function getStoredTheme() { function isToolHosted(tool) {
return localStorage.getItem(THEME_KEY) || 'auto'; return tool.projectUrl !== undefined &&
} tool.projectUrl !== null &&
tool.projectUrl !== "" &&
tool.projectUrl.trim() !== "";
}
function applyTheme(theme) { // Consolidated scrolling utility - also moved outside DOMContentLoaded
const effectiveTheme = theme === 'auto' ? getSystemTheme() : theme; function scrollToElement(element, options = {}) {
document.documentElement.setAttribute('data-theme', effectiveTheme); if (!element) return;
}
// Calculate target position manually to avoid double-scroll
function updateThemeToggle(theme) { setTimeout(() => {
document.querySelectorAll('[data-theme-toggle]').forEach(button => { const headerHeight = document.querySelector('nav')?.offsetHeight || 80;
button.setAttribute('data-current-theme', theme); const elementRect = element.getBoundingClientRect();
}); const absoluteElementTop = elementRect.top + window.pageYOffset;
} const targetPosition = absoluteElementTop - headerHeight - 20; // Adjust this 20 as needed
function initTheme() { window.scrollTo({
const storedTheme = getStoredTheme(); top: targetPosition,
applyTheme(storedTheme); behavior: 'smooth'
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');
}
}); });
}, 100);
}
(window as any).themeUtils = { // Convenience functions for common scroll targets
initTheme, function scrollToElementById(elementId, options = {}) {
toggleTheme, const element = document.getElementById(elementId);
getStoredTheme scrollToElement(element, options);
}; }
// Consolidated scrolling utility function scrollToElementBySelector(selector, options = {}) {
(window as any).scrollToElement = function(element, options = {}) { const element = document.querySelector(selector);
if (!element) return; scrollToElement(element, options);
}
// 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);
};
// Convenience functions for common scroll targets // Attach to window immediately - BEFORE DOMContentLoaded
(window as any).scrollToElementById = function(elementId, options = {}) { (window as any).createToolSlug = createToolSlug;
const element = document.getElementById(elementId); (window as any).findToolByIdentifier = findToolByIdentifier;
(window as any).scrollToElement(element, options); (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 = {}) { document.addEventListener('DOMContentLoaded', () => {
const element = document.querySelector(selector); const THEME_KEY = 'dfir-theme';
(window as any).scrollToElement(element, options);
};
function createToolSlug(toolName) { function getSystemTheme() {
if (!toolName || typeof toolName !== 'string') { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
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) { function getStoredTheme() {
if (!identifier || !Array.isArray(tools)) return undefined; return localStorage.getItem(THEME_KEY) || 'auto';
}
return tools.find(tool =>
tool.name === identifier ||
createToolSlug(tool.name) === identifier.toLowerCase()
);
}
function isToolHosted(tool) { function applyTheme(theme) {
return tool.projectUrl !== undefined && const effectiveTheme = theme === 'auto' ? getSystemTheme() : theme;
tool.projectUrl !== null && document.documentElement.setAttribute('data-theme', effectiveTheme);
tool.projectUrl !== "" && }
tool.projectUrl.trim() !== "";
}
(window as any).createToolSlug = createToolSlug; function updateThemeToggle(theme) {
(window as any).findToolByIdentifier = findToolByIdentifier; document.querySelectorAll('[data-theme-toggle]').forEach(button => {
(window as any).isToolHosted = isToolHosted; button.setAttribute('data-current-theme', theme);
});
}
async function checkClientAuth(context = 'general') { function initTheme() {
try { const storedTheme = getStoredTheme();
const response = await fetch('/api/auth/status'); applyTheme(storedTheme);
const data = await response.json(); updateThemeToggle(storedTheme);
}
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') { function toggleTheme() {
const authStatus = await checkClientAuth(context); const current = getStoredTheme();
const themes = ['light', 'dark', 'auto'];
if (authStatus.authRequired && !authStatus.authenticated) { const currentIndex = themes.indexOf(current);
const targetUrl = returnUrl || window.location.href; const nextIndex = (currentIndex + 1) % themes.length;
window.location.href = `/api/auth/login?returnTo=${encodeURIComponent(targetUrl)}`; const nextTheme = themes[nextIndex];
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 () => { localStorage.setItem(THEME_KEY, nextTheme);
await showIfAuthenticated('#ai-view-toggle', 'ai'); applyTheme(nextTheme);
}; updateThemeToggle(nextTheme);
initAIButton(); }
console.log('[CONSOLIDATED] All utilities loaded and initialized'); 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> </head>
<body> <body>
<Navigation /> <Navigation />