diff --git a/.gitignore b/.gitignore index 8b2697f80d..74e6d9be5d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,16 @@ site # python cache **/__pycache__/** +*.pyc # IDE files .idea/ +# OS files +.DS_Store +# npm +.npm-cache/ .Rproj.user /tmp-site/ diff --git a/docs/index.md b/docs/index.md index 84d03b7f08..d7097329cb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,55 +6,51 @@ hide: - toc --- -
-
+
+

Over het algoritmekader

Belangrijkste wetten en regels voor verantwoord gebruik van overheidsalgoritmes, met tips en hulpmiddelen.

Meer informatie over het algoritmekader

-
-

Lees alles over de Europese AI-verordening

-
-
-
+
+ -
+ - - - - -


diff --git a/docs/javascripts/exportExcel.js b/docs/javascripts/exportExcel.js index 08d5a4e464..26c9f9ab75 100644 --- a/docs/javascripts/exportExcel.js +++ b/docs/javascripts/exportExcel.js @@ -97,9 +97,10 @@ function capitalize(str) { } -function exportToExcel(config) { +function exportToFormat(config, format) { const button = document.getElementById(config.buttonId); const originalButtonHTML = button ? button.innerHTML : ''; + const formatLabel = format.toUpperCase(); try { setButtonState(button, true, 'Exporteren...'); @@ -107,47 +108,24 @@ function exportToExcel(config) { const table = document.getElementById(config.tableId); const activeFilters = getCurrentFilters(config); - const exportData = extractTableDataForExcel(table, config, activeFilters); - const workbook = createWorkbook(exportData, activeFilters, config, 'xlsx'); + const exportData = extractTableData(table, config, activeFilters); + const workbook = createWorkbook(exportData, activeFilters, config, format); const timestamp = new Date().toISOString().slice(0, 10); - const filename = `${config.filename}_${timestamp}.xlsx`; + const filename = `${config.filename}_${timestamp}.${format}`; - XLSX.writeFile(workbook, filename, { bookType: 'xlsx' }); + XLSX.writeFile(workbook, filename, { bookType: format }); } catch (error) { - console.error('Excel export error:', error); - alert(`Excel export mislukt: ${error.message}`); + console.error(`${formatLabel} export error:`, error); + alert(`${formatLabel} export mislukt: ${error.message}`); } finally { restoreButtonState(button, originalButtonHTML); } } -function exportToODS(config) { - const button = document.getElementById(config.buttonId); - const originalButtonHTML = button ? button.innerHTML : ''; - - try { - setButtonState(button, true, 'Exporteren...'); - validateRequirements(config); - - const table = document.getElementById(config.tableId); - const activeFilters = getCurrentFilters(config); - const exportData = extractTableDataForODS(table, config, activeFilters); - const workbook = createWorkbook(exportData, activeFilters, config, 'ods'); - - const timestamp = new Date().toISOString().slice(0, 10); - const filename = `${config.filename}_${timestamp}.ods`; - - XLSX.writeFile(workbook, filename, { bookType: 'ods' }); - - } catch (error) { - console.error('ODS export error:', error); - alert(`ODS export mislukt: ${error.message}`); - } finally { - restoreButtonState(button, originalButtonHTML); - } -} +function exportToExcel(config) { exportToFormat(config, 'xlsx'); } +function exportToODS(config) { exportToFormat(config, 'ods'); } function validateRequirements(config) { if (typeof XLSX === 'undefined') { @@ -160,39 +138,7 @@ function validateRequirements(config) { } } -function extractTableDataForExcel(table, config, activeFilters) { - const rows = table.getElementsByTagName("tr"); - - if (rows.length === 0) { - throw new Error('Geen data om te exporteren'); - } - - const exportData = []; - const headers = extractHeaders(rows[0]); - - // Add filterrow is active filters are present - const hasActiveFilters = activeFilters && activeFilters.length > 0; - if (hasActiveFilters) { - const filterRow = [getActiveFiltersString(activeFilters)]; - while (filterRow.length < headers.length) filterRow.push(''); - exportData.push(filterRow); - } - - exportData.push(headers); - - // Only export visible rows (after filtering) - for (let i = 1; i < rows.length; i++) { - const row = rows[i]; - if (row.classList && row.classList.contains('filter-row')) continue; - if (row.style.display === 'none') continue; - const rowData = extractRowData(row, headers, config); - exportData.push(rowData); - } - - return exportData; -} - -function extractTableDataForODS(table, config, activeFilters) { +function extractTableData(table, config, activeFilters) { const rows = table.getElementsByTagName("tr"); if (rows.length === 0) { @@ -465,6 +411,23 @@ function closeExportDropdown() { } } +// Event delegation for data-action attributes +document.addEventListener('click', function(e) { + var target = e.target; + var action = target.getAttribute('data-action') || (target.closest && target.closest('[data-action]') ? target.closest('[data-action]').getAttribute('data-action') : null); + if (action === 'export-excel') exportExcel(); + if (action === 'export-ods') exportODS(); + if (action === 'toggle-export-dropdown') toggleExportDropdown(); +}); + +document.addEventListener('keydown', function(e) { + var target = e.target; + var action = target.getAttribute('data-action') || (target.closest && target.closest('[data-action]') ? target.closest('[data-action]').getAttribute('data-action') : null); + if (action === 'toggle-export-dropdown') handleExportKeydown(e); + var exportType = target.getAttribute('data-export-type'); + if (exportType) handleDropdownKeydown(e, exportType); +}); + // Export functions globally window.toggleExportDropdown = toggleExportDropdown; window.exportExcel = exportExcel; diff --git a/docs/javascripts/extra.js b/docs/javascripts/extra.js index 4d0a8ab55f..5f735e6f59 100644 --- a/docs/javascripts/extra.js +++ b/docs/javascripts/extra.js @@ -42,30 +42,6 @@ initialize(); }); - // Function to initialize Choices.js for multi-select filters - function initializeChoices() { - const elements = document.querySelectorAll('.js-example-basic-multiple'); - - elements.forEach(function(element) { - // Check if Choices.js is already initialized - if (!element.choicesInstance) { - const choices = new Choices(element, { - removeItemButton: true, - placeholder: true, - searchEnabled: true, - noResultsText: 'Geen resultaten', - noChoicesText: 'Geen keuzes beschikbaar', - itemSelectText: 'Klik om te selecteren', - resetScrollPosition: false - }); - - // Store the Choices.js instance to avoid re-initialization - element.choicesInstance = choices; - } - }); - } - - // Function to initialize accessible abbreviations and tooltips function initializeAccessibleAbbreviations() { // Handle abbreviations with custom tooltips diff --git a/docs/javascripts/filtering.js b/docs/javascripts/filtering.js index 9af1d4f625..b8cda1d032 100644 --- a/docs/javascripts/filtering.js +++ b/docs/javascripts/filtering.js @@ -237,23 +237,62 @@ function evaluateLabelExpression(expression, selectedLabels) { } try { - // Transformeer labels in de expressie naar hasLabel functie - const transformedExpression = expression.replace(/["']?([a-zA-Z0-9-_]+)["']?/g, "hasLabel('$1')"); - - // Maak de functie die geëvalueerd wordt - const functionBody = ` - const hasLabel = (label) => { - // Controleer of een van de geselecteerde labels overeenkomt via labelMapper - for (const selLabel of selectedLabels) { - const mappedLabel = labelMapper.find(selLabel)?.label; - if (mappedLabel && mappedLabel === label) return true; - } - return false; - }; - return ${transformedExpression}; - `; + // Helper: check if a label is present in selectedLabels via labelMapper + var hasLabel = function(label) { + for (var i = 0; i < selectedLabels.length; i++) { + var mappedLabel = labelMapper.find(selectedLabels[i]); + if (mappedLabel && mappedLabel.label === label) return true; + } + return false; + }; + + // Tokenize the expression into parentheses, operators, and label identifiers + var tokens = expression.match(/\(|\)|&&|\|\||!|[a-zA-Z0-9_-]+/g) || []; + var pos = 0; + + function peek() { return tokens[pos]; } + function consume() { return tokens[pos++]; } + + function parseOr() { + var left = parseAnd(); + while (peek() === '||') { + consume(); + var right = parseAnd(); + left = left || right; + } + return left; + } + + function parseAnd() { + var left = parseNot(); + while (peek() === '&&') { + consume(); + var right = parseNot(); + left = left && right; + } + return left; + } + + function parseNot() { + if (peek() === '!') { + consume(); + return !parsePrimary(); + } + return parsePrimary(); + } + + function parsePrimary() { + if (peek() === '(') { + consume(); // consume '(' + var result = parseOr(); + consume(); // consume ')' + return result; + } + var label = consume(); + return hasLabel(label); + } - return new Function('selectedLabels', functionBody)(selectedLabels); + return parseOr(); } catch (error) { console.error('Error evaluating label expression (oude logica):', error, 'Expression:', expression); return false; diff --git a/docs/javascripts/hamburger-control.js b/docs/javascripts/hamburger-control.js deleted file mode 100644 index 5b721d7671..0000000000 --- a/docs/javascripts/hamburger-control.js +++ /dev/null @@ -1,63 +0,0 @@ -// Force hide hamburger menu above 800px -function controlHamburgerMenu() { - function findAndHideHamburger() { - // Even more aggressive selector search - const selectors = [ - '.md-header__button[for="__drawer"]', - '.md-header__button.md-icon--menu', - '.md-nav__button', - 'button[for="__drawer"]', - '[data-md-component="navigation"]', - '.md-header__button[type="button"]', - '.md-header button' - ]; - - const hamburgers = document.querySelectorAll(selectors.join(', ')); - - hamburgers.forEach(hamburger => { - if (window.innerWidth > 800) { - // Nuclear option - remove all possible CSS - hamburger.style.cssText = 'display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; position: absolute !important; left: -9999px !important;'; - hamburger.setAttribute('hidden', 'true'); - hamburger.setAttribute('aria-hidden', 'true'); - hamburger.disabled = true; - } else { - hamburger.style.cssText = ''; - hamburger.removeAttribute('hidden'); - hamburger.removeAttribute('aria-hidden'); - hamburger.disabled = false; - } - }); - } - - // Initial check - findAndHideHamburger(); - - // Listen for resize events - window.addEventListener('resize', findAndHideHamburger); - - // Force check every 100ms to override any dynamic changes - const forceInterval = setInterval(findAndHideHamburger, 100); - - // Also watch for DOM mutations - if (typeof MutationObserver !== 'undefined') { - const observer = new MutationObserver(findAndHideHamburger); - observer.observe(document.body, { childList: true, subtree: true }); - } - - // Clear interval after 10 seconds to avoid performance impact - setTimeout(() => clearInterval(forceInterval), 10000); -} - -// Run immediately -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', controlHamburgerMenu); -} else { - controlHamburgerMenu(); -} - -// Also run on page show (for browser back/forward) -window.addEventListener('pageshow', controlHamburgerMenu); - -// Run again after a delay to catch any late-loading elements -setTimeout(controlHamburgerMenu, 1000); diff --git a/docs/javascripts/modal.js b/docs/javascripts/modal.js index e35d8b2d6c..6f35c42cec 100644 --- a/docs/javascripts/modal.js +++ b/docs/javascripts/modal.js @@ -41,7 +41,8 @@ function updateFieldsBasedOnType(selectedTypeElement) { } function closeModal() { - document.getElementById('modal').classList.add("display-none") + disableFocusTrap(); + document.getElementById('modal').classList.add("display-none"); } function onDynamicContentLoaded(targetDiv, callback) { @@ -750,17 +751,3 @@ function disableFocusTrap() { } } -// Update closeModal function to disable focus trap -const originalCloseModal = window.closeModal; -window.closeModal = function() { - disableFocusTrap(); - if (originalCloseModal) { - originalCloseModal(); - } else { - // Fallback close functionality - const modal = document.getElementById('modal'); - if (modal) { - modal.classList.add('display-none'); - } - } -}; diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index e332aa43a7..67e674a042 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -24,6 +24,7 @@ body{ font-size: .85em; display: inline-block; margin: .2rem .3rem; + text-decoration: none; } .mdx-badge__icon { @@ -93,7 +94,7 @@ table { } .md-typeset__table { -color: #154271; +color: #154273; } @@ -170,15 +171,9 @@ tbody tr td a:hover { .float-container a { color: #154273; - text-align: left; text-decoration: underline; } -/* Grid Styling */ -.md-typeset .grid { - grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); -} - /* Admonition Styling */ :root { --md-admonition-icon--simple: url('data:image/svg+xml;charset=utf-8,'); @@ -189,13 +184,11 @@ tbody tr td a:hover { .md-typeset .admonition.simple, .md-typeset details.simple { border-color: rgb(5, 80, 141); - text-align: left; } .md-typeset .simple > .admonition-title, .md-typeset .simple > summary { background-color: rgba(185, 185, 185, 0.1); - text-align: left; } .md-typeset .simple > .admonition-title::before, @@ -208,25 +201,21 @@ tbody tr td a:hover { /* Info Section */ .info-section { margin-bottom: 30px; - text-align: left; /* Ensure left alignment */ } .info-section h2 { font-size: .8em; color: #154273; margin: 0 0 10px; - text-align: left; /* Ensure left alignment */ } .subheader a { color: #154273; text-decoration: underline; - text-align: left; } .subheader a:hover { text-decoration: none; - text-align: left; } .float-container { @@ -239,7 +228,7 @@ tbody tr td a:hover { .float-container h2 { width: 100%; -border-bottom: solid 3px #154271; +border-bottom: solid 3px #154273; padding-bottom: 8px; } @@ -301,25 +290,20 @@ padding-bottom: 8px; margin-top: 30px; padding-top: 10px; border-top: 1px solid #ddd; - text-align: left; } .footer-section h2 { font-size: 1.5em; color: #154273; - text-align: left; } .footer-section a { color: #154273; font-weight: bold; - text-align: left; - } .footer-section a:hover { text-decoration: underline; - text-align: left; } /* Full Width Outline Block */ @@ -330,43 +314,36 @@ padding-bottom: 8px; padding: 20px; box-sizing: border-box; margin-bottom: 20px; - text-align: left; } .full-width-header { font-size: 1.5em; color: #154273; margin: 0 0 10px; - text-align: left; } .full-width-item { margin: 10px 0; - text-align: left; } .item-description { font-size: 1em; color: #666; margin: 0 0 10px; - text-align: left; } .show-more-link { font-weight: bold; color: #154273; text-decoration: none; - text-align: left; } .show-more-link:hover { text-decoration: underline; - text-align: left; } [dir=ltr] .md-typeset .float-container ol li, [dir=ltr] .md-typeset .float-container ul li { margin-left: 0; - text-align: left; } [dir=ltr] .md-typeset .float-container ol, [dir=ltr] .md-typeset .float-container ul { @@ -377,63 +354,6 @@ padding-bottom: 8px; margin-bottom: 0.2rem; } -/* Badge Styling */ -.mdx-badge { - font-size: 0.85em; - display: inline-block; - margin: 0.2rem 0.3rem; - text-align: left; -} - -.mdx-badge__icon { - border-radius: 0.1rem 0 0 0.1rem; - background: var(--md-accent-fg-color--transparent); - text-align: left; -} - -.mdx-badge__text { - border-radius: 0 0.1rem 0.1rem 0; - padding: 0.2rem 0.3rem; - text-align: left; - text-decoration: none; -} - -.mdx-badge a { - color: var(--md-accent-fg-color); - text-align: left; - text-decoration: none; -} - -/* Grid Styling */ -.md-typeset .grid { - grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); - text-align: left; -} - -/* Admonition Styling */ -:root { - --md-admonition-icon--simple: url('data:image/svg+xml;charset=utf-8,'); -} - -.md-typeset .admonition.simple, -.md-typeset details.simple { - border-color: rgb(5, 80, 141); - text-align: left; -} - -.md-typeset .simple > .admonition-title, -.md-typeset .simple > summary { - background-color: rgba(185, 185, 185, 0.1); - text-align: left; -} - -.md-typeset .simple > .admonition-title::before, -.md-typeset .simple > summary::before { - background-color: rgb(5, 80, 141); - -webkit-mask-image: var(--md-admonition-icon--simple); - mask-image: var(--md-admonition-icon--simple); - text-align: left; -} /* Algemene layout van de header */ .header-container { @@ -441,7 +361,6 @@ padding-bottom: 8px; flex-direction: row; align-items: center; margin-bottom: 12px; - text-align: left; } .version-container { @@ -461,7 +380,6 @@ padding-bottom: 8px; font-weight: 500; text-transform: capitalize; cursor: pointer; - text-align: left; } .hover-info { @@ -488,13 +406,6 @@ padding-bottom: 8px; display: block; } -.subheader { - font-size: 1.2em; - color: #154273; - margin-left: 0; - text-align: left; -} - .md-container li{ color: #154273; } @@ -512,14 +423,6 @@ padding-bottom: 8px; margin-bottom: .3em; } -.md-typeset h1{ - margin: 0.6em 0 .4em 0; - font-size: 2.2em; - font-weight: 800; - line-height: 1em; -} - - .block-image { width: 100%; /* Zorgt ervoor dat de afbeelding over de breedte van het blok gaat */ height: 120px; /* Stel een vaste hoogte in voor consistente weergave */ @@ -551,10 +454,6 @@ a{ scrollbar-color:#316fb3; } -[data-md-color-accent=indigo]{ - --md-accent-fg-color:#B2D7EE; -} - .button-primary { background-color: #154273; color: #fff; @@ -595,30 +494,25 @@ color: #154273; } [data-md-color-accent=indigo]{ - --md-accent-fg-color:154271 !important; + --md-accent-fg-color:#154273 !important; --md-accent-fg-color--transparent:#B2D7EE; } [data-md-color-accent=teal]{ - --md-accent-fg-color:#154271 !important; + --md-accent-fg-color:#154273 !important; --md-accent-fg-color--transparent:#E2EDDB; } [data-md-color-accent=deep-orange]{ - --md-accent-fg-color:154271 !important; + --md-accent-fg-color:#154273 !important; --md-accent-fg-color--transparent:#FBEAD9; } [data-md-color-accent=blue]{ - --md-accent-fg-color:154271 !important; + --md-accent-fg-color:#154273 !important; --md-accent-fg-color--transparent:#DCE3EA; } -.mdx-badge { - text-decoration: none; /* Optional: remove underline */ - transition: background-color 0.3s, color 0.3s; /* Smooth transition */ -} - .mdx-badge:hover { background-color: #f1f1f1; /* Change background color on hover */ color: #333; /* Change text color on hover */ @@ -645,7 +539,7 @@ li.md-source_fact.md-source_fact--forks { } li.md-source__fact.md-source__fact--version{ - color: #154271; + color: #154273; } .md-typeset .admonition.tip, .md-typeset details.tip{ @@ -662,7 +556,7 @@ font-size: 0.875rem !important; border: .075rem solid #B2D7EE; border-radius: .2rem; font-size: 0.875rem !important; - color: #154271; + color: #154273; display: flow-root; margin: 1.5625em 0; padding: 0 .6rem; @@ -670,13 +564,13 @@ font-size: 0.875rem !important; } .md-typeset .tip>.admonition-title:before, .md-typeset .tip>summary:before{ - background-color: #154271; + background-color: #154273; } .md-typeset .md-button--primary{ - background-color: #154271; + background-color: #154273; color: #FFFFFF; - border-color: #154271; + border-color: #154273; text-decoration: none; } @@ -690,8 +584,8 @@ font-size: 0.875rem !important; .md-typeset .md-button--secondary { background-color: #ffffff; - color: #154271; - border: 1px solid #154271; + color: #154273; + border: 1px solid #154273; text-decoration: none; font-size: 16px; font-weight: 700; @@ -700,8 +594,8 @@ font-size: 0.875rem !important; .md-typeset .md-button--secondary:hover { background-color: #ffffff; - color: #154271; - border: 1px solid #154271; + color: #154273; + border: 1px solid #154273; text-decoration: none; font-size: 16px; font-weight: 700; @@ -724,14 +618,14 @@ font-size: 0.875rem !important; .md-typeset .expander > summary { background-color: rgba(43, 155, 70, 0); font-size: 1.1em; /* Adjust font size */ - color: #154271; /* Change text color */ + color: #154273; /* Change text color */ font-weight: 400; padding-top: 8px; text-decoration: underline; } .md-typeset .expander > .admonition-title::before, .md-typeset .expander > summary::before { - background-color: #154271; + background-color: #154273; -webkit-mask-image: var(--md-admonition-icon--expander); mask-image: var(--md-admonition-icon--expander); margin-top: 0; @@ -749,6 +643,45 @@ font-size: 0.875rem !important; text-decoration: none; } +/* Homepage layout classes */ +.responsive-grid-2 { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin-bottom: 40px; +} + +.responsive-grid-3 { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 20px; +} + +.homepage-card { + background-color: #e5f1f9; + padding: 1em 1em 1em 1em; +} + +.homepage-card-plain { + padding-left: 1.5em; + padding-top: 0; +} + +.homepage-card-plain h2 { + padding-top: 0; + margin-top: 0; +} + +.homepage-list { + list-style: none; + padding: 0; + margin-left: 0; +} + +.homepage-list li { + margin-left: 0; +} + /* Homepage grid h2 styling */ .responsive-grid-2 h2, .responsive-grid-3 h2 { @@ -816,3 +749,64 @@ input[type="text"]::placeholder { font-size: 0.9rem; font-family: "ROsanswebtextregular", sans-serif; } + +/* Filter and export components */ +.filter-container { + background-color: #e6f3fb; + padding: 16px; + border-radius: 8px; + margin-bottom: 16px; +} + +.filter-actions { + display: flex; + align-items: center; + gap: 20px; +} + +.export-dropdown-container { + position: relative; + display: inline-block; +} + +.export-dropdown { + display: none; + position: absolute; + background: white; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + width: 100%; + z-index: 10000; + font-family: "ROsanswebtextregular", Arial, sans-serif; + top: calc(100% - 1px); + left: 0; +} + +.export-dropdown-item { + padding: 12px 16px; + cursor: pointer; + color: #154273; + font-size: 14px; + font-family: "ROsanswebtextregular", Arial, sans-serif; + background: none; + border: none; + width: 100%; + text-align: left; +} + +.export-dropdown-item:hover { + background-color: #f5f5f5; +} + +.export-dropdown-item + .export-dropdown-item { + border-top: 1px solid #eee; +} + +/* Export button icon styling */ +#export-btn .fa-download, +#export-btn .fa-chevron-down { + font-size: 14px; + vertical-align: middle; + color: #154271; +} diff --git a/docs/stylesheets/filters.css b/docs/stylesheets/filters.css index cec94837a2..c307f0e383 100644 --- a/docs/stylesheets/filters.css +++ b/docs/stylesheets/filters.css @@ -40,7 +40,6 @@ outline: none; font-size: 16px; font-family: "ROsanswebtextregular", sans-serif; - width: 100%; min-width: 250px; width: 260px; min-height: 48px; @@ -148,9 +147,6 @@ content: none !important; } -.mdx-badge{ - text-decoration: none; -} /* Fix excessive right padding in dropdown items */ @media (min-width: 640px) { diff --git a/docs/stylesheets/footer.css b/docs/stylesheets/footer.css index 1913c1f79d..2520109b5a 100644 --- a/docs/stylesheets/footer.css +++ b/docs/stylesheets/footer.css @@ -46,7 +46,7 @@ transition: transform 0.2s ease; /* Animatie voor hover */ } - .md-footer__column a:hover, td:hover { + .md-footer__column a:hover { color: #316fb3; } diff --git a/docs/stylesheets/modal.css b/docs/stylesheets/modal.css index e18396752a..604af5bc61 100644 --- a/docs/stylesheets/modal.css +++ b/docs/stylesheets/modal.css @@ -185,30 +185,11 @@ abbr[title][tabindex="0"]:focus::after { } .button:hover { - transition: color 150ms ease-in-out, outline-offset 150ms ease-in-out; appearance: none; - border: 1px solid #e6e6e6; - display: inline-block; font-weight: 700; line-height: 1; - margin: 0 .5em .5em 0; - max-width: 100%; - padding: .75em; - position: relative; - text-align: center; - text-shadow: none; - user-select: none; - vertical-align: middle; - word-break: break-word; - cursor: pointer; - font-size: 1em; - border-radius: .25em; - border-color: #154273; outline: 0; box-shadow: 0 0 .75em rgba(21, 66, 115, .4); - background-color: #fff; - color: #154273; - text-decoration: none; } .button--primary { @@ -234,26 +215,9 @@ abbr[title][tabindex="0"]:focus::after { } .button--primary:hover { - display: inline-block; - line-height: 1; - margin: 0 .5em .5em 0; - max-width: 100%; - padding: .75em; - position: relative; - text-align: center; - text-shadow: none; - user-select: none; - vertical-align: middle; - word-break: break-word; - cursor: pointer; - border: 1px solid transparent; - font-size: 1em; - border-radius: .25em; - border-color: #154273; outline: 0; box-shadow: 0 0 .75em rgba(21,66,115,.4); background-color: #10345c; - color: #fff; } .input-text { @@ -275,24 +239,9 @@ abbr[title][tabindex="0"]:focus::after { } .input-text:focus { - -webkit-font-smoothing: antialiased; - --swiper-theme-color: #007aff; - box-sizing: inherit; box-shadow: 0 0 0 2px #000 !important; outline: 1px solid #fff; outline-offset: 2px; - background: #fff; - line-height: 1.5; - font-family: inherit; - border: 1px solid #154273; - color: #154273; - border-radius: .25em; - font-size: 1em; - margin: 0; - display: block; - width: 100%; - height: 50px; - padding: .6em .8em; } .input-select { @@ -312,22 +261,9 @@ abbr[title][tabindex="0"]:focus::after { } .input-select:focus { - -webkit-font-smoothing: antialiased; - --swiper-theme-color: #007aff; - box-sizing: inherit; - background: #fff; - border: 1px solid #154273; - line-height: 1.5; - font-family: inherit; - padding: .409em .5em; - border-radius: .5em; - font-size: 1em; box-shadow: 0 0 0 2px #000 !important; outline: 1px solid #fff; outline-offset: 2px; - display: block; - width: 100%; - height: 2.5em; } /* Disabled state for select that matches design system */ diff --git a/docs/stylesheets/navigation.css b/docs/stylesheets/navigation.css index 2dbe81ab68..acabae1fe2 100644 --- a/docs/stylesheets/navigation.css +++ b/docs/stylesheets/navigation.css @@ -5,7 +5,7 @@ position: absolute; top: -40px; left: 6px; - background-color: #154273; + background-color: var(--dark-blue); color: white; padding: 8px; text-decoration: none; @@ -32,7 +32,7 @@ } #main-content:focus { - outline: 2px solid #154273; + outline: 2px solid var(--dark-blue); outline-offset: 2px; } @@ -43,7 +43,7 @@ } .md-header__button.md-logo:focus { - outline: 3px solid #154273 !important; + outline: 3px solid var(--dark-blue) !important; outline-offset: 2px !important; background-color: rgba(21, 66, 115, 0.1) !important; } @@ -53,8 +53,8 @@ button, .md-button { border: 1px solid transparent; } -button:focus, .md-button:focus { - outline: 2px solid #154273 !important; +button:focus, .md-button:focus, .md-header__button:focus { + outline: 2px solid var(--dark-blue) !important; outline-offset: 2px !important; background-color: rgba(21, 66, 115, 0.1) !important; } @@ -94,6 +94,7 @@ body { display: flex; position: static; box-shadow: 0 2px 4px rgba(0,0,0,0.1); + width: 100%; } .md-tabs__list { @@ -125,6 +126,10 @@ body { padding-left: 1.2rem; padding-right: 1.2rem; max-width: var(--md-main-max-width, 61rem); + display: flex; + align-items: center; + width: 100%; + box-sizing: border-box; } .md-tabs__item--active { @@ -153,7 +158,7 @@ body { } h1{ - color: #154273 !important; + color: var(--dark-blue) !important; font-weight: 900 !important; } @@ -182,38 +187,21 @@ h1{ display: none; } -/* Ensure hover and focus effects are applied to the link itself */ -.md-tabs__list .md-tabs__item .md-tabs__link:hover, -.md-tabs__list .md-tabs__item .md-tabs__link:focus { - color: #154273 !important; /* Same color as active */ - background-color: #b2d7ee !important; -} - -/* Apply color and background on both link and item on hover and focus */ +/* Tab hover and focus effects */ .md-tabs__item:hover, -.md-tabs__item:hover .md-tabs__link, -.md-tabs__link:hover, .md-tabs__item:focus-within, -.md-tabs__item:focus-within .md-tabs__link { - color: var(--dark-blue) !important; /* Set text color to blue */ - background-color: var(--light-blue) !important; /* Set background color to light blue */ -} - .md-tabs__item:hover .md-tabs__link, -.md-tabs__item:focus-within .md-tabs__link { - color: #154273 !important; /* Zorgt ervoor dat de link blauw blijft als je over het tab-item zweeft */ -} - -.md-tabs__item:hover, -.md-tabs__item:focus-within { - background-color: #b2d7ee !important; - color: #154273 !important; +.md-tabs__item:focus-within .md-tabs__link, +.md-tabs__link:hover, +.md-tabs__link:focus { + color: var(--dark-blue) !important; + background-color: var(--light-blue) !important; } /* Active tab styling - only for actually active pages, not focus */ .md-tabs .md-tabs__list .md-tabs__item--active .md-tabs__link { - color: #154273 !important; /* Active color */ - background-color: #b2d7ee !important; /* Active background */ + color: var(--dark-blue) !important; + background-color: var(--light-blue) !important; } /* Enhanced focus states for accessibility */ @@ -226,28 +214,22 @@ h1{ /* Focus states for search */ .md-search__input:focus { - outline: 2px solid #154273 !important; + outline: 2px solid var(--dark-blue) !important; outline-offset: 2px !important; - border-color: #154273 !important; + border-color: var(--dark-blue) !important; } /* Focus states for navigation links */ .md-nav__link:focus { - outline: 2px solid #154273 !important; + outline: 2px solid var(--dark-blue) !important; outline-offset: 2px !important; background-color: rgba(21, 66, 115, 0.1) !important; } -/* Focus states for buttons */ -button:focus, .md-header__button:focus { - outline: 2px solid #154273 !important; - outline-offset: 2px !important; -} - /* Focus states for links in content */ .md-content a:focus { - outline: 2px solid #154273 !important; + outline: 2px solid var(--dark-blue) !important; outline-offset: 2px !important; background-color: rgba(21, 66, 115, 0.1) !important; } @@ -257,7 +239,7 @@ abbr[data-tooltip]:focus, abbr[title]:focus, [title]:focus, .md-typeset abbr:focus { - outline: 2px solid #154273 !important; + outline: 2px solid var(--dark-blue) !important; outline-offset: 2px !important; background-color: rgba(21, 66, 115, 0.15) !important; position: relative; @@ -269,7 +251,7 @@ abbr[data-tooltip], abbr[title], [title]:not(img):not(.md-header__button):not(.md-nav__button):not(.md-search__button) { cursor: help; - border-bottom: 1px dotted #154273; + border-bottom: 1px dotted var(--dark-blue); text-decoration: none; position: relative; } @@ -282,9 +264,9 @@ abbr[title]:focus, [title]:not(img):not(.md-header__button):not(.md-nav__button):not(.md-search__button):hover, [title]:not(img):not(.md-header__button):not(.md-nav__button):not(.md-search__button):focus { background-color: rgba(21, 66, 115, 0.15) !important; - border-bottom: 2px solid #154273 !important; + border-bottom: 2px solid var(--dark-blue) !important; border-radius: 2px; - color: #154273 !important; + color: var(--dark-blue) !important; } /* Stronger focus indicators for keyboard users */ @@ -292,14 +274,14 @@ abbr[title]:focus, [title][tabindex]:focus, abbr[title][tabindex="0"]:focus, abbr[title][tabindex]:focus { - outline: 3px solid #154273 !important; + outline: 3px solid var(--dark-blue) !important; outline-offset: 1px !important; background-color: rgba(21, 66, 115, 0.2) !important; } /* Specific styling for MkDocs abbreviations from begrippenlijst */ .md-typeset abbr[data-tooltip] { - border-bottom: 1px dotted #154273 !important; + border-bottom: 1px dotted var(--dark-blue) !important; text-decoration: none !important; cursor: help !important; position: relative; @@ -308,16 +290,16 @@ abbr[title][tabindex]:focus { .md-typeset abbr[data-tooltip]:hover, .md-typeset abbr[data-tooltip]:focus { background-color: rgba(21, 66, 115, 0.15) !important; - border-bottom: 2px solid #154273 !important; + border-bottom: 2px solid var(--dark-blue) !important; border-radius: 2px; - color: #154273 !important; - outline: 2px solid #154273 !important; + color: var(--dark-blue) !important; + outline: 2px solid var(--dark-blue) !important; outline-offset: 2px !important; } /* Also target general elements with title attribute in md-typeset */ .md-typeset [title]:not(img):not(button):not(.md-header__button):not(.md-nav__button) { - border-bottom: 1px dotted #154273; + border-bottom: 1px dotted var(--dark-blue); cursor: help; position: relative; } @@ -325,10 +307,10 @@ abbr[title][tabindex]:focus { .md-typeset [title]:not(img):not(button):not(.md-header__button):not(.md-nav__button):hover, .md-typeset [title]:not(img):not(button):not(.md-header__button):not(.md-nav__button):focus { background-color: rgba(21, 66, 115, 0.15) !important; - border-bottom: 2px solid #154273 !important; + border-bottom: 2px solid var(--dark-blue) !important; border-radius: 2px; - color: #154273 !important; - outline: 2px solid #154273 !important; + color: var(--dark-blue) !important; + outline: 2px solid var(--dark-blue) !important; outline-offset: 2px !important; } @@ -337,7 +319,7 @@ a[href*="github.com"]:focus, a[href*="GitHub"]:focus, a[target="_blank"]:focus, .is-external-link-icon:focus { - outline: 2px solid #154273 !important; + outline: 2px solid var(--dark-blue) !important; outline-offset: 2px !important; background-color: rgba(21, 66, 115, 0.1) !important; border-radius: 2px; @@ -345,7 +327,7 @@ a[target="_blank"]:focus, /* Fix filter tag styling for better visibility */ .choices__list--multiple .choices__item { - background-color: #154273 !important; + background-color: var(--dark-blue) !important; border: 1px solid #123456 !important; color: white !important; font-weight: 500 !important; @@ -368,13 +350,13 @@ a[target="_blank"]:focus, .choices__input { background-color: transparent !important; - color: #154273 !important; + color: var(--dark-blue) !important; } .md-search__input{ background: #fff !important; - color: #154273 !important; - border: #154273 solid 1px !important; + color: var(--dark-blue) !important; + border: var(--dark-blue) solid 1px !important; border-radius: 5px !important; font-size: 1rem !important; padding: 8px 12px 8px 40px !important; /* Left padding for icon space */ @@ -384,18 +366,18 @@ a[target="_blank"]:focus, } .md-search__form{ - color: #154273; + color: var(--dark-blue); } .md-search__input::placeholder { - color: #333333; /* Set your desired color */ + color: var(--dark-gray); /* Set your desired color */ opacity: 1; /* Ensures the color appears as expected */ font-size: 1rem !important; /* Larger placeholder text */ font-family: "ROsanswebtextregular" !important; } .md-search__input+.md-search__icon{ - color: #333333; + color: var(--dark-gray); width: 18px !important; height: 18px !important; position: absolute !important; @@ -405,7 +387,7 @@ a[target="_blank"]:focus, } .md-search__suggest{ - color: #154273; + color: var(--dark-blue); } .md-search__overlay{ @@ -428,47 +410,47 @@ a[target="_blank"]:focus, .md-search-result__title, .md-search-result h1, .md-search-result h2 { - font-size: 1rem !important; - font-weight: 600 !important; - color: #154273 !important; + font-size: 1rem; + font-weight: 600; + color: var(--dark-blue); text-decoration: none; - line-height: 1.4 !important; - margin: 4px 0 4px 0 !important; - padding: 0 !important; + line-height: 1.4; + margin: 4px 0 4px 0; + padding: 0; display: block; - background: none !important; - border: none !important; - pointer-events: auto !important; - cursor: pointer !important; + background: none; + border: none; + pointer-events: auto; + cursor: pointer; } .md-search-result__title:hover, .md-search-result h1:hover, .md-search-result h2:hover { - color: #0066a3 !important; + color: #0066a3; } .md-search-result__article { font-size: 0.984375rem; - color: #154273; + color: var(--dark-blue); margin-bottom: 4px; - text-decoration: none !important; - pointer-events: none !important; - cursor: default !important; + text-decoration: none; + pointer-events: none; + cursor: default; } .md-search-result__text { font-size: 0.984375rem; - color: #154273; + color: var(--dark-blue); line-height: 1.4; } .md-search-result__meta { background-color: var(--md-default-fg-color--lightest); - color: #154273 !important; - font-size: 0.984375rem !important; - line-height: 1.4 !important; - padding: 16px 12px 8px 12px !important; + color: var(--dark-blue); + font-size: 0.984375rem; + line-height: 1.4; + padding: 16px 12px 8px 12px; scroll-snap-align: start; } @@ -477,53 +459,41 @@ a[target="_blank"]:focus, .md-search-result a, .md-search-result__text a, .md-search-result__article a { - font-size: 0.984375rem !important; - line-height: 1.4 !important; - margin: 0 !important; - color: #154273 !important; - text-decoration: none !important; - pointer-events: none !important; - cursor: default !important; + font-size: 0.984375rem; + line-height: 1.4; + margin: 0; + color: var(--dark-blue); + text-decoration: none; + pointer-events: none; + cursor: default; } .md-search-result__more, .md-search-result__more div, .md-search-result__more summary { - font-size: 0.984375rem !important; - line-height: 1.4 !important; - color: #154273 !important; - pointer-events: none !important; - cursor: default !important; + font-size: 0.984375rem; + line-height: 1.4; + color: var(--dark-blue); + pointer-events: none; + cursor: default; } .md-search-result__more details { - pointer-events: none !important; + pointer-events: none; } .md-header__source{ - color: #154273; + color: var(--dark-blue); } /* Search always uses mobile-style positioning */ -/* Align navigation tabs container with main content layout */ -.md-tabs { - width: 100%; -} - -.md-tabs .md-grid { - display: flex; - align-items: center; - width: 100%; - box-sizing: border-box; -} - .md-typeset a { - color: #154273; + color: var(--dark-blue); } .md-ellipsis{ - color: #154273; + color: var(--dark-blue); } :root { @@ -594,13 +564,8 @@ abbr[data-tooltip]:focus::before { pointer-events: none; } -/* Hide the native tooltip by removing title on hover and restoring on mouse leave */ -abbr[data-tooltip]:hover { - /* Custom tooltip replaces native one */ -} - .md-nav__title{ - color: #154273; + color: var(--dark-blue); } .md-nav--primary .md-nav__title[for=__drawer] { @@ -616,19 +581,19 @@ abbr[data-tooltip]:hover { } .md-source__repository .md-source__repository--active{ - color: #154273; + color: var(--dark-blue); } @media screen and (max-width: 800px) { .md-nav--primary .md-nav__item--active>.md-nav__link { - color: #154273; + color: var(--dark-blue); } } @media screen and (max-width: 800px) { .md-nav--primary .md-nav__title { background-color: #fff; - color: #154273; + color: var(--dark-blue); cursor: pointer; height: 5.6rem; line-height: 2.4rem; @@ -692,25 +657,11 @@ abbr[data-tooltip]:hover { } } -/* NUCLEAR CSS - Hide hamburger menu above 800px with maximum specificity */ +/* Hide default hamburger menu above 800px */ @media screen and (min-width: 801px) { - html body .md-container .md-header .md-header__inner .md-header__button[for="__drawer"], - html body .md-container .md-header .md-header__inner .md-header__button.md-icon--menu, - html body .md-container .md-header .md-header__inner .md-nav__button, - html body .md-container .md-header .md-header__inner button[for="__drawer"], - html body .md-container .md-header button, - html body .md-header button[type="button"] { + label[for="__drawer"]:not(.mobile-menu-button), + .md-header__button[for="__drawer"] { display: none !important; - visibility: hidden !important; - opacity: 0 !important; - pointer-events: none !important; - position: absolute !important; - left: -9999px !important; - top: -9999px !important; - width: 0 !important; - height: 0 !important; - overflow: hidden !important; - clip: rect(0,0,0,0) !important; } } diff --git a/mkdocs.yml b/mkdocs.yml index ed32fe3f6f..1264795c08 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -124,7 +124,7 @@ extra_javascript: - javascripts/mermaid.min.js - javascripts/modal.js - javascripts/extra.js - - javascripts/hamburger-control.js + plugins: - glightbox diff --git a/src/overrides/hooks/definities.py b/src/overrides/hooks/definities.py index 0bf7a337d5..fa6ea252de 100644 --- a/src/overrides/hooks/definities.py +++ b/src/overrides/hooks/definities.py @@ -5,8 +5,6 @@ import os import re from mkdocs.config.defaults import MkDocsConfig -from mkdocs.structure.files import Files -from mkdocs.structure.pages import Page def on_pre_build(config: MkDocsConfig) -> None: @@ -88,19 +86,3 @@ def on_pre_build(config: MkDocsConfig) -> None: except Exception as e: print(f"Error generating definitions.md: {e}") - - -def on_files(files: Files, config: MkDocsConfig) -> Files: - """ - Called after files are collected but before they are processed - """ - return files - - -def on_page_markdown( - markdown: str, page: Page, config: MkDocsConfig, files: Files -) -> str: - """ - Called for each page after markdown is loaded but before it's converted to HTML - """ - return markdown diff --git a/src/overrides/hooks/lists.py b/src/overrides/hooks/lists.py index 00b30ec282..fa2fab589c 100644 --- a/src/overrides/hooks/lists.py +++ b/src/overrides/hooks/lists.py @@ -1,4 +1,6 @@ from typing import List, Dict, Callable, Optional +import json +import logging import posixpath import re import os @@ -6,6 +8,8 @@ from mkdocs.structure.files import File, Files from re import Match +log = logging.getLogger("mkdocs.hooks.lists") + # Cache for abbreviations to avoid reading file multiple times _abbreviations_cache = None _current_config = None @@ -42,7 +46,7 @@ def _load_abbreviations(config: MkDocsConfig) -> Dict[str, str]: _abbreviations_cache = abbreviations return abbreviations except Exception as e: - print(f"Error loading abbreviations: {e}") + log.error("Error loading abbreviations: %s", e, exc_info=True) break _abbreviations_cache = {} @@ -421,7 +425,7 @@ def on_env(env, config: MkDocsConfig, files: Files): def generate_filters( content_type: str, - list: List[File], + item_list: List[File], filter_options: Dict[str, bool], current_file: File, column_config: Optional[List[ColumnConfig]] = None, @@ -442,7 +446,7 @@ def generate_filters( filter_options.get(column.key, column.default_enabled) and column.render_filter ): - filter_html = column.render_filter(list, filter_options) + filter_html = column.render_filter(item_list, filter_options) if filter_html: # Only add if filter produces output column_filters_html.extend(filter_html) @@ -455,9 +459,9 @@ def generate_filters( # Only add wrapper div if there are actually filters to show if has_any_filters: - wrapper_class = "filter-wrapper" + wrapper_class = "filter-wrapper filter-container" filters.append( - f'
' + f'
' ) filters.append('
') @@ -503,23 +507,23 @@ def generate_filters( filters.extend( [ '
', - '
', + '
', "
", - f'Er zijn {len(list)} resultaten gevonden', + f'Er zijn {len(item_list)} resultaten gevonden', "
", "
", - '
', - '", - '", "
", @@ -566,7 +570,7 @@ def replace_content(match: Match, content_type: str, current_file: File): if filter_name in filter_options: filter_options[filter_name] = True - list: List[File] = [] + item_list: List[File] = [] for file in files: if not file.src_path.startswith( f"voldoen-aan-wetten-en-regels/{content_type}/" @@ -578,10 +582,10 @@ def replace_content(match: Match, content_type: str, current_file: File): continue if not type_value_bundle or all( - value in file.page.meta.get(type, []) - for type, value in type_value_bundle + value in file.page.meta.get(filter_type, []) + for filter_type, value in type_value_bundle ): - list.append(file) + item_list.append(file) # Only enable ai-act-labels if we're on the main vereisten page AND this is a vereisten list if ( @@ -593,7 +597,7 @@ def replace_content(match: Match, content_type: str, current_file: File): filter_options["ai-act-labels"] = False filters = generate_filters( - content_type, list, filter_options, current_file, column_config + content_type, item_list, filter_options, current_file, column_config ) # Generate table headers dynamically based on column configuration @@ -619,8 +623,6 @@ def replace_content(match: Match, content_type: str, current_file: File): for idx, col in enumerate(enabled_columns) } - import json - column_mapping_json = json.dumps(column_mapping) result = "".join( @@ -638,7 +640,7 @@ def replace_content(match: Match, content_type: str, current_file: File): _create_table_row_2( item, filter_options, current_file, config, column_config ) - for item in list + for item in item_list ], "", "", @@ -648,112 +650,65 @@ def replace_content(match: Match, content_type: str, current_file: File): return result - # NEW FUNCTION: To generate the Vereisten for a specific maatregel - def generate_vereisten_for_maatregel(file: File) -> str: - vereisten = file.page.meta.get("vereiste", []) - if not vereisten: - return "

Geen vereisten beschikbaar voor deze maatregel.

" + def generate_related_items_table(file: File, meta_key: str, headers: List[str], content_type: str) -> str: + """Generic function to generate a related items table (vereisten or maatregelen).""" + items = file.page.meta.get(meta_key, []) + if not items: + return f"

Geen {headers[1].lower()} beschikbaar.

" - vereisten_table = [ + table_rows = [ "", "", "", - "", - "", + f"", + f"", "", "", "", ] - for vereiste in vereisten: - vereiste_file = find_file_by_name(vereiste, "vereisten", files) - if vereiste_file: - # Retrieve the title from the vereiste file's metadata - vereiste_id = vereiste_file.page.meta.get("id", "")[ - 14: - ] # remove the first part of the urn - vereiste_title = vereiste_file.page.meta.get( - "title", vereiste - ) # Fallback to vereiste name if no title - vereiste_link = posixpath.join( - config.site_url or "/", vereiste_file.url - ) - vereisten_table.append( - f'' + for item_name in items: + matched_file = find_file_by_name(item_name, content_type, files) + if matched_file: + item_id = matched_file.page.meta.get("id", "")[14:] # remove the first part of the urn + item_title = matched_file.page.meta.get("title", item_name) # Fallback to name if no title + item_link = posixpath.join(config.site_url or "/", matched_file.url) + table_rows.append( + f'' ) else: - vereisten_table.append( - f"" + table_rows.append( + f"" ) # No link if the file is not found - vereisten_table.append("
IDVereiste{headers[0]}{headers[1]}
{vereiste_id}{vereiste_title}
{item_id}{item_title}
{vereiste}
{item_name}
") + table_rows.append("") - return "".join(vereisten_table) + return "".join(table_rows) - # NEW FUNCTION: To generate the Maatregelen for a specific Hulpmiddel - def generate_maatregelen_for_hulpmiddel(file: File) -> str: - maatregelen = file.page.meta.get("maatregel", []) - if not maatregelen: - return "

Geen maatregelen beschikbaar voor dit hulpmiddel.

" - - maatregelen_table = [ - "", - "", - "", - "", - "", - "", - "", - "", - ] - for maatregel in maatregelen: - maatregel_file = find_file_by_name(maatregel, "maatregelen", files) - if maatregel_file: - # Retrieve the title from the maatregel file's metadata - maatregel_id = maatregel_file.page.meta.get("id", "")[ - 14: - ] # remove the first part of the urn - maatregel_title = maatregel_file.page.meta.get( - "title", maatregel - ) # Fallback to maatregel name if no title - maatregel_link = posixpath.join( - config.site_url or "/", maatregel_file.url - ) - maatregelen_table.append( - f'' - ) - else: - maatregelen_table.append( - f"" - ) # No link if the file is not found - - maatregelen_table.append("
IDMaatregel
{maatregel_id}{maatregel_title}
{maatregel}
") - - return "".join(maatregelen_table) + # Build a file index for fast lookups by (content_type, basename) + _file_index: Dict[tuple, File] = {} + for f in files: + if f.src_path.startswith("voldoen-aan-wetten-en-regels/"): + parts = f.src_path.split("/") + if len(parts) >= 3: + idx_content_type = parts[1] + basename = posixpath.splitext(posixpath.basename(f.src_path))[0] + _file_index[(idx_content_type, basename)] = f def find_file_by_name(name: str, content_type: str, files: Files) -> File: - for file in files: - file_name = posixpath.splitext(posixpath.basename(file.src_path))[0] - if ( - file.src_path.startswith( - f"voldoen-aan-wetten-en-regels/{content_type}/" - ) - and file_name == name - ): - return file - return None + return _file_index.get((content_type, name)) def replace_vereisten_content(file: File): file.page.content = re.sub( r"", - lambda match: generate_vereisten_for_maatregel(file), + lambda match: generate_related_items_table(file, "vereiste", ["ID", "Vereiste"], "vereisten"), file.page.content, ) def replace_maatregelen_content(file: File): file.page.content = re.sub( r"", - lambda match: generate_maatregelen_for_hulpmiddel(file), + lambda match: generate_related_items_table(file, "maatregel", ["ID", "Maatregel"], "maatregelen"), file.page.content, ) @@ -768,7 +723,7 @@ def replace_maatregelen_content(file: File): else: continue - print("Processing file", file.src_path) + log.debug("Processing file %s", file.src_path) try: if "maatregelen" in file.src_path or "hulpmiddelen" in file.src_path: @@ -799,4 +754,4 @@ def replace_maatregelen_content(file: File): flags=re.I | re.M, ) except Exception as e: - print(e) + log.error("Error processing file %s: %s", file.src_path, e, exc_info=True) diff --git a/src/overrides/hooks/security_txt.py b/src/overrides/hooks/security_txt.py index c609e3220e..4b67c6211f 100644 --- a/src/overrides/hooks/security_txt.py +++ b/src/overrides/hooks/security_txt.py @@ -11,7 +11,7 @@ import os import urllib.request import urllib.error -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from mkdocs.config.defaults import MkDocsConfig @@ -87,7 +87,7 @@ def _fetch_ncsc_security_txt(url: str, canonical_url: str) -> str: return "\n".join(updated_lines) - except (urllib.error.URLError, urllib.error.HTTPError, Exception) as e: + except Exception as e: print(f" Error fetching NCSC security.txt: {e}") return "" @@ -100,7 +100,7 @@ def _generate_fallback_security_txt(canonical_url: str) -> str: The fallback security.txt content. """ # Set expiry to 1 year from now - expires_date = (datetime.utcnow() + timedelta(days=365)).strftime( + expires_date = (datetime.now(timezone.utc) + timedelta(days=365)).strftime( "%Y-%m-%dT%H:%M:%SZ" ) diff --git a/src/overrides/hooks/tags.py b/src/overrides/hooks/tags.py index 85b2a86ae5..19c3c573de 100644 --- a/src/overrides/hooks/tags.py +++ b/src/overrides/hooks/tags.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging import posixpath import re @@ -8,6 +9,61 @@ from mkdocs.structure.pages import Page from re import Match +log = logging.getLogger("mkdocs.hooks.tags") + +# ----------------------------------------------------------------------------- +# Badge configuration +# ----------------------------------------------------------------------------- + +# For simple badge types that follow the pattern: +# icon_path = "{category}/index.md" +# text_path = "{category}/{value}.md" +# text = value.capitalize().replace('-', ' ') +BADGE_CONFIG = { + "levenscyclus": { + "icon": "material-reload", + "color": "indigo", + "icon_path": "levenscyclus/index.md", + "text_path": "levenscyclus/{value}.md", + "title": "Levencyclus", + }, + "rollen": { + "icon": "material-account", + "color": "deep-orange", + "icon_path": "rollen/index.md", + "text_path": "rollen/{value}.md", + "title": "Rollen", + }, + "onderwerp": { + "icon": "material-lightbulb", + "color": "teal", + "icon_path": "onderwerpen/index.md", + "text_path": "onderwerpen/{value}.md", + "title": "Onderwerp", + }, + "transparantieverplichting": { + "icon": "material-magnify", + "color": "blue", + "icon_path": "soorten-algoritmes-en-ai/risico-van-ai-systemen.md#risico-op-misleiding", + "text_path": "soorten-algoritmes-en-ai/risico-van-ai-systemen.md#risico-op-misleiding", + "title": "Transparantieverplichting AI-verordening", + }, + "systeemrisico": { + "icon": "material-network", + "color": "blue", + "icon_path": "ai-verordening/ai-verordening-in-het-kort.md#ai-model-voor-algemene-doeleinden", + "text_path": "ai-verordening/ai-verordening-in-het-kort.md#ai-model-voor-algemene-doeleinden", + "title": "Systeemrisico AI-verordening", + }, +} + +# Special text path mappings for soort-toepassing +_SOORT_TOEPASSING_PATHS = { + "ai-systeem": "ai-verordening/ai-verordening-in-het-kort.md#ai-systeem", + "ai-systeem-voor-algemene-doeleinden": "ai-verordening/ai-verordening-in-het-kort.md#ai-model-voor-algemene-doeleinden", + "ai-model-voor-algemene-doeleinden": "ai-verordening/ai-verordening-in-het-kort.md#ai-model-voor-algemene-doeleinden", +} + # ----------------------------------------------------------------------------- # Hooks # ----------------------------------------------------------------------------- @@ -18,34 +74,34 @@ def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: # Replace callback def replace(_: Match): buttons = [] - for type in ["id", "levenscyclus", "rollen", "onderwerp"]: - field = page.meta.get(type, []) + for tag_type in ["id", "levenscyclus", "rollen", "onderwerp"]: + field = page.meta.get(tag_type, []) if isinstance(field, str): - buttons.append(flag(type, field, page, files)) + buttons.append(flag(tag_type, field, page, files)) else: for role in field: - buttons.append(flag(type, role, page, files)) + buttons.append(flag(tag_type, role, page, files)) return "".join(buttons) # Replace callback def replace_ai_act(_: Match): buttons = [] - for type in [ + for tag_type in [ "soort-toepassing", "risicogroep", "rol-ai-act", "systeemrisico", "transparantieverplichting", ]: - field = page.meta.get(type, []) + field = page.meta.get(tag_type, []) if isinstance(field, str): if ( field != "niet-van-toepassing" and field != "uitzondering-van-toepassing" ): # Skip niet-van-toepassing and skip uitzondering van toepassing - buttons.append(flag(type, field, page, files)) + buttons.append(flag(tag_type, field, page, files)) else: for role in field: # Skip niet-van-toepassing or uitzondering-van-toepassing @@ -55,10 +111,11 @@ def replace_ai_act(_: Match): ): continue - buttons.append(flag(type, role, page, files)) + buttons.append(flag(tag_type, role, page, files)) # for non-aia vereisten, remove the buttons and add other toelichting - if not page.meta.get("id").startswith("urn:nl:ak:ver:aia-"): + page_id = page.meta.get("id") + if not page_id or not page_id.startswith("urn:nl:ak:ver:aia-"): toelichting = "Deze vereiste geldt waarschijnlijk voor jouw algoritmische toepassingen. Bekijk de [bronnen](#bronnen) om te controleren of dit zo is." buttons = toelichting else: # for aia vereisten, add toelichting to the buttons @@ -90,26 +147,42 @@ def replace_ai_act(_: Match): # ----------------------------------------------------------------------------- -# Create a flag of a specific type -def flag(type: str, arg: str, page: Page, files: Files): - if type == "id": +def _badge_generic(tag_type: str, value: str, page: Page, files: Files) -> str: + """Generate a badge from the BADGE_CONFIG dictionary.""" + cfg = BADGE_CONFIG.get(tag_type) + if not cfg: + return "" + icon = cfg["icon"] + color = cfg["color"] + title = cfg["title"] + icon_path = cfg["icon_path"] + text_path = cfg["text_path"].format(value=value) + + href_icon = _resolve_path(icon_path, page, files) + href_text = _resolve_path(text_path, page, files) + display_text = value.capitalize().replace("-", " ") + return _badge( + icon=f"[:{icon}:]({href_icon} '{title}')", + text=f"[{display_text}]({href_text})", + color=color, + ) + + +# Create a flag of a specific tag_type +def flag(tag_type: str, arg: str, page: Page, files: Files): + # Handle types in BADGE_CONFIG with the generic approach + if tag_type in BADGE_CONFIG: + return _badge_generic(tag_type, arg, page, files) + + # Handle special types that need custom logic + if tag_type == "id": return _badge_id(page, files, arg) - elif type == "levenscyclus": - return _badge_levenscyclus(page, files, arg) - elif type == "rollen": - return _badge_rollen(page, files, arg) - elif type == "onderwerp": - return _badge_onderwerp(page, files, arg) - elif type == "soort-toepassing": + elif tag_type == "soort-toepassing": return _badge_soort_toepassing(page, files, arg) - elif type == "risicogroep": + elif tag_type == "risicogroep": return _badge_risicogroep(page, files, arg) - elif type == "rol-ai-act": + elif tag_type == "rol-ai-act": return _badge_rol_ai_act(page, files, arg) - elif type == "transparantieverplichting": - return _badge_transparantieverplichting(page, files, arg) - elif type == "systeemrisico": - return _badge_systeemrisico(page, files, arg) return "" @@ -128,8 +201,10 @@ def _resolve_path(path: str, page: Page, files: Files): def _resolve(file: File, page: Page): # Ensure file and page.file are valid objects if not file or not page.file: - print( - f"Error: Invalid file or page when resolving. file={file}, page.file={page.file}" + log.warning( + "Invalid file or page when resolving. file=%s, page.file=%s", + file, + page.file, ) return "" @@ -152,8 +227,8 @@ def _resolve(file: File, page: Page): # Create badge - keep original system but remove icon link, keep text link -def _badge(icon: str, text: str = "", type: str = "", color: str = "blue"): - classes = f"mdx-badge mdx-badge--{type}" if type else "mdx-badge" +def _badge(icon: str, text: str = "", tag_type: str = "", color: str = "blue"): + classes = f"mdx-badge mdx-badge--{tag_type}" if tag_type else "mdx-badge" # Extract just the icon name without the link icon_content = icon @@ -184,42 +259,6 @@ def _badge_id(page: Page, files: Files, phase: str): ) -# Create badge for levenscyclus -def _badge_levenscyclus(page: Page, files: Files, phase: str): - icon = "material-reload" - href_levenscyclus = _resolve_path("levenscyclus/index.md", page, files) - href_fase = _resolve_path(f"levenscyclus/{phase}.md", page, files) - return _badge( - icon=f"[:{icon}:]({href_levenscyclus} 'Levencyclus')", - text=f"[{phase.capitalize().replace('-', ' ')}]({href_fase})", - color="indigo", - ) - - -# Create badge for rollen -def _badge_rollen(page: Page, files: Files, rol: str): - icon = "material-account" - href_rol = _resolve_path("rollen/index.md", page, files) - href_fase = _resolve_path(f"rollen/{rol}.md", page, files) - return _badge( - icon=f"[:{icon}:]({href_rol} 'Rollen')", - text=f"[{rol.capitalize().replace('-', ' ')}]({href_fase})", - color="deep-orange", - ) - - -# Create badge for onderwerp -def _badge_onderwerp(page: Page, files: Files, blok: str): - icon = "material-lightbulb" - href_onderwerp = _resolve_path("onderwerpen/index.md", page, files) - href_fase = _resolve_path(f"onderwerpen/{blok}.md", page, files) - return _badge( - icon=f"[:{icon}:]({href_onderwerp} 'Onderwerp')", - text=f"[{blok.capitalize().replace('-', ' ')}]({href_fase})", - color="teal", - ) - - # Create badge for soort-toepassing def _badge_soort_toepassing(page: Page, files: Files, soort: str): icon = "material-graph" @@ -227,22 +266,8 @@ def _badge_soort_toepassing(page: Page, files: Files, soort: str): "soorten-algoritmes-en-ai/wat-is-een-algoritme.md", page, files ) href_fase = href_soort_toepassing - if soort == "ai-systeem": - href_fase = _resolve_path( - "ai-verordening/ai-verordening-in-het-kort.md#ai-systeem", page, files - ) - elif soort == "ai-systeem-voor-algemene-doeleinden": - href_fase = _resolve_path( - "ai-verordening/ai-verordening-in-het-kort.md#ai-model-voor-algemene-doeleinden", - page, - files, - ) - elif soort == "ai-model-voor-algemene-doeleinden": - href_fase = _resolve_path( - "ai-verordening/ai-verordening-in-het-kort.md#ai-model-voor-algemene-doeleinden", - page, - files, - ) + if soort in _SOORT_TOEPASSING_PATHS: + href_fase = _resolve_path(_SOORT_TOEPASSING_PATHS[soort], page, files) return _badge( icon=f"[:{icon}:]({href_soort_toepassing} 'Soort toepassing')", @@ -297,33 +322,3 @@ def _badge_rol_ai_act(page: Page, files: Files, rol: str): text=f"[{rol.capitalize().replace('-', ' ')}]({href_fase})", color="blue", ) - - -# Create badge for transparantieverplichting -def _badge_transparantieverplichting(page: Page, files: Files, rol: str): - icon = "material-magnify" - href_transparantieverplichting = _resolve_path( - "soorten-algoritmes-en-ai/risico-van-ai-systemen.md#risico-op-misleiding", - page, - files, - ) - return _badge( - icon=f"[:{icon}:]({href_transparantieverplichting} 'Transparantieverplichting AI-verordening')", - text=f"[{rol.capitalize().replace('-', ' ')}]({href_transparantieverplichting})", - color="blue", - ) - - -# Create badge for systeemrisico -def _badge_systeemrisico(page: Page, files: Files, rol: str): - icon = "material-network" - href_systeemrisico = _resolve_path( - "ai-verordening/ai-verordening-in-het-kort.md#ai-model-voor-algemene-doeleinden", - page, - files, - ) - return _badge( - icon=f"[:{icon}:]({href_systeemrisico} 'Systeemrisico AI-verordening')", - text=f"[{rol.capitalize().replace('-', ' ')}]({href_systeemrisico})", - color="blue", - ) diff --git a/src/overrides/partials/breadcrumbs.html b/src/overrides/partials/breadcrumbs.html index 6c6365ef51..36f01c0036 100644 --- a/src/overrides/partials/breadcrumbs.html +++ b/src/overrides/partials/breadcrumbs.html @@ -1,3 +1,5 @@ +{# NOTE: This template may be unused. It is not explicitly referenced in mkdocs.yml, Python hooks, + or other templates. It may be auto-loaded by MkDocs Material as a partial override. #}