knowledgebase share button
This commit is contained in:
parent
9ce2098439
commit
9a3122745d
@ -771,35 +771,28 @@ const sortedTags = Object.entries(tagFrequency)
|
|||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
const view = btn.getAttribute('data-view');
|
const view = btn.getAttribute('data-view');
|
||||||
|
|
||||||
// Update active states
|
|
||||||
elements.viewToggles.forEach(b => {
|
elements.viewToggles.forEach(b => {
|
||||||
b.classList.toggle('active', b.getAttribute('data-view') === view);
|
b.classList.toggle('active', b.getAttribute('data-view') === view);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Call the global switchToView function
|
|
||||||
if (window.switchToView) {
|
if (window.switchToView) {
|
||||||
window.switchToView(view);
|
window.switchToView(view);
|
||||||
} else {
|
} else {
|
||||||
console.error('switchToView function not available');
|
console.error('switchToView function not available');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch view changed event
|
|
||||||
window.dispatchEvent(new CustomEvent('viewChanged', {
|
window.dispatchEvent(new CustomEvent('viewChanged', {
|
||||||
detail: view,
|
detail: view,
|
||||||
triggeredByButton: true
|
triggeredByButton: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Handle filtering after view switch
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (view === 'matrix') {
|
if (view === 'matrix') {
|
||||||
// Ensure matrix gets populated by triggering filter
|
|
||||||
filterTools();
|
filterTools();
|
||||||
} else if (view === 'grid') {
|
} else if (view === 'grid') {
|
||||||
// Standard filtering for grid view
|
|
||||||
filterTools();
|
filterTools();
|
||||||
}
|
}
|
||||||
// AI view doesn't need filtering from here
|
}, 100);
|
||||||
}, 100); // Slightly longer delay to ensure view switch completes
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -789,7 +789,7 @@ domains.forEach((domain: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('toolsFiltered', (event) => {
|
window.addEventListener('toolsFiltered', (event) => {
|
||||||
const { tools: filtered, semanticSearch } = event.detail; // ✅ Correct destructuring
|
const { tools: filtered, semanticSearch } = event.detail;
|
||||||
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
|
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
|
||||||
|
|
||||||
if (currentView === 'matrix') {
|
if (currentView === 'matrix') {
|
||||||
|
3
src/env.d.ts
vendored
3
src/env.d.ts
vendored
@ -39,6 +39,9 @@ declare global {
|
|||||||
toggleAllScenarios?: () => void;
|
toggleAllScenarios?: () => void;
|
||||||
showShareDialog?: (shareButton: Element) => void;
|
showShareDialog?: (shareButton: Element) => void;
|
||||||
modalHideInProgress?: boolean;
|
modalHideInProgress?: boolean;
|
||||||
|
|
||||||
|
shareArticle: (button: HTMLElement, url: string, title: string) => Promise<void>;
|
||||||
|
shareCurrentArticle: (button: HTMLElement) => Promise<void>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,18 +22,15 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
|
|||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Import utility functions from shared client module instead of duplicating
|
|
||||||
async function loadUtilityFunctions() {
|
async function loadUtilityFunctions() {
|
||||||
try {
|
try {
|
||||||
const { createToolSlug, findToolByIdentifier, isToolHosted } = await import('../utils/clientUtils.js');
|
const { createToolSlug, findToolByIdentifier, isToolHosted } = await import('../utils/clientUtils.js');
|
||||||
|
|
||||||
// Make functions available globally for backward compatibility
|
|
||||||
(window as any).createToolSlug = createToolSlug;
|
(window as any).createToolSlug = createToolSlug;
|
||||||
(window as any).findToolByIdentifier = findToolByIdentifier;
|
(window as any).findToolByIdentifier = findToolByIdentifier;
|
||||||
(window as any).isToolHosted = isToolHosted;
|
(window as any).isToolHosted = isToolHosted;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load utility functions:', error);
|
console.error('Failed to load utility functions:', error);
|
||||||
// Minimal fallback for critical functionality only
|
|
||||||
(window as any).createToolSlug = (toolName: string) => {
|
(window as any).createToolSlug = (toolName: string) => {
|
||||||
if (!toolName || typeof toolName !== 'string') return '';
|
if (!toolName || typeof toolName !== 'string') return '';
|
||||||
return toolName.toLowerCase().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
return toolName.toLowerCase().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
||||||
@ -92,14 +89,12 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set non-duplicated functions on window
|
|
||||||
(window as any).scrollToElement = scrollToElement;
|
(window as any).scrollToElement = scrollToElement;
|
||||||
(window as any).scrollToElementById = scrollToElementById;
|
(window as any).scrollToElementById = scrollToElementById;
|
||||||
(window as any).scrollToElementBySelector = scrollToElementBySelector;
|
(window as any).scrollToElementBySelector = scrollToElementBySelector;
|
||||||
(window as any).prioritizeSearchResults = prioritizeSearchResults;
|
(window as any).prioritizeSearchResults = prioritizeSearchResults;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// Load utility functions first
|
|
||||||
loadUtilityFunctions();
|
loadUtilityFunctions();
|
||||||
|
|
||||||
const THEME_KEY = 'dfir-theme';
|
const THEME_KEY = 'dfir-theme';
|
||||||
@ -236,6 +231,51 @@ const { title, description = 'ForensicPathways - A comprehensive directory of di
|
|||||||
(window as any).showIfAuthenticated = showIfAuthenticated;
|
(window as any).showIfAuthenticated = showIfAuthenticated;
|
||||||
(window as any).setupAuthButtons = setupAuthButtons;
|
(window as any).setupAuthButtons = setupAuthButtons;
|
||||||
|
|
||||||
|
async function copyUrlToClipboard(url: string, button: HTMLElement) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(url);
|
||||||
|
|
||||||
|
const originalHTML = button.innerHTML;
|
||||||
|
button.innerHTML = `
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.375rem;">
|
||||||
|
<polyline points="20,6 9,17 4,12"/>
|
||||||
|
</svg>
|
||||||
|
Kopiert!
|
||||||
|
`;
|
||||||
|
button.style.color = 'var(--color-accent)';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.innerHTML = originalHTML;
|
||||||
|
button.style.color = '';
|
||||||
|
}, 2000);
|
||||||
|
} catch (err) {
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = url;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
|
||||||
|
const originalHTML = button.innerHTML;
|
||||||
|
button.innerHTML = 'Kopiert!';
|
||||||
|
setTimeout(() => {
|
||||||
|
button.innerHTML = originalHTML;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shareArticle(button: HTMLElement, url: string, title: string) {
|
||||||
|
const fullUrl = window.location.origin + url;
|
||||||
|
await copyUrlToClipboard(fullUrl, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shareCurrentArticle(button: HTMLElement) {
|
||||||
|
await copyUrlToClipboard(window.location.href, button);
|
||||||
|
}
|
||||||
|
|
||||||
|
(window as any).shareArticle = shareArticle;
|
||||||
|
(window as any).shareCurrentArticle = shareCurrentArticle;
|
||||||
|
|
||||||
initTheme();
|
initTheme();
|
||||||
setupAuthButtons('[data-contribute-button]');
|
setupAuthButtons('[data-contribute-button]');
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// src/pages/api/ai/query.ts - FIXED: Rate limiting for micro-task pipeline
|
// src/pages/api/ai/query.ts
|
||||||
|
|
||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
import { withAPIAuth } from '../../../utils/auth.js';
|
import { withAPIAuth } from '../../../utils/auth.js';
|
||||||
|
@ -8,7 +8,7 @@ configDotenv();
|
|||||||
const DEFAULT_MAX_RESULTS = (() => {
|
const DEFAULT_MAX_RESULTS = (() => {
|
||||||
const raw = process.env.AI_EMBEDDING_CANDIDATES;
|
const raw = process.env.AI_EMBEDDING_CANDIDATES;
|
||||||
const n = Number.parseInt(raw ?? '', 10);
|
const n = Number.parseInt(raw ?? '', 10);
|
||||||
return Number.isFinite(n) && n > 0 ? n : 50; // fallback
|
return Number.isFinite(n) && n > 0 ? n : 50;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const DEFAULT_THRESHOLD = (() => {
|
const DEFAULT_THRESHOLD = (() => {
|
||||||
@ -22,7 +22,6 @@ export const prerender = false;
|
|||||||
|
|
||||||
export const POST: APIRoute = async ({ request }) => {
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
try {
|
try {
|
||||||
/* ---------- get body & apply defaults from env ---------------- */
|
|
||||||
const {
|
const {
|
||||||
query,
|
query,
|
||||||
maxResults = DEFAULT_MAX_RESULTS,
|
maxResults = DEFAULT_MAX_RESULTS,
|
||||||
|
@ -367,7 +367,6 @@ if (aiAuthRequired) {
|
|||||||
const methodologySection = document.getElementById('methodology-section');
|
const methodologySection = document.getElementById('methodology-section');
|
||||||
const targetedSection = document.getElementById('targeted-section');
|
const targetedSection = document.getElementById('targeted-section');
|
||||||
|
|
||||||
// Hide all views
|
|
||||||
if (toolsGrid) toolsGrid.style.display = 'none';
|
if (toolsGrid) toolsGrid.style.display = 'none';
|
||||||
if (matrixContainer) {
|
if (matrixContainer) {
|
||||||
matrixContainer.style.display = 'none';
|
matrixContainer.style.display = 'none';
|
||||||
@ -424,7 +423,6 @@ if (aiAuthRequired) {
|
|||||||
console.warn('[VIEW] Unknown view:', view);
|
console.warn('[VIEW] Unknown view:', view);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore all filter sections for non-AI views
|
|
||||||
if (view !== 'ai' && filtersSection) {
|
if (view !== 'ai' && filtersSection) {
|
||||||
const filterSections = filtersSection.querySelectorAll('.filter-section');
|
const filterSections = filtersSection.querySelectorAll('.filter-section');
|
||||||
filterSections.forEach(section => {
|
filterSections.forEach(section => {
|
||||||
|
@ -108,13 +108,15 @@ knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
|
|||||||
const isConcept = hasAssociatedTool && entry.associatedTool.type === 'concept';
|
const isConcept = hasAssociatedTool && entry.associatedTool.type === 'concept';
|
||||||
const isStandalone = !hasAssociatedTool;
|
const isStandalone = !hasAssociatedTool;
|
||||||
|
|
||||||
|
const articleUrl = `/knowledgebase/${entry.slug}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
class="kb-entry card cursor-pointer"
|
class="kb-entry card cursor-pointer"
|
||||||
id={`kb-${entry.slug}`}
|
id={`kb-${entry.slug}`}
|
||||||
data-tool-name={entry.title.toLowerCase()}
|
data-tool-name={entry.title.toLowerCase()}
|
||||||
data-article-type={isStandalone ? 'standalone' : 'tool-associated'}
|
data-article-type={isStandalone ? 'standalone' : 'tool-associated'}
|
||||||
onclick={`window.location.href='/knowledgebase/${entry.slug}'`}
|
onclick={`window.location.href='${articleUrl}'`}
|
||||||
>
|
>
|
||||||
<!-- Card Header -->
|
<!-- Card Header -->
|
||||||
<div class="flex-between mb-3">
|
<div class="flex-between mb-3">
|
||||||
@ -137,7 +139,7 @@ knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2 flex-shrink-0" onclick="event.stopPropagation();">
|
<div class="flex gap-2 flex-shrink-0" onclick="event.stopPropagation();">
|
||||||
<a href={`/knowledgebase/${entry.slug}`} class="btn btn-primary btn-sm">
|
<a href={articleUrl} class="btn btn-primary btn-sm">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.375rem;">
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.375rem;">
|
||||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||||
<polyline points="15 3 21 3 21 9"/>
|
<polyline points="15 3 21 3 21 9"/>
|
||||||
@ -145,7 +147,21 @@ knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
|
|||||||
</svg>
|
</svg>
|
||||||
Öffnen
|
Öffnen
|
||||||
</a>
|
</a>
|
||||||
<ContributionButton type="edit" toolName={entry.tool_name || entry.title} variant="secondary" text="Edit" style="font-size: 0.8125rem; padding: 0.5rem 0.75rem;" />
|
<button
|
||||||
|
class="btn btn-secondary btn-sm"
|
||||||
|
onclick="window.shareArticle(this, '${articleUrl}', '${entry.title}')"
|
||||||
|
title="Artikel teilen"
|
||||||
|
style="font-size: 0.8125rem; padding: 0.5rem 0.75rem;"
|
||||||
|
>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.375rem;">
|
||||||
|
<circle cx="18" cy="5" r="3"/>
|
||||||
|
<circle cx="6" cy="12" r="3"/>
|
||||||
|
<circle cx="18" cy="19" r="3"/>
|
||||||
|
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
|
||||||
|
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
|
||||||
|
</svg>
|
||||||
|
Teilen
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -283,5 +299,7 @@ knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
|
|||||||
lastScrollY = window.scrollY;
|
lastScrollY = window.scrollY;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
@ -47,6 +47,8 @@ const hasValidProjectUrl = displayTool && displayTool.projectUrl !== undefined &
|
|||||||
displayTool.projectUrl !== null &&
|
displayTool.projectUrl !== null &&
|
||||||
displayTool.projectUrl !== "" &&
|
displayTool.projectUrl !== "" &&
|
||||||
displayTool.projectUrl.trim() !== "";
|
displayTool.projectUrl.trim() !== "";
|
||||||
|
|
||||||
|
const currentUrl = Astro.url.href;
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={entry.data.title} description={entry.data.description}>
|
<BaseLayout title={entry.data.title} description={entry.data.description}>
|
||||||
@ -77,6 +79,22 @@ const hasValidProjectUrl = displayTool && displayTool.projectUrl !== undefined &
|
|||||||
)}
|
)}
|
||||||
<span class="badge badge-error">📖</span>
|
<span class="badge badge-error">📖</span>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
id="share-article-btn"
|
||||||
|
class="btn btn-secondary btn-sm"
|
||||||
|
onclick="window.shareCurrentArticle(this)"
|
||||||
|
title="Artikel teilen"
|
||||||
|
style="font-size: 0.8125rem; padding: 0.5rem 0.75rem;"
|
||||||
|
>
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.375rem;">
|
||||||
|
<circle cx="18" cy="5" r="3"/>
|
||||||
|
<circle cx="6" cy="12" r="3"/>
|
||||||
|
<circle cx="18" cy="19" r="3"/>
|
||||||
|
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
|
||||||
|
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
|
||||||
|
</svg>
|
||||||
|
Artikel teilen
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user