Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

HTML
<!-- JS: Alexandr Petrunin-->
<script>
    document.addEventListener('DOMContentLoaded', function() {
        // Handle collapse functionality
        document.querySelectorAll('[data-bs-toggle="collapse"]').forEach(trigger => {
            trigger.addEventListener('click', function(e) {
                e.preventDefault();
                const target = document.querySelector(this.getAttribute('data-bs-target'));

                // If there's a parent accordion, hide other panels
                const parent = document.querySelector(target.getAttribute('data-bs-parent'));
                if (parent) {
                    parent.querySelectorAll('.collapse.show').forEach(el => {
                        if (el !== target) {
                            el.classList.remove('show');
                        }
                    });
                }

                // Toggle target panel
                target.classList.toggle('show');
            });
        });

        // Handle list/tab functionality
        document.querySelectorAll('[data-bs-toggle="list"]').forEach(trigger => {
            trigger.addEventListener('click', function(e) {
                e.preventDefault();

                // Remove active class from all tabs in group
                const listGroup = this.closest('.list-group');
                listGroup.querySelectorAll('.active').forEach(el => {
                    el.classList.remove('active');
                });

                // Add active class to clicked tab
                this.classList.add('active');

                // Show corresponding content
                const target = document.querySelector(this.getAttribute('href'));
                const tabContent = target.closest('.tab-content');

                tabContent.querySelectorAll('.tab-pane').forEach(pane => {
                    pane.classList.remove('show', 'active');
                });

                target.classList.add('show', 'active');
            });
        });
    });

    // Add required CSS if not already present
    const style = document.createElement('style');
    style.textContent = `
    .collapse:not(.show) {
        display: none;
    }
    .collapse.show {
        display: block;
    }
    .tab-content > .tab-pane {
        display: none;
    }
    .tab-content > .active {
        display: block;
    }
`;
    document.head.appendChild(style);
</script>

<!-- Activate tooltips -->
<script>
    document.addEventListener('DOMContentLoaded', function() {
        // Find all elements with tooltip
        document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(element => {
            const title = element.getAttribute('title');

            // Create tooltip element
            const tooltip = document.createElement('div');
            tooltip.className = 'custom-tooltip';
            tooltip.textContent = title;
            document.body.appendChild(tooltip);

            // Show tooltip
            element.addEventListener('mouseenter', e => {
                const bounds = element.getBoundingClientRect();
                tooltip.style.display = 'block';

                // Position tooltip above the element
                tooltip.style.left = bounds.left + (bounds.width - tooltip.offsetWidth) / 2 + 'px';
                tooltip.style.top = bounds.top - tooltip.offsetHeight - 5 + 'px';
            });

            // Hide tooltip
            element.addEventListener('mouseleave', () => {
                tooltip.style.display = 'none';
            });

            // Remove title attribute to prevent default browser tooltip
            element.removeAttribute('title');
        });
    });

    // Add required CSS
    const tooltipStyle = document.createElement('style');
    tooltipStyle.textContent = `
    .custom-tooltip {
        display: none;
        position: fixed;
        background: rgba(0, 0, 0, 0.8);
        color: white;
        padding: 5px 10px;
        border-radius: 4px;
        font-size: 14px;
        z-index: 1000;
        pointer-events: none;
    }

    .custom-tooltip::after {
        content: '';
        position: absolute;
        bottom: -5px;
        left: 50%;
        transform: translateX(-50%);
        border-width: 5px 5px 0;
        border-style: solid;
        border-color: rgba(0, 0, 0, 0.8) transparent transparent;
    }
`;
    document.head.appendChild(tooltipStyle);
</script>

<!-- Filter bar -->
<script>
    function extractTags(raw) {
  return raw
    // extract "cycle N" first (keeps them intact)
    .replace(/cycle\s+(\d+)/gi, 'cycle-$1')
    // split on whitespace/newlines
    .trim()
    .toLowerCase()
    .split(/\s+/)
    // convert cycle-N back to "cycle N"
    .map(tag => tag.replace(/cycle-(\d+)/, 'cycle $1'))
    .filter(Boolean);
}
function searchActivities() {
  	const cards = document.querySelectorAll('#card-grid .card');

  // normalize	const filter values
  const fv = {
    	text: (document.getElementById('card-filter-text').value || '').trim().toLowerCase(),
    	topic: (document.getElementById('card-filter-topic').value || '').trim().toLowerCase(),
    	cycle: (document.getElementById('card-filter-cycle').value || '').trim().toLowerCase(),
    	deliverable: (document.getElementById('card-filter-deliverable').value || '').trim().toLowerCase(),
  	};

  	cards.forEach(card => {
    // Title text (normalize)
    	const titleEltitle = card.querySelector('.card-body h2.card-title').innerText.trim().toLowerCase();
    	
		const titlerawTags = (titleEl ? titleEl.innerText : '')card.querySelector('.tags').innerText.trim().toLowerCase();

    // Build tag list robustly:
    let tagList = [];

    // 1) Prefer .tag elements if present:
    const tagElems = card.querySelectorAll('.card-body .tags .tag');
    if (tagElems && tagElems.length) {
      tagList = Array.from(tagElems).map(t => t.innerText.trim().toLowerCase()).filter(Boolean);
    } else {
      // 2) Fallback to raw text and split on common delimiters
      const tagsNode = card.querySelector('.card-body .tags');
      const tagsText = tagsNode ? tagsNode.innerText.trim().toLowerCase() : '';
      if (tagsText) {
        // split on comma, slash, pipe, bullet, newline, or two+ spaces
        tagList = tagsText.split(/[,\/\|\u2022\n\r]+|\s{2,}/).map(s => s.trim()).filter(Boolean);
      }
    }

    // DEBUG (remove later) — shows what's being compared
    console.log('card:', titleEl ? titleEl.innerText : '(no title)', '-> tagList:', tagList);

    // matching helper: empty filter => true, otherwise exact match in tagList
    const matchesTag = (filterVal) => {
      if (!filterVal) return true; // treat empty as wildcard
      // If the filter is like "cycle 1" we want exact match (not substring)
      // But also allow numeric-only match like "1" to match "cycle 1"
      const fv = filterVal.toLowerCase( 	const tagList = extractTags(rawTags);
      // exact match
      if (tagList.includes(fv)) return true;
      // match when tag contains the filter as a distinct token (e.g. "cycle 1" vs "cycle 10")
      // split each tag into tokens and compare tokens
      for (const tag of tagList) {
        const tokens = tag.split(/\s+/).map(t => t.trim()).filter(Boolean);
        if (tokens.includes(fv)) return true;
      }
      return false;
    };	const matches = (val) => !val || tagList.includes(val);


    	const show =
      	title.includes(fvfilter.text) &&
      matchesTag	matches(fvfilter.topic) &&
      matchesTag	matches(fvfilter.cycle) &&
      matchesTag	matches(fvfilter.deliverable);

    	card.style.display = show ? ''"" : '"none'";
  	});
}
</script>