Merge branch 'embeddings-1' of https://git.cc24.dev/HSMW_CC24/forensic-pathways into embeddings-1

This commit is contained in:
overcuriousity 2025-08-02 11:54:47 +02:00
commit d3c4d7ccc4
2 changed files with 295 additions and 102 deletions

View File

@ -91,119 +91,137 @@ const sortedTags = Object.entries(tagFrequency)
</div>
</div>
<!-- Advanced Filters Section -->
<!-- Advanced Filters Section - COLLAPSIBLE -->
<div class="filter-section">
<div class="filter-card-compact">
<div class="filter-header-compact">
<h3>⚙️ Erweiterte Filter</h3>
<button class="filter-reset" id="reset-advanced" title="Erweiterte Filter zurücksetzen">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="1 4 1 10 7 10"/>
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
</svg>
</button>
<div class="filter-header-controls">
<button class="filter-reset" id="reset-advanced" title="Erweiterte Filter zurücksetzen">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="1 4 1 10 7 10"/>
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
</svg>
</button>
<button class="collapse-toggle" id="toggle-advanced" data-collapsed="true" title="Erweiterte Filter ein/ausblenden">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
</div>
</div>
<div class="advanced-filters-compact">
<div class="filter-grid-compact">
<div class="filter-group">
<label class="filter-label">Tool-Typ</label>
<select id="type-select" class="filter-select">
<option value="">Alle Typen</option>
{toolTypes.map((type: string) => (
<option value={type}>{type}</option>
))}
</select>
<div class="collapsible-content hidden" id="advanced-filters-content">
<div class="advanced-filters-compact">
<div class="filter-grid-compact">
<div class="filter-group">
<label class="filter-label">Tool-Typ</label>
<select id="type-select" class="filter-select">
<option value="">Alle Typen</option>
{toolTypes.map((type: string) => (
<option value={type}>{type}</option>
))}
</select>
</div>
<div class="filter-group">
<label class="filter-label">Skill Level</label>
<select id="skill-select" class="filter-select">
<option value="">Alle Level</option>
{skillLevels.map((level: string) => (
<option value={level}>{level}</option>
))}
</select>
</div>
<div class="filter-group">
<label class="filter-label">Plattform</label>
<select id="platform-select" class="filter-select">
<option value="">Alle Plattformen</option>
{platforms.map((platform: string) => (
<option value={platform}>{platform}</option>
))}
</select>
</div>
<div class="filter-group">
<label class="filter-label">Lizenztyp</label>
<select id="license-select" class="filter-select">
<option value="">Alle Lizenzen</option>
{licenses.map((license: string) => (
<option value={license}>{license}</option>
))}
</select>
</div>
<div class="filter-group">
<label class="filter-label">Zugangsart</label>
<select id="access-select" class="filter-select">
<option value="">Alle Zugangsarten</option>
{accessTypes.map((access: string) => (
<option value={access}>{access}</option>
))}
</select>
</div>
</div>
<div class="filter-group">
<label class="filter-label">Skill Level</label>
<select id="skill-select" class="filter-select">
<option value="">Alle Level</option>
{skillLevels.map((level: string) => (
<option value={level}>{level}</option>
))}
</select>
<div class="filter-toggles-compact">
<label class="toggle-wrapper">
<input type="checkbox" id="hosted-only" />
<span class="toggle-label">🟣 Nur CC24-Server Tools</span>
</label>
<label class="toggle-wrapper">
<input type="checkbox" id="knowledgebase-only" />
<span class="toggle-label">📖 Nur Tools mit Knowledgebase</span>
</label>
</div>
<div class="filter-group">
<label class="filter-label">Plattform</label>
<select id="platform-select" class="filter-select">
<option value="">Alle Plattformen</option>
{platforms.map((platform: string) => (
<option value={platform}>{platform}</option>
))}
</select>
</div>
<div class="filter-group">
<label class="filter-label">Lizenztyp</label>
<select id="license-select" class="filter-select">
<option value="">Alle Lizenzen</option>
{licenses.map((license: string) => (
<option value={license}>{license}</option>
))}
</select>
</div>
<div class="filter-group">
<label class="filter-label">Zugangsart</label>
<select id="access-select" class="filter-select">
<option value="">Alle Zugangsarten</option>
{accessTypes.map((access: string) => (
<option value={access}>{access}</option>
))}
</select>
</div>
</div>
<div class="filter-toggles-compact">
<label class="toggle-wrapper">
<input type="checkbox" id="hosted-only" />
<span class="toggle-label">🟣 Nur CC24-Server Tools</span>
</label>
<label class="toggle-wrapper">
<input type="checkbox" id="knowledgebase-only" />
<span class="toggle-label">📖 Nur Tools mit Knowledgebase</span>
</label>
</div>
</div>
</div>
</div>
<!-- Tag Filters Section -->
<!-- Tag Filters Section - COLLAPSIBLE -->
<div class="filter-section">
<div class="filter-card-compact">
<div class="filter-header-compact">
<h3>🏷️ Tag-Filter</h3>
<div class="tag-controls">
<div class="filter-header-controls">
<button class="filter-reset" id="reset-tags" title="Tags zurücksetzen">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="1 4 1 10 7 10"/>
<path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
</svg>
</button>
<button id="tag-cloud-toggle" class="tag-toggle" data-expanded="false">
Mehr zeigen
<button class="collapse-toggle" id="toggle-tags" data-collapsed="true" title="Tag-Filter ein/ausblenden">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
</div>
</div>
<div class="tag-section">
<div class="selected-tags" id="selected-tags"></div>
<div class="tag-cloud" id="tag-cloud">
{sortedTags.map((tag, index) => (
<button
class="tag-cloud-item"
data-tag={tag}
data-frequency={tagFrequency[tag]}
data-index={index}
>
{tag}
<span class="tag-frequency">({tagFrequency[tag]})</span>
<div class="collapsible-content hidden" id="tag-filters-content">
<div class="tag-section">
<div class="selected-tags" id="selected-tags"></div>
<div class="tag-controls">
<button id="tag-cloud-toggle" class="tag-toggle" data-expanded="false">
Mehr zeigen
</button>
))}
</div>
<div class="tag-cloud" id="tag-cloud">
{sortedTags.map((tag, index) => (
<button
class="tag-cloud-item"
data-tag={tag}
data-frequency={tagFrequency[tag]}
data-index={index}
>
{tag}
<span class="tag-frequency">({tagFrequency[tag]})</span>
</button>
))}
</div>
</div>
</div>
</div>
@ -293,7 +311,12 @@ const sortedTags = Object.entries(tagFrequency)
advanced: document.getElementById('reset-advanced'),
tags: document.getElementById('reset-tags'),
all: document.getElementById('reset-all-filters')
}
},
// Collapsible elements
toggleAdvanced: document.getElementById('toggle-advanced'),
toggleTags: document.getElementById('toggle-tags'),
advancedContent: document.getElementById('advanced-filters-content'),
tagContent: document.getElementById('tag-filters-content')
};
// Verify critical elements exist
@ -307,6 +330,52 @@ const sortedTags = Object.entries(tagFrequency)
let selectedPhase = '';
let isTagCloudExpanded = false;
// Collapsible functionality
function toggleCollapsible(toggleBtn, content, storageKey) {
const isCollapsed = toggleBtn.getAttribute('data-collapsed') === 'true';
const newState = !isCollapsed;
toggleBtn.setAttribute('data-collapsed', newState.toString());
if (newState) {
// Collapse
content.classList.add('hidden');
toggleBtn.style.transform = 'rotate(0deg)';
} else {
// Expand
content.classList.remove('hidden');
toggleBtn.style.transform = 'rotate(180deg)';
}
// Store state in sessionStorage
sessionStorage.setItem(storageKey, newState.toString());
}
// Initialize collapsible sections (collapsed by default)
function initializeCollapsible() {
// Advanced filters
const advancedCollapsed = sessionStorage.getItem('advanced-collapsed') !== 'false';
elements.toggleAdvanced.setAttribute('data-collapsed', advancedCollapsed.toString());
if (advancedCollapsed) {
elements.advancedContent.classList.add('hidden');
elements.toggleAdvanced.style.transform = 'rotate(0deg)';
} else {
elements.advancedContent.classList.remove('hidden');
elements.toggleAdvanced.style.transform = 'rotate(180deg)';
}
// Tag filters
const tagsCollapsed = sessionStorage.getItem('tags-collapsed') !== 'false';
elements.toggleTags.setAttribute('data-collapsed', tagsCollapsed.toString());
if (tagsCollapsed) {
elements.tagContent.classList.add('hidden');
elements.toggleTags.style.transform = 'rotate(0deg)';
} else {
elements.tagContent.classList.remove('hidden');
elements.toggleTags.style.transform = 'rotate(180deg)';
}
}
// Helper function to check if tool is hosted
function isToolHosted(tool) {
return tool.projectUrl !== undefined &&
@ -418,18 +487,23 @@ const sortedTags = Object.entries(tagFrequency)
});
}
// Add/remove tags
// Add/remove tags - FIXED: Update ALL matching elements
function addTag(tag) {
selectedTags.add(tag);
document.querySelector(`[data-tag="${tag}"]`).classList.add('active');
// FIXED: Use querySelectorAll to update ALL matching tag elements
document.querySelectorAll(`[data-tag="${tag}"]`).forEach(element => {
element.classList.add('active');
});
updateSelectedTags();
filterTools();
}
function removeTag(tag) {
selectedTags.delete(tag);
const tagElement = document.querySelector(`[data-tag="${tag}"]`);
if (tagElement) tagElement.classList.remove('active');
// FIXED: Use querySelectorAll to update ALL matching tag elements
document.querySelectorAll(`[data-tag="${tag}"]`).forEach(element => {
element.classList.remove('active');
});
updateSelectedTags();
filterTools();
}
@ -553,7 +627,10 @@ const sortedTags = Object.entries(tagFrequency)
function resetTags() {
selectedTags.clear();
elements.tagCloudItems.forEach(item => item.classList.remove('active'));
// FIXED: Update ALL tag elements
document.querySelectorAll('.tag-cloud-item').forEach(item => {
item.classList.remove('active');
});
updateSelectedTags();
filterTools();
}
@ -630,11 +707,21 @@ const sortedTags = Object.entries(tagFrequency)
elements.resetButtons.tags.addEventListener('click', resetTags);
elements.resetButtons.all.addEventListener('click', resetAllFilters);
// Collapsible toggle listeners
elements.toggleAdvanced.addEventListener('click', () => {
toggleCollapsible(elements.toggleAdvanced, elements.advancedContent, 'advanced-collapsed');
});
elements.toggleTags.addEventListener('click', () => {
toggleCollapsible(elements.toggleTags, elements.tagContent, 'tags-collapsed');
});
// Expose functions globally for backwards compatibility
window.clearTagFilters = resetTags;
window.clearAllFilters = resetAllFilters;
// Initialize
initializeCollapsible();
initTagCloud();
filterTagCloud();
updateSelectedTags();

View File

@ -1263,6 +1263,12 @@ input[type="checkbox"] {
gap: 0.5rem;
}
.filter-header-controls {
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Search Components */
.search-wrapper {
position: relative;
@ -1315,6 +1321,64 @@ input[type="checkbox"] {
color: var(--color-text);
}
.collapse-toggle {
background: none;
border: 1px solid var(--color-border);
border-radius: 0.375rem;
color: var(--color-text-secondary);
cursor: pointer;
padding: 0.375rem;
transition: var(--transition-fast);
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
}
.collapse-toggle:hover {
background-color: var(--color-bg-secondary);
border-color: var(--color-primary);
color: var(--color-text);
}
.collapse-toggle svg {
transition: transform var(--transition-medium);
}
/* When expanded, rotate the chevron */
.collapse-toggle[data-collapsed="false"] svg {
transform: rotate(180deg);
}
/* Collapsible Content */
.collapsible-content {
overflow: hidden;
transition: all var(--transition-medium);
opacity: 1;
max-height: 1000px;
}
.collapsible-content.hidden {
opacity: 0;
max-height: 0;
padding-top: 0;
padding-bottom: 0;
margin-top: 0;
margin-bottom: 0;
}
/* Smooth animation for expanding content */
.collapsible-content:not(.hidden) {
animation: expandContent 0.3s ease-out;
}
/* Content spacing when expanded */
.collapsible-content:not(.hidden) .advanced-filters-compact,
.collapsible-content:not(.hidden) .tag-section {
padding-top: 0.75rem;
}
/* Filter Grids & Groups */
.filter-grid-compact {
display: grid;
@ -1429,11 +1493,9 @@ input[type="checkbox"] {
user-select: none;
}
/* Tag System */
.tag-section {
display: flex;
flex-direction: column;
gap: 1rem;
.tag-section .tag-controls {
order: -1;
margin-bottom: 0.75rem;
}
.selected-tags {
@ -1574,6 +1636,14 @@ input[type="checkbox"] {
transition: var(--transition-fast);
}
.filter-reset {
width: 32px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
}
.filter-reset:hover {
background-color: var(--color-bg-secondary);
border-color: var(--color-warning);
@ -1591,13 +1661,6 @@ input[type="checkbox"] {
opacity: 0.9;
}
/* Tag Controls */
.tag-controls {
display: flex;
align-items: center;
gap: 0.75rem;
}
.tag-toggle {
padding: 0.375rem 0.75rem;
border: 1px solid var(--color-border);
@ -2391,6 +2454,17 @@ footer {
to { opacity: 1; }
}
@keyframes expandContent {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
@ -3385,6 +3459,23 @@ footer {
.view-toggle {
justify-content: center;
}
.filter-header-controls {
gap: 0.375rem;
}
.collapse-toggle,
.filter-reset {
width: 28px;
height: 28px;
padding: 0.25rem;
}
.collapse-toggle svg,
.filter-reset svg {
width: 14px;
height: 14px;
}
}
@media (width <= 640px) {
@ -3519,6 +3610,21 @@ footer {
.filter-card-compact {
padding: 0.5rem;
}
.filter-header-compact {
flex-wrap: wrap;
gap: 0.5rem;
}
.filter-header-compact h3 {
flex: 1 1 100%;
margin-bottom: 0.25rem;
}
.filter-header-controls {
flex: 1 1 100%;
justify-content: flex-end;
}
}