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 '';
|
||||
}
|
||||
|
||||
function getSystemTheme() {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
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 getStoredTheme() {
|
||||
return localStorage.getItem(THEME_KEY) || 'auto';
|
||||
}
|
||||
function findToolByIdentifier(tools, identifier) {
|
||||
if (!identifier || !Array.isArray(tools)) return undefined;
|
||||
|
||||
function applyTheme(theme) {
|
||||
const effectiveTheme = theme === 'auto' ? getSystemTheme() : theme;
|
||||
document.documentElement.setAttribute('data-theme', effectiveTheme);
|
||||
}
|
||||
return tools.find(tool =>
|
||||
tool.name === identifier ||
|
||||
createToolSlug(tool.name) === identifier.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
function updateThemeToggle(theme) {
|
||||
document.querySelectorAll('[data-theme-toggle]').forEach(button => {
|
||||
button.setAttribute('data-current-theme', theme);
|
||||
});
|
||||
}
|
||||
function isToolHosted(tool) {
|
||||
return tool.projectUrl !== undefined &&
|
||||
tool.projectUrl !== null &&
|
||||
tool.projectUrl !== "" &&
|
||||
tool.projectUrl.trim() !== "";
|
||||
}
|
||||
|
||||
function initTheme() {
|
||||
const storedTheme = getStoredTheme();
|
||||
applyTheme(storedTheme);
|
||||
updateThemeToggle(storedTheme);
|
||||
}
|
||||
// Consolidated scrolling utility - also moved outside DOMContentLoaded
|
||||
function scrollToElement(element, options = {}) {
|
||||
if (!element) return;
|
||||
|
||||
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];
|
||||
// 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
|
||||
|
||||
localStorage.setItem(THEME_KEY, nextTheme);
|
||||
applyTheme(nextTheme);
|
||||
updateThemeToggle(nextTheme);
|
||||
}
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
if (getStoredTheme() === 'auto') {
|
||||
applyTheme('auto');
|
||||
}
|
||||
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;
|
||||
function scrollToElementBySelector(selector, options = {}) {
|
||||
const element = document.querySelector(selector);
|
||||
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
|
||||
// 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.scrollTo({
|
||||
top: targetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}, 100);
|
||||
};
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const THEME_KEY = 'dfir-theme';
|
||||
|
||||
// Convenience functions for common scroll targets
|
||||
(window as any).scrollToElementById = function(elementId, options = {}) {
|
||||
const element = document.getElementById(elementId);
|
||||
(window as any).scrollToElement(element, options);
|
||||
};
|
||||
function getSystemTheme() {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
(window as any).scrollToElementBySelector = function(selector, options = {}) {
|
||||
const element = document.querySelector(selector);
|
||||
(window as any).scrollToElement(element, options);
|
||||
};
|
||||
function getStoredTheme() {
|
||||
return localStorage.getItem(THEME_KEY) || 'auto';
|
||||
}
|
||||
|
||||
function createToolSlug(toolName) {
|
||||
if (!toolName || typeof toolName !== 'string') {
|
||||
console.warn('[toolHelpers] Invalid toolName provided to createToolSlug:', toolName);
|
||||
return '';
|
||||
}
|
||||
function applyTheme(theme) {
|
||||
const effectiveTheme = theme === 'auto' ? getSystemTheme() : theme;
|
||||
document.documentElement.setAttribute('data-theme', effectiveTheme);
|
||||
}
|
||||
|
||||
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 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');
|
||||
}
|
||||
|
||||
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() !== "";
|
||||
}
|
||||
|
||||
(window as any).createToolSlug = createToolSlug;
|
||||
(window as any).findToolByIdentifier = findToolByIdentifier;
|
||||
(window as any).isToolHosted = isToolHosted;
|
||||
|
||||
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>
|
||||
|
||||
(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