remove dev comments
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
---
|
||||
import { getToolsData } from '../utils/dataService.js';
|
||||
|
||||
// Load tools data for validation
|
||||
const data = await getToolsData();
|
||||
|
||||
const tools = data.tools;
|
||||
@@ -9,7 +8,6 @@ const phases = data.phases;
|
||||
const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
---
|
||||
|
||||
<!-- AI Query Interface -->
|
||||
<section id="ai-interface" class="ai-interface" style="display: none;">
|
||||
<div class="ai-query-section">
|
||||
<div style="text-align: center; margin-bottom: 2rem;">
|
||||
@@ -27,7 +25,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
</div>
|
||||
|
||||
<div class="ai-input-container" style="max-width: 800px; margin: 0 auto;">
|
||||
<!-- Mode Toggle -->
|
||||
<div class="ai-mode-toggle" style="display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 1.5rem; padding: 1rem; background-color: var(--color-bg-secondary); border-radius: 0.75rem; border: 1px solid var(--color-border);">
|
||||
<span id="workflow-label" class="toggle-label active" style="font-weight: 500; color: var(--color-primary); cursor: pointer; transition: var(--transition-fast);">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;">
|
||||
@@ -56,7 +53,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
maxlength="2000"
|
||||
></textarea>
|
||||
|
||||
<!-- Privacy Notice -->
|
||||
<div style="margin-top: 0.5rem; margin-bottom: 1rem;">
|
||||
<p style="font-size: 0.75rem; color: var(--color-text-secondary); text-align: center; line-height: 1.4;">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.25rem; vertical-align: middle;">
|
||||
@@ -82,8 +78,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- This should be your loading section in AIQueryInterface.astro -->
|
||||
<!-- Loading State -->
|
||||
<div id="ai-loading" class="ai-loading" style="display: none; text-align: center; padding: 2rem;">
|
||||
<div style="display: inline-block; margin-bottom: 1rem;">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="var(--color-primary)" stroke-width="2" style="animation: pulse 2s ease-in-out infinite;">
|
||||
@@ -94,7 +88,6 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
</div>
|
||||
<p id="loading-text" style="color: var(--color-text-secondary);">Analysiere Szenario und generiere Empfehlungen...</p>
|
||||
|
||||
<!-- Queue Status Display - THIS SECTION SHOULD BE PRESENT -->
|
||||
<div id="queue-status" style="margin-top: 1rem; padding: 1rem; background-color: var(--color-bg-secondary); border-radius: 0.5rem; border: 1px solid var(--color-border); display: none;">
|
||||
<div style="display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 0.75rem;">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
@@ -114,14 +107,12 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress bar -->
|
||||
<div style="margin-top: 1rem; background-color: var(--color-bg-tertiary); border-radius: 0.25rem; height: 4px; overflow: hidden;">
|
||||
<div id="queue-progress" style="height: 100%; background-color: var(--color-primary); width: 0%; transition: width 0.3s ease;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div id="ai-error" class="ai-error" style="display: none; text-align: center; padding: 2rem;">
|
||||
<div style="background-color: var(--color-error); color: white; padding: 1rem; border-radius: 0.5rem; max-width: 600px; margin: 0 auto;">
|
||||
<h3 style="margin-bottom: 0.5rem;">Fehler bei der KI-Anfrage</h3>
|
||||
@@ -129,9 +120,7 @@ const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Results -->
|
||||
<div id="ai-results" class="ai-results" style="display: none;">
|
||||
<!-- Results will be populated here by JavaScript -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -154,21 +143,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const aiResults = document.getElementById('ai-results');
|
||||
const aiDescription = document.getElementById('ai-description');
|
||||
|
||||
// Toggle elements
|
||||
const toggleSwitch = document.querySelector('.toggle-switch');
|
||||
const toggleSlider = document.querySelector('.toggle-slider');
|
||||
const workflowLabel = document.getElementById('workflow-label');
|
||||
const toolLabel = document.getElementById('tool-label');
|
||||
|
||||
let currentRecommendation = null;
|
||||
let currentMode = 'workflow'; // 'workflow' or 'tool'
|
||||
let currentMode = 'workflow';
|
||||
|
||||
if (!aiInput || !aiSubmitBtn || !aiLoading || !aiError || !aiResults) {
|
||||
console.error('AI interface elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Mode content configuration
|
||||
const modeConfig = {
|
||||
workflow: {
|
||||
placeholder: "Beschreiben Sie Ihr forensisches Szenario... z.B. 'Verdacht auf Ransomware-Angriff auf Windows-Domänencontroller mit verschlüsselten Dateien und verdächtigen Netzwerkverbindungen'",
|
||||
@@ -184,23 +171,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Update UI based on current mode
|
||||
function updateModeUI() {
|
||||
const config = modeConfig[currentMode];
|
||||
|
||||
// Update placeholder
|
||||
aiInput.placeholder = config.placeholder;
|
||||
|
||||
// Update description
|
||||
aiDescription.textContent = config.description;
|
||||
|
||||
// Update submit button text
|
||||
submitBtnText.textContent = config.submitText;
|
||||
|
||||
// Update loading text
|
||||
loadingText.textContent = config.loadingText;
|
||||
|
||||
// Update toggle visual state
|
||||
if (currentMode === 'workflow') {
|
||||
toggleSlider.style.transform = 'translateX(0)';
|
||||
toggleSwitch.style.backgroundColor = 'var(--color-primary)';
|
||||
@@ -217,13 +198,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
workflowLabel.classList.remove('active');
|
||||
}
|
||||
|
||||
// Clear previous results when switching modes
|
||||
aiResults.style.display = 'none';
|
||||
aiError.style.display = 'none';
|
||||
currentRecommendation = null;
|
||||
}
|
||||
|
||||
// Toggle mode handlers
|
||||
function switchToMode(mode) {
|
||||
if (currentMode !== mode) {
|
||||
currentMode = mode;
|
||||
@@ -243,7 +222,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
switchToMode('tool');
|
||||
});
|
||||
|
||||
// Character counter for input
|
||||
const updateCharacterCount = () => {
|
||||
const length = aiInput.value.length;
|
||||
const maxLength = 2000;
|
||||
@@ -267,7 +245,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
aiInput.addEventListener('input', updateCharacterCount);
|
||||
updateCharacterCount();
|
||||
|
||||
// Submit handler with enhanced queue feedback
|
||||
const handleSubmit = async () => {
|
||||
const query = aiInput.value.trim();
|
||||
|
||||
@@ -281,15 +258,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate task ID for tracking
|
||||
const taskId = `ai_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
|
||||
|
||||
// Hide previous results and errors
|
||||
aiResults.style.display = 'none';
|
||||
aiError.style.display = 'none';
|
||||
aiLoading.style.display = 'block';
|
||||
|
||||
// Show queue status section
|
||||
const queueStatus = document.getElementById('queue-status');
|
||||
const taskIdDisplay = document.getElementById('current-task-id');
|
||||
if (queueStatus && taskIdDisplay) {
|
||||
@@ -297,11 +271,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
taskIdDisplay.textContent = taskId;
|
||||
}
|
||||
|
||||
// Disable submit button
|
||||
aiSubmitBtn.disabled = true;
|
||||
submitBtnText.textContent = currentMode === 'workflow' ? 'Generiere Empfehlungen...' : 'Suche passende Methode...';
|
||||
|
||||
// Start queue status polling
|
||||
let statusInterval;
|
||||
let startTime = Date.now();
|
||||
|
||||
@@ -329,14 +301,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (positionBadge && data.currentPosition) {
|
||||
positionBadge.textContent = data.currentPosition;
|
||||
|
||||
// Update progress bar (inverse of position)
|
||||
if (progressBar && data.queueLength > 0) {
|
||||
const progress = Math.max(0, ((data.queueLength - data.currentPosition + 1) / data.queueLength) * 100);
|
||||
progressBar.style.width = `${progress}%`;
|
||||
}
|
||||
}
|
||||
|
||||
// If processing and no position (request is being handled)
|
||||
if (data.isProcessing && !data.currentPosition) {
|
||||
if (positionBadge) positionBadge.textContent = '⚡';
|
||||
if (progressBar) progressBar.style.width = '100%';
|
||||
@@ -348,10 +318,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Initial status update
|
||||
updateQueueStatus();
|
||||
|
||||
// Poll every 500ms for status updates
|
||||
statusInterval = setInterval(updateQueueStatus, 500);
|
||||
|
||||
try {
|
||||
@@ -363,13 +331,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
mode: currentMode,
|
||||
taskId // Include task ID for backend tracking
|
||||
taskId
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Clear status polling
|
||||
if (statusInterval) clearInterval(statusInterval);
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -380,10 +347,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
throw new Error(data.error || 'Unknown error');
|
||||
}
|
||||
|
||||
// Store recommendation for restoration
|
||||
currentRecommendation = data.recommendation;
|
||||
|
||||
// Display results based on mode
|
||||
if (currentMode === 'workflow') {
|
||||
displayWorkflowResults(data.recommendation, query);
|
||||
} else {
|
||||
@@ -396,13 +361,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
} catch (error) {
|
||||
console.error('AI query failed:', error);
|
||||
|
||||
// Clear status polling
|
||||
if (statusInterval) clearInterval(statusInterval);
|
||||
|
||||
aiLoading.style.display = 'none';
|
||||
aiError.style.display = 'block';
|
||||
|
||||
// Show user-friendly error messages
|
||||
if (error.message.includes('429')) {
|
||||
aiErrorMessage.textContent = 'Zu viele Anfragen. Bitte warten Sie einen Moment und versuchen Sie es erneut.';
|
||||
} else if (error.message.includes('401')) {
|
||||
@@ -413,7 +376,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
aiErrorMessage.textContent = `Fehler: ${error.message}`;
|
||||
}
|
||||
} finally {
|
||||
// Re-enable submit button and hide queue status
|
||||
aiSubmitBtn.disabled = false;
|
||||
const config = modeConfig[currentMode];
|
||||
submitBtnText.textContent = config.submitText;
|
||||
@@ -423,7 +385,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Event listeners
|
||||
aiSubmitBtn.addEventListener('click', handleSubmit);
|
||||
|
||||
aiInput.addEventListener('keydown', (e) => {
|
||||
@@ -433,7 +394,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Function to restore AI results when switching back to AI view
|
||||
window.restoreAIResults = () => {
|
||||
if (currentRecommendation) {
|
||||
aiResults.style.display = 'block';
|
||||
@@ -442,7 +402,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to format workflow suggestions as proper lists
|
||||
function formatWorkflowSuggestion(text) {
|
||||
const numberedListPattern = /(\d+\.\s)/g;
|
||||
|
||||
@@ -485,7 +444,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return `<p style="margin: 0; line-height: 1.6; color: var(--color-text);">${text}</p>`;
|
||||
}
|
||||
|
||||
// NEW: Function to render background knowledge concepts
|
||||
function renderBackgroundKnowledge(backgroundKnowledge) {
|
||||
if (!backgroundKnowledge || backgroundKnowledge.length === 0) {
|
||||
return '';
|
||||
@@ -523,9 +481,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
`;
|
||||
}
|
||||
|
||||
// Display results for workflow mode (ENHANCED with concepts)
|
||||
function displayWorkflowResults(recommendation, originalQuery) {
|
||||
// Group tools by phase
|
||||
const toolsByPhase = {};
|
||||
|
||||
const phaseOrder = phases.map(phase => phase.id);
|
||||
@@ -534,12 +490,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Initialize phases
|
||||
phaseOrder.forEach(phase => {
|
||||
toolsByPhase[phase] = [];
|
||||
});
|
||||
|
||||
// Group recommended tools by phase
|
||||
recommendation.recommended_tools?.forEach(recTool => {
|
||||
if (toolsByPhase[recTool.phase]) {
|
||||
const fullTool = tools.find(t => t.name === recTool.name);
|
||||
@@ -682,7 +636,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
aiResults.appendChild(tempDiv);
|
||||
}
|
||||
|
||||
// Display results for tool-specific mode (ENHANCED with concepts)
|
||||
function displayToolResults(recommendation, originalQuery) {
|
||||
function getSuitabilityText(score) {
|
||||
const suitabilityTexts = {
|
||||
@@ -693,7 +646,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return suitabilityTexts[score] || 'GEEIGNET';
|
||||
}
|
||||
|
||||
// Helper function to get phase names for a tool
|
||||
function getToolPhases(tool) {
|
||||
if (!tool.phases || tool.phases.length === 0) return '';
|
||||
|
||||
@@ -900,7 +852,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
aiResults.appendChild(tempDiv);
|
||||
}
|
||||
|
||||
// Initialize UI
|
||||
updateModeUI();
|
||||
});
|
||||
</script>
|
||||
@@ -18,7 +18,6 @@ const {
|
||||
style = ''
|
||||
} = Astro.props;
|
||||
|
||||
// Generate appropriate URLs and text based on type
|
||||
let href: string;
|
||||
let defaultText: string;
|
||||
let icon: string;
|
||||
|
||||
@@ -17,27 +17,27 @@ const currentPath = Astro.url.pathname;
|
||||
<ul class="nav-links">
|
||||
<li>
|
||||
<a href="/" class={`nav-link ${currentPath === '/' ? 'active' : ''}`}>
|
||||
~/
|
||||
Start
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/knowledgebase" class={`nav-link ${currentPath === '/knowledgebase' ? 'active' : ''}`}>
|
||||
~/knowledgebase
|
||||
Knowledgebase
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/contribute" class={`nav-link ${currentPath.startsWith('/contribute') ? 'active' : ''}`}>
|
||||
~/contribute
|
||||
Beitragen
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/status" class={`nav-link ${currentPath === '/status' ? 'active' : ''}`}>
|
||||
~/status
|
||||
Status
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about" class={`nav-link ${currentPath === '/about' ? 'active' : ''}`}>
|
||||
~/about
|
||||
Über das Projekt
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -9,7 +9,6 @@ export interface Props {
|
||||
|
||||
const { toolName, context, size = 'small' } = Astro.props;
|
||||
|
||||
// AFTER: Single line with centralized function
|
||||
const toolSlug = createToolSlug(toolName);
|
||||
|
||||
const iconSize = size === 'small' ? '14' : '16';
|
||||
|
||||
@@ -24,26 +24,21 @@ export interface Props {
|
||||
|
||||
const { tool } = Astro.props;
|
||||
|
||||
// Check types
|
||||
const isMethod = tool.type === 'method';
|
||||
const isConcept = tool.type === 'concept';
|
||||
|
||||
// Check if tool has a valid project URL (means we're hosting it)
|
||||
const hasValidProjectUrl = tool.projectUrl !== undefined &&
|
||||
tool.projectUrl !== null &&
|
||||
tool.projectUrl !== "" &&
|
||||
tool.projectUrl.trim() !== "";
|
||||
|
||||
// Check if tool has knowledgebase entry
|
||||
const hasKnowledgebase = tool.knowledgebase === true;
|
||||
|
||||
// Determine card styling based on type and hosting status
|
||||
const cardClass = isConcept ? 'card card-concept tool-card cursor-pointer' :
|
||||
isMethod ? 'card card-method tool-card cursor-pointer' :
|
||||
hasValidProjectUrl ? 'card card-hosted tool-card cursor-pointer' :
|
||||
(tool.license !== 'Proprietary' ? 'card card-oss tool-card cursor-pointer' : 'card tool-card cursor-pointer');
|
||||
|
||||
// ENHANCED: Data attributes for filtering
|
||||
const toolDataAttributes = {
|
||||
'data-tool-name': tool.name.toLowerCase(),
|
||||
'data-tool-type': tool.type,
|
||||
@@ -62,26 +57,22 @@ const toolDataAttributes = {
|
||||
{...toolDataAttributes}
|
||||
onclick={`window.showToolDetails('${tool.name}')`}
|
||||
>
|
||||
<!-- Card Header with Fixed Height -->
|
||||
<div class="tool-card-header">
|
||||
<h3>
|
||||
{tool.icon && <span class="mr-2 text-lg">{tool.icon}</span>}
|
||||
{tool.name}
|
||||
</h3>
|
||||
<div class="tool-card-badges">
|
||||
<!-- Only show CC24-Server and Knowledgebase badges -->
|
||||
{!isMethod && hasValidProjectUrl && <span class="badge badge-primary">CC24-Server</span>}
|
||||
{hasKnowledgebase && <span class="badge badge-error">📖</span>}
|
||||
<ShareButton toolName={tool.name} context="card" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description - Truncated to 2 lines -->
|
||||
<p class="text-muted">
|
||||
{tool.description}
|
||||
</p>
|
||||
|
||||
<!-- Metadata - Compact Icons with Better Alignment -->
|
||||
<div class="tool-card-metadata flex items-center gap-4 mb-3" style="line-height: 1;">
|
||||
<div class="metadata-item flex items-center gap-2 text-xs text-secondary flex-shrink-1 min-w-0">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="flex-shrink-0">
|
||||
@@ -115,14 +106,12 @@ const toolDataAttributes = {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tags - Two Lines with Fade -->
|
||||
<div class="tool-tags-container">
|
||||
{tool.tags.slice(0, 8).map(tag => (
|
||||
<span class="tag">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<!-- Buttons - Fixed at Bottom (NO EDIT BUTTONS - Available in modals) -->
|
||||
<div class="tool-card-buttons" onclick="event.stopPropagation();">
|
||||
{isConcept ? (
|
||||
<a href={tool.url} target="_blank" rel="noopener noreferrer" class="btn btn-primary single-button" style="background-color: var(--color-concept); border-color: var(--color-concept);">
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
---
|
||||
import { getToolsData } from '../utils/dataService.js';
|
||||
|
||||
// Load tools data
|
||||
const data = await getToolsData();
|
||||
|
||||
const domains = data.domains;
|
||||
const phases = data.phases;
|
||||
|
||||
// Get unique tags from all tools with frequency count
|
||||
const tagFrequency = data.tools.reduce((acc: Record<string, number>, tool: any) => {
|
||||
tool.tags.forEach((tag: string) => {
|
||||
acc[tag] = (acc[tag] || 0) + 1;
|
||||
@@ -15,14 +13,12 @@ const tagFrequency = data.tools.reduce((acc: Record<string, number>, tool: any)
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Sort tags by frequency (descending)
|
||||
const sortedTags = Object.entries(tagFrequency)
|
||||
.sort(([,a], [,b]) => (b as number) - (a as number))
|
||||
.map(([tag]) => tag);
|
||||
---
|
||||
|
||||
<div class="filters-container">
|
||||
<!-- Search Bar -->
|
||||
<div style="margin-bottom: 1.5rem;">
|
||||
<input
|
||||
type="text"
|
||||
@@ -32,9 +28,7 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Domain Dropdown and Phase Buttons -->
|
||||
<div class="domain-phase-container" style="margin-bottom: 1.5rem;">
|
||||
<!-- Domain Selection -->
|
||||
<div class="domain-section">
|
||||
<label for="domain-select" style="display: block; margin-bottom: 0.5rem; font-weight: 500;">
|
||||
Forensische Domäne
|
||||
@@ -47,7 +41,6 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Phase Selection Buttons -->
|
||||
<div class="phase-section">
|
||||
<label style="display: block; margin-bottom: 0.75rem; font-weight: 500;">
|
||||
Untersuchungsphase
|
||||
@@ -66,14 +59,12 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Filters -->
|
||||
<div style="margin-bottom: 1.5rem;">
|
||||
<div class="checkbox-wrapper" style="margin-bottom: 1rem;">
|
||||
<input type="checkbox" id="include-proprietary" checked />
|
||||
<label for="include-proprietary">Proprietäre Software mit einschließen</label>
|
||||
</div>
|
||||
|
||||
<!-- Tag Cloud -->
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<div class="tag-header">
|
||||
<label style="font-weight: 500;">
|
||||
@@ -103,12 +94,10 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View Toggle -->
|
||||
<div style="display: flex; gap: 1rem; margin-bottom: 1.5rem; align-items: center;">
|
||||
<button class="btn btn-secondary view-toggle active" style="height:50px" data-view="grid">Kachelansicht</button>
|
||||
<button class="btn btn-secondary view-toggle" style="height:50px" data-view="matrix">Matrix-Ansicht</button>
|
||||
|
||||
<!-- AI Recommendations Button (only visible when authenticated) -->
|
||||
<button
|
||||
id="ai-view-toggle"
|
||||
class="btn btn-secondary view-toggle"
|
||||
@@ -125,10 +114,8 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
</div>
|
||||
|
||||
<script define:vars={{ toolsData: data.tools, tagFrequency, sortedTags }}>
|
||||
// Store tools data globally for filtering
|
||||
window.toolsData = toolsData;
|
||||
|
||||
// Initialize filters
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const domainSelect = document.getElementById('domain-select');
|
||||
@@ -140,12 +127,10 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
const viewToggles = document.querySelectorAll('.view-toggle');
|
||||
const aiViewToggle = document.getElementById('ai-view-toggle');
|
||||
|
||||
// Track selected tags and phase
|
||||
let selectedTags = new Set();
|
||||
let selectedPhase = '';
|
||||
let isTagCloudExpanded = false;
|
||||
|
||||
// Initialize tag cloud state
|
||||
function initTagCloud() {
|
||||
const visibleCount = 22;
|
||||
tagCloudItems.forEach((item, index) => {
|
||||
@@ -155,7 +140,6 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle tag cloud expansion
|
||||
function toggleTagCloud() {
|
||||
isTagCloudExpanded = !isTagCloudExpanded;
|
||||
const visibleCount = 22;
|
||||
@@ -189,7 +173,6 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter tag cloud based on search input
|
||||
function filterTagCloud() {
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
let visibleCount = 0;
|
||||
@@ -219,7 +202,6 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
tagCloudToggle.style.display = hasHiddenTags ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Check if tool is hosted (has valid projectUrl)
|
||||
function isToolHosted(tool) {
|
||||
return tool.projectUrl !== undefined &&
|
||||
tool.projectUrl !== null &&
|
||||
@@ -227,12 +209,10 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
tool.projectUrl.trim() !== "";
|
||||
}
|
||||
|
||||
// Check if item is a method
|
||||
function isMethod(tool) {
|
||||
return tool.type === 'method';
|
||||
}
|
||||
|
||||
// Update matrix highlighting based on current filters
|
||||
function updateMatrixHighlighting() {
|
||||
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
|
||||
if (currentView !== 'matrix') return;
|
||||
@@ -240,14 +220,12 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
const matrixTable = document.querySelector('.matrix-table');
|
||||
if (!matrixTable) return;
|
||||
|
||||
// Clear existing highlights
|
||||
matrixTable.querySelectorAll('.highlight-row, .highlight-column').forEach(el => {
|
||||
el.classList.remove('highlight-row', 'highlight-column');
|
||||
});
|
||||
|
||||
const selectedDomain = domainSelect.value;
|
||||
|
||||
// Highlight selected domain row
|
||||
if (selectedDomain) {
|
||||
const domainRow = matrixTable.querySelector(`tr[data-domain="${selectedDomain}"]`);
|
||||
if (domainRow) {
|
||||
@@ -255,7 +233,6 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight selected phase column
|
||||
if (selectedPhase) {
|
||||
const phaseHeaders = matrixTable.querySelectorAll('thead th[data-phase]');
|
||||
const phaseIndex = Array.from(phaseHeaders).findIndex(th =>
|
||||
@@ -263,7 +240,7 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
);
|
||||
|
||||
if (phaseIndex >= 0) {
|
||||
const columnIndex = phaseIndex + 1; // +1 because first column is domain names
|
||||
const columnIndex = phaseIndex + 1;
|
||||
matrixTable.querySelectorAll(`tr`).forEach(row => {
|
||||
const cell = row.children[columnIndex];
|
||||
if (cell) {
|
||||
@@ -274,19 +251,16 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
}
|
||||
}
|
||||
|
||||
// ENHANCED: Filter function with better performance for show/hide pattern
|
||||
function filterTools() {
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
const selectedDomain = domainSelect.value;
|
||||
const includeProprietary = proprietaryCheckbox.checked;
|
||||
|
||||
const filtered = window.toolsData.filter(tool => {
|
||||
// Ensure arrays exist with fallbacks
|
||||
const domains = tool.domains || [];
|
||||
const phases = tool.phases || [];
|
||||
const tags = tool.tags || [];
|
||||
|
||||
// Search filter - more comprehensive
|
||||
if (searchTerm && !(
|
||||
tool.name.toLowerCase().includes(searchTerm) ||
|
||||
tool.description.toLowerCase().includes(searchTerm) ||
|
||||
@@ -295,22 +269,18 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Domain filter
|
||||
if (selectedDomain && !domains.includes(selectedDomain)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Phase filter
|
||||
if (selectedPhase && !phases.includes(selectedPhase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Proprietary filter (skip for methods and concepts since they don't have licenses)
|
||||
if (!includeProprietary && !isMethod(tool) && tool.type !== 'concept' && tool.license === 'Proprietary') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tag filter - ensure all selected tags are present
|
||||
if (selectedTags.size > 0 && !Array.from(selectedTags).every(tag => tags.includes(tag))) {
|
||||
return false;
|
||||
}
|
||||
@@ -318,14 +288,11 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
return true;
|
||||
});
|
||||
|
||||
// Update matrix highlighting
|
||||
updateMatrixHighlighting();
|
||||
|
||||
// Emit custom event with filtered results
|
||||
window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: filtered }));
|
||||
}
|
||||
|
||||
// Handle tag cloud clicks
|
||||
function handleTagClick(tagItem) {
|
||||
const tag = tagItem.getAttribute('data-tag');
|
||||
|
||||
@@ -340,7 +307,6 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
filterTools();
|
||||
}
|
||||
|
||||
// Handle phase button clicks
|
||||
function handlePhaseClick(button) {
|
||||
const phase = button.getAttribute('data-phase');
|
||||
|
||||
@@ -356,7 +322,6 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
filterTools();
|
||||
}
|
||||
|
||||
// View toggle handler
|
||||
function handleViewToggle(view) {
|
||||
viewToggles.forEach(btn => {
|
||||
btn.classList.toggle('active', btn.getAttribute('data-view') === view);
|
||||
@@ -372,14 +337,12 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all tag filters function
|
||||
function clearTagFilters() {
|
||||
selectedTags.clear();
|
||||
tagCloudItems.forEach(item => item.classList.remove('active'));
|
||||
filterTools();
|
||||
}
|
||||
|
||||
// Clear all filters function
|
||||
function clearAllFilters() {
|
||||
searchInput.value = '';
|
||||
domainSelect.value = '';
|
||||
@@ -389,7 +352,6 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
filterTagCloud();
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
searchInput.addEventListener('input', () => {
|
||||
filterTagCloud();
|
||||
filterTools();
|
||||
@@ -411,15 +373,12 @@ const sortedTags = Object.entries(tagFrequency)
|
||||
btn.addEventListener('click', () => handleViewToggle(btn.getAttribute('data-view')));
|
||||
});
|
||||
|
||||
// Expose functions globally for potential use
|
||||
window.clearTagFilters = clearTagFilters;
|
||||
window.clearAllFilters = clearAllFilters;
|
||||
|
||||
// Initialize
|
||||
initTagCloud();
|
||||
filterTagCloud();
|
||||
|
||||
// Delay initial filter to ensure all event listeners are ready
|
||||
setTimeout(() => {
|
||||
filterTools();
|
||||
}, 100);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { getToolsData } from '../utils/dataService.js';
|
||||
import ShareButton from './ShareButton.astro';
|
||||
|
||||
// Load tools data
|
||||
const data = await getToolsData();
|
||||
|
||||
const domains = data.domains;
|
||||
@@ -10,7 +9,6 @@ const phases = data.phases;
|
||||
const tools = data.tools;
|
||||
const domainAgnosticSoftware = data['domain-agnostic-software'] || [];
|
||||
|
||||
// Get tools for each domain-agnostic section based on the tool's domain-agnostic-software field
|
||||
const domainAgnosticTools = domainAgnosticSoftware.map((section: any) => ({
|
||||
section,
|
||||
tools: tools.filter((tool: any) =>
|
||||
@@ -18,13 +16,12 @@ const domainAgnosticTools = domainAgnosticSoftware.map((section: any) => ({
|
||||
)
|
||||
}));
|
||||
|
||||
// Matrix shows ALL tools based on domains × phases (independent of domain-agnostic-software)
|
||||
const matrix: Record<string, Record<string, any[]>> = {};
|
||||
domains.forEach((domain: any) => {
|
||||
matrix[domain.id] = {};
|
||||
phases.forEach((phase: any) => {
|
||||
matrix[domain.id][phase.id] = tools.filter((tool: any) =>
|
||||
tool.type !== 'concept' && // Exclude concepts from matrix
|
||||
tool.type !== 'concept' &&
|
||||
tool.domains && tool.domains.includes(domain.id) &&
|
||||
tool.phases && tool.phases.includes(phase.id)
|
||||
);
|
||||
@@ -33,7 +30,6 @@ domains.forEach((domain: any) => {
|
||||
---
|
||||
|
||||
<div id="matrix-container" class="matrix-wrapper" style="display: none;">
|
||||
<!-- Domain-Agnostic Software Sections -->
|
||||
{domainAgnosticTools.map((sectionData: any, index: number) => (
|
||||
<div id={`domain-agnostic-section-${sectionData.section.id}`} class="card collaboration-section-collapsed mb-6 border-l-4" style="border-left-color: var(--color-accent);">
|
||||
<div class="collaboration-header cursor-pointer flex items-center gap-3" onclick={`toggleDomainAgnosticSection('${sectionData.section.id}')`} style="margin-bottom: 0.1rem;">
|
||||
@@ -91,7 +87,6 @@ domains.forEach((domain: any) => {
|
||||
</div>
|
||||
))}
|
||||
|
||||
<!-- DFIR Tools Matrix -->
|
||||
<div id="dfir-matrix-section">
|
||||
<h2 class="mb-4" style="color: var(--color-text);">MATRIX</h2>
|
||||
<table class="matrix-table">
|
||||
@@ -139,19 +134,15 @@ domains.forEach((domain: any) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tool Details Modals - Dual Modal System -->
|
||||
<div class="modal-overlay" id="modal-overlay" onclick="window.hideAllToolDetails()"></div>
|
||||
|
||||
<!-- Primary Modal -->
|
||||
<div class="tool-details" id="tool-details-primary">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<h2 id="tool-name-primary" class="m-0">Tool Name</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<div id="share-button-primary" style="display: none;">
|
||||
<!-- Share button will be populated by JavaScript -->
|
||||
</div>
|
||||
<div id="contribute-button-primary" style="display: none;">
|
||||
<!-- Contribution button will be populated by JavaScript -->
|
||||
</div>
|
||||
<button class="btn-icon" onclick="window.hideToolDetails('primary')">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -173,16 +164,13 @@ domains.forEach((domain: any) => {
|
||||
<div id="tool-links-primary" class="flex flex-col gap-2"></div>
|
||||
</div>
|
||||
|
||||
<!-- Secondary Modal -->
|
||||
<div class="tool-details" id="tool-details-secondary">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<h2 id="tool-name-secondary" class="m-0">Tool Name</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<div id="share-button-secondary" style="display: none;">
|
||||
<!-- Share button will be populated by JavaScript -->
|
||||
</div>
|
||||
<div id="contribute-button-secondary" style="display: none;">
|
||||
<!-- Contribution button will be populated by JavaScript -->
|
||||
</div>
|
||||
<button class="btn-icon" onclick="window.hideToolDetails('secondary')">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -205,24 +193,20 @@ domains.forEach((domain: any) => {
|
||||
</div>
|
||||
|
||||
<script define:vars={{ toolsData: tools, domainAgnosticSoftware, domainAgnosticTools }}>
|
||||
// Helper function to get selected phase from active button
|
||||
function getSelectedPhase() {
|
||||
const activePhaseButton = document.querySelector('.phase-button.active');
|
||||
return activePhaseButton ? activePhaseButton.getAttribute('data-phase') : '';
|
||||
}
|
||||
|
||||
// Helper function to get selected domain from dropdown
|
||||
function getSelectedDomain() {
|
||||
const domainSelect = document.getElementById('domain-select');
|
||||
return domainSelect ? domainSelect.value : '';
|
||||
}
|
||||
|
||||
// Update matrix highlighting based on current filters
|
||||
function updateMatrixHighlighting() {
|
||||
const matrixTable = document.querySelector('.matrix-table');
|
||||
if (!matrixTable) return;
|
||||
|
||||
// Clear existing highlights
|
||||
matrixTable.querySelectorAll('.highlight-row, .highlight-column').forEach(el => {
|
||||
el.classList.remove('highlight-row', 'highlight-column');
|
||||
});
|
||||
@@ -230,7 +214,6 @@ domains.forEach((domain: any) => {
|
||||
const selectedDomain = getSelectedDomain();
|
||||
const selectedPhase = getSelectedPhase();
|
||||
|
||||
// Highlight selected domain row
|
||||
if (selectedDomain) {
|
||||
const domainRow = matrixTable.querySelector(`tr[data-domain="${selectedDomain}"]`);
|
||||
if (domainRow) {
|
||||
@@ -238,7 +221,6 @@ domains.forEach((domain: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight selected phase column
|
||||
if (selectedPhase) {
|
||||
const phaseHeaders = matrixTable.querySelectorAll('thead th[data-phase]');
|
||||
const phaseIndex = Array.from(phaseHeaders).findIndex(th =>
|
||||
@@ -246,7 +228,7 @@ domains.forEach((domain: any) => {
|
||||
);
|
||||
|
||||
if (phaseIndex >= 0) {
|
||||
const columnIndex = phaseIndex + 1; // +1 because first column is domain names
|
||||
const columnIndex = phaseIndex + 1;
|
||||
matrixTable.querySelectorAll(`tr`).forEach(row => {
|
||||
const cell = row.children[columnIndex];
|
||||
if (cell) {
|
||||
@@ -257,7 +239,6 @@ domains.forEach((domain: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle domain-agnostic section
|
||||
function toggleDomainAgnosticSection(sectionId) {
|
||||
const section = document.getElementById(`domain-agnostic-section-${sectionId}`);
|
||||
const content = section?.querySelector('.collaboration-content');
|
||||
@@ -268,13 +249,11 @@ domains.forEach((domain: any) => {
|
||||
const isExpanded = section.classList.contains('collaboration-section-expanded');
|
||||
|
||||
if (isExpanded) {
|
||||
// Collapse
|
||||
section.classList.remove('collaboration-section-expanded');
|
||||
section.classList.add('collaboration-section-collapsed');
|
||||
content.style.display = 'none';
|
||||
icon.style.transform = 'rotate(0deg)';
|
||||
} else {
|
||||
// Expand
|
||||
section.classList.remove('collaboration-section-collapsed');
|
||||
section.classList.add('collaboration-section-expanded');
|
||||
content.style.display = 'block';
|
||||
@@ -282,7 +261,6 @@ domains.forEach((domain: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate share URLs
|
||||
function generateShareURL(toolName, view, modal = null) {
|
||||
const toolSlug = window.createToolSlug(toolName);
|
||||
const baseUrl = window.location.origin + window.location.pathname;
|
||||
@@ -295,12 +273,10 @@ domains.forEach((domain: any) => {
|
||||
return `${baseUrl}?${params.toString()}`;
|
||||
}
|
||||
|
||||
// Copy to clipboard with feedback
|
||||
async function copyToClipboard(text, button) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
|
||||
// Show feedback
|
||||
const originalHTML = button.innerHTML;
|
||||
button.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20,6 9,17 4,12"/></svg> Kopiert!';
|
||||
button.style.color = 'var(--color-accent)';
|
||||
@@ -310,7 +286,6 @@ domains.forEach((domain: any) => {
|
||||
button.style.color = '';
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
// Fallback for older browsers
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
@@ -318,7 +293,6 @@ domains.forEach((domain: any) => {
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
|
||||
// Show feedback
|
||||
const originalHTML = button.innerHTML;
|
||||
button.innerHTML = 'Kopiert!';
|
||||
setTimeout(() => {
|
||||
@@ -327,12 +301,10 @@ domains.forEach((domain: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Show share dialog
|
||||
window.showShareDialog = function(shareButton) {
|
||||
const toolName = shareButton.getAttribute('data-tool-name');
|
||||
const context = shareButton.getAttribute('data-context');
|
||||
|
||||
// Create modal backdrop
|
||||
let backdrop = document.getElementById('share-modal-backdrop');
|
||||
if (!backdrop) {
|
||||
backdrop = document.createElement('div');
|
||||
@@ -354,7 +326,6 @@ domains.forEach((domain: any) => {
|
||||
document.body.appendChild(backdrop);
|
||||
}
|
||||
|
||||
// Create share dialog
|
||||
const dialog = document.createElement('div');
|
||||
dialog.style.cssText = `
|
||||
background-color: var(--color-bg);
|
||||
@@ -440,13 +411,11 @@ domains.forEach((domain: any) => {
|
||||
|
||||
backdrop.appendChild(dialog);
|
||||
|
||||
// Show with animation
|
||||
requestAnimationFrame(() => {
|
||||
backdrop.style.opacity = '1';
|
||||
dialog.style.transform = 'scale(1)';
|
||||
});
|
||||
|
||||
// Event handlers
|
||||
const closeDialog = () => {
|
||||
backdrop.style.opacity = '0';
|
||||
dialog.style.transform = 'scale(0.9)';
|
||||
@@ -463,7 +432,6 @@ domains.forEach((domain: any) => {
|
||||
|
||||
document.getElementById('close-share-dialog').addEventListener('click', closeDialog);
|
||||
|
||||
// Share option handlers
|
||||
dialog.querySelectorAll('.share-option-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const url = btn.getAttribute('data-url');
|
||||
@@ -472,10 +440,8 @@ domains.forEach((domain: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
// Make functions globally available
|
||||
window.toggleDomainAgnosticSection = toggleDomainAgnosticSection;
|
||||
|
||||
// Enhanced modal system for side-by-side display
|
||||
window.showToolDetails = function(toolName, modalType = 'primary') {
|
||||
const tool = toolsData.find(t => t.name === toolName);
|
||||
if (!tool) {
|
||||
@@ -486,7 +452,6 @@ domains.forEach((domain: any) => {
|
||||
const isMethod = tool.type === 'method';
|
||||
const isConcept = tool.type === 'concept';
|
||||
|
||||
// Get modal-specific element IDs
|
||||
const elements = {
|
||||
name: document.getElementById(`tool-name-${modalType}`),
|
||||
description: document.getElementById(`tool-description-${modalType}`),
|
||||
@@ -496,7 +461,6 @@ domains.forEach((domain: any) => {
|
||||
links: document.getElementById(`tool-links-${modalType}`)
|
||||
};
|
||||
|
||||
// Check if all elements exist
|
||||
for (const [key, element] of Object.entries(elements)) {
|
||||
if (!element) {
|
||||
console.error(`Element not found: tool-${key}-${modalType}`);
|
||||
@@ -504,12 +468,10 @@ domains.forEach((domain: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Update modal content
|
||||
const iconHtml = tool.icon ? `<span class="mr-3 text-xl">${tool.icon}</span>` : '';
|
||||
elements.name.innerHTML = `${iconHtml}${tool.name}`;
|
||||
elements.description.textContent = tool.description;
|
||||
|
||||
// Badges
|
||||
const hasValidProjectUrl = window.isToolHosted(tool);
|
||||
|
||||
elements.badges.innerHTML = '';
|
||||
@@ -525,7 +487,6 @@ domains.forEach((domain: any) => {
|
||||
elements.badges.innerHTML += '<span class="badge badge-error">📖</span>';
|
||||
}
|
||||
|
||||
// Metadata
|
||||
const domains = tool.domains || [];
|
||||
const phases = tool.phases || [];
|
||||
const domainsText = domains.length > 0 ? domains.join(', ') : 'Domain-agnostic';
|
||||
@@ -551,7 +512,6 @@ domains.forEach((domain: any) => {
|
||||
|
||||
elements.metadata.innerHTML = metadataHTML;
|
||||
|
||||
// Tags and Related Concepts
|
||||
const tags = tool.tags || [];
|
||||
let tagsHTML = `
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@@ -559,7 +519,6 @@ domains.forEach((domain: any) => {
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Related Concepts section - only show in primary modal to avoid infinite loops
|
||||
const relatedConcepts = tool.related_concepts || [];
|
||||
if (relatedConcepts.length > 0 && modalType === 'primary') {
|
||||
const conceptLinks = relatedConcepts.map(conceptName => {
|
||||
@@ -575,7 +534,6 @@ domains.forEach((domain: any) => {
|
||||
return `<span class="tag" style="background-color: var(--color-bg-tertiary); color: var(--color-text-secondary);">${conceptName}</span>`;
|
||||
}).join('');
|
||||
|
||||
// Check if mobile device
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
const collapseOnMobile = isMobile && relatedConcepts.length > 2;
|
||||
|
||||
@@ -600,7 +558,6 @@ domains.forEach((domain: any) => {
|
||||
|
||||
elements.tags.innerHTML = tagsHTML;
|
||||
|
||||
// Links
|
||||
let linksHTML = '';
|
||||
|
||||
if (isConcept) {
|
||||
@@ -652,7 +609,6 @@ domains.forEach((domain: any) => {
|
||||
|
||||
elements.links.innerHTML = linksHTML;
|
||||
|
||||
// ===== POPULATE SHARE BUTTON =====
|
||||
const shareButtonContainer = document.getElementById(`share-button-${modalType}`);
|
||||
if (shareButtonContainer) {
|
||||
const toolSlug = window.createToolSlug(tool.name);
|
||||
@@ -676,7 +632,6 @@ domains.forEach((domain: any) => {
|
||||
shareButtonContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
// ===== POPULATE CONTRIBUTION BUTTON =====
|
||||
const contributeButtonContainer = document.getElementById(`contribute-button-${modalType}`);
|
||||
if (contributeButtonContainer) {
|
||||
contributeButtonContainer.innerHTML = `
|
||||
@@ -696,7 +651,6 @@ domains.forEach((domain: any) => {
|
||||
contributeButtonContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
// Show modals and update layout
|
||||
const overlay = document.getElementById('modal-overlay');
|
||||
const primaryModal = document.getElementById('tool-details-primary');
|
||||
const secondaryModal = document.getElementById('tool-details-secondary');
|
||||
@@ -705,7 +659,6 @@ domains.forEach((domain: any) => {
|
||||
if (modalType === 'primary' && primaryModal) primaryModal.classList.add('active');
|
||||
if (modalType === 'secondary' && secondaryModal) secondaryModal.classList.add('active');
|
||||
|
||||
// Check if both modals are now active
|
||||
const primaryActive = primaryModal && primaryModal.classList.contains('active');
|
||||
const secondaryActive = secondaryModal && secondaryModal.classList.contains('active');
|
||||
|
||||
@@ -749,7 +702,6 @@ domains.forEach((domain: any) => {
|
||||
if (contributeButtonSecondary) contributeButtonSecondary.style.display = 'none';
|
||||
}
|
||||
|
||||
// Check if any modal is still active
|
||||
const primaryActive = primaryModal && primaryModal.classList.contains('active');
|
||||
const secondaryActive = secondaryModal && secondaryModal.classList.contains('active');
|
||||
|
||||
@@ -757,7 +709,6 @@ domains.forEach((domain: any) => {
|
||||
if (overlay) overlay.classList.remove('active');
|
||||
document.body.classList.remove('modals-side-by-side');
|
||||
} else if (primaryActive !== secondaryActive) {
|
||||
// Only one modal left - remove side-by-side layout
|
||||
document.body.classList.remove('modals-side-by-side');
|
||||
}
|
||||
};
|
||||
@@ -766,7 +717,6 @@ domains.forEach((domain: any) => {
|
||||
window.hideToolDetails('both');
|
||||
};
|
||||
|
||||
// Keep existing event listeners
|
||||
window.addEventListener('viewChanged', (event) => {
|
||||
const view = event.detail;
|
||||
if (view === 'matrix') {
|
||||
|
||||
Reference in New Issue
Block a user