feat: add route-aware programme navigation
This commit is contained in:
parent
6c3900a1d4
commit
8c471fc0fa
12 changed files with 234 additions and 14 deletions
|
|
@ -28,6 +28,9 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
favicon: '/favicon.ico',
|
favicon: '/favicon.ico',
|
||||||
customCss: ['./src/styles/incognito.css'],
|
customCss: ['./src/styles/incognito.css'],
|
||||||
|
components: {
|
||||||
|
Sidebar: './src/components/Sidebar.astro',
|
||||||
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
processedDirs: ['./src/content/shared-courses/'],
|
processedDirs: ['./src/content/shared-courses/'],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,15 @@ start https://msvincognito.nl/wiki/start represented src/content/docs/index.mdx
|
||||||
study https://msvincognito.nl/wiki/study represented src/content/docs/index.mdx to-be-studied/previous-wiki/2026-08-03/pages/study.txt Already represented by the selected export migration.
|
study https://msvincognito.nl/wiki/study represented src/content/docs/index.mdx to-be-studied/previous-wiki/2026-08-03/pages/study.txt Already represented by the selected export migration.
|
||||||
study:bachelor https://msvincognito.nl/wiki/study/bachelor represented src/content/docs/data-science-and-ai/index.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor.txt Already represented by the selected export migration.
|
study:bachelor https://msvincognito.nl/wiki/study/bachelor represented src/content/docs/data-science-and-ai/index.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor.txt Already represented by the selected export migration.
|
||||||
study:bachelor:year_1 https://msvincognito.nl/wiki/study/bachelor/year_1 represented src/content/docs/data-science-and-ai/year-1/index.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1.txt Already represented by the selected export migration.
|
study:bachelor:year_1 https://msvincognito.nl/wiki/study/bachelor/year_1 represented src/content/docs/data-science-and-ai/year-1/index.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1.txt Already represented by the selected export migration.
|
||||||
study:bachelor:year_1:block_1:discrete_mathematics https://msvincognito.nl/wiki/study/bachelor/year_1/block_1/discrete_mathematics migrated src/content/docs/data-science-and-ai/year-1/block-1/discrete-mathematics.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_1/discrete_mathematics.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_1:discrete_mathematics https://msvincognito.nl/wiki/study/bachelor/year_1/block_1/discrete_mathematics migrated src/content/docs/data-science-and-ai/year-1/block-1/discrete-mathematics.mdx to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_1/discrete_mathematics.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
study:bachelor:year_1:block_1:introduction_to_data_science_and_artifical_intelligence https://msvincognito.nl/wiki/study/bachelor/year_1/block_1/introduction_to_data_science_and_artifical_intelligence migrated src/content/docs/data-science-and-ai/year-1/block-1/introduction-to-data-science-and-artifical-intelligence.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_1/introduction_to_data_science_and_artifical_intelligence.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_1:introduction_to_data_science_and_artifical_intelligence https://msvincognito.nl/wiki/study/bachelor/year_1/block_1/introduction_to_data_science_and_artifical_intelligence migrated src/content/docs/data-science-and-ai/year-1/block-1/introduction-to-data-science-and-artifical-intelligence.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_1/introduction_to_data_science_and_artifical_intelligence.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
study:bachelor:year_1:block_1:procedural_programming https://msvincognito.nl/wiki/study/bachelor/year_1/block_1/procedural_programming migrated src/content/docs/data-science-and-ai/year-1/block-1/procedural-programming.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_1/procedural_programming.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_1:procedural_programming https://msvincognito.nl/wiki/study/bachelor/year_1/block_1/procedural_programming migrated src/content/docs/data-science-and-ai/year-1/block-1/procedural-programming.mdx to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_1/procedural_programming.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
study:bachelor:year_1:block_2:calculus https://msvincognito.nl/wiki/study/bachelor/year_1/block_2/calculus migrated src/content/docs/data-science-and-ai/year-1/block-2/calculus.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_2/calculus.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_2:calculus https://msvincognito.nl/wiki/study/bachelor/year_1/block_2/calculus migrated src/content/docs/data-science-and-ai/year-1/block-2/calculus.mdx to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_2/calculus.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
study:bachelor:year_1:block_2:logic https://msvincognito.nl/wiki/study/bachelor/year_1/block_2/logic migrated src/content/docs/data-science-and-ai/year-1/block-2/logic.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_2/logic.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_2:logic https://msvincognito.nl/wiki/study/bachelor/year_1/block_2/logic migrated src/content/docs/data-science-and-ai/year-1/block-2/logic.mdx to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_2/logic.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
study:bachelor:year_1:block_2:objects_in_programming https://msvincognito.nl/wiki/study/bachelor/year_1/block_2/objects_in_programming migrated src/content/docs/data-science-and-ai/year-1/block-2/objects-in-programming.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_2/objects_in_programming.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_2:objects_in_programming https://msvincognito.nl/wiki/study/bachelor/year_1/block_2/objects_in_programming migrated src/content/docs/data-science-and-ai/year-1/block-2/objects-in-programming.mdx to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_2/objects_in_programming.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
study:bachelor:year_1:block_3:placeholder https://msvincognito.nl/wiki/study/bachelor/year_1/block_3/placeholder empty to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_3/placeholder.txt Explicit placeholder page with no course details.
|
study:bachelor:year_1:block_3:placeholder https://msvincognito.nl/wiki/study/bachelor/year_1/block_3/placeholder empty to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_3/placeholder.txt Explicit placeholder page with no course details.
|
||||||
study:bachelor:year_1:block_4:data_structures_and_algorithms https://msvincognito.nl/wiki/study/bachelor/year_1/block_4/data_structures_and_algorithms migrated src/content/docs/data-science-and-ai/year-1/block-4/data-structures-and-algorithms.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_4/data_structures_and_algorithms.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_4:data_structures_and_algorithms https://msvincognito.nl/wiki/study/bachelor/year_1/block_4/data_structures_and_algorithms migrated src/content/docs/data-science-and-ai/year-1/block-4/data-structures-and-algorithms.mdx to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_4/data_structures_and_algorithms.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
study:bachelor:year_1:block_4:linear_algebra https://msvincognito.nl/wiki/study/bachelor/year_1/block_4/linear_algebra migrated src/content/docs/data-science-and-ai/year-1/block-4/linear-algebra.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_4/linear_algebra.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_4:linear_algebra https://msvincognito.nl/wiki/study/bachelor/year_1/block_4/linear_algebra migrated src/content/docs/data-science-and-ai/year-1/block-4/linear-algebra.mdx to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_4/linear_algebra.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
study:bachelor:year_1:block_4:principles_of_data_science https://msvincognito.nl/wiki/study/bachelor/year_1/block_4/principles_of_data_science migrated src/content/docs/data-science-and-ai/year-1/block-4/principles-of-data-science.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_4/principles_of_data_science.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_4:principles_of_data_science https://msvincognito.nl/wiki/study/bachelor/year_1/block_4/principles_of_data_science migrated src/content/docs/data-science-and-ai/year-1/block-4/principles-of-data-science.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_4/principles_of_data_science.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
study:bachelor:year_1:block_5:computational_and_cognitive_neuroscience https://msvincognito.nl/wiki/study/bachelor/year_1/block_5/computational_and_cognitive_neuroscience migrated src/content/docs/data-science-and-ai/year-1/block-5/computational-and-cognitive-neuroscience.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_5/computational_and_cognitive_neuroscience.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_5:computational_and_cognitive_neuroscience https://msvincognito.nl/wiki/study/bachelor/year_1/block_5/computational_and_cognitive_neuroscience migrated src/content/docs/data-science-and-ai/year-1/block-5/computational-and-cognitive-neuroscience.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_5/computational_and_cognitive_neuroscience.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
study:bachelor:year_1:block_5:numerical_methods https://msvincognito.nl/wiki/study/bachelor/year_1/block_5/numerical_methods migrated src/content/docs/data-science-and-ai/year-1/block-5/numerical-methods.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_5/numerical_methods.txt Substantive public course page migrated from the live DokuWiki.
|
study:bachelor:year_1:block_5:numerical_methods https://msvincognito.nl/wiki/study/bachelor/year_1/block_5/numerical_methods migrated src/content/docs/data-science-and-ai/year-1/block-5/numerical-methods.md to-be-studied/previous-wiki/2026-08-03/pages/study/bachelor/year_1/block_5/numerical_methods.txt Substantive public course page migrated from the live DokuWiki.
|
||||||
|
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { mkdir, writeFile } from 'node:fs/promises';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
|
||||||
const PROGRAMMES = new Map([
|
const PROGRAMMES = new Map([
|
||||||
['bachelor', 'bachelor'],
|
['bachelor', 'data-science-and-ai'],
|
||||||
['master_ai', 'master-ai'],
|
['master_ai', 'master-ai'],
|
||||||
['master_dsdm', 'master-dsdm'],
|
['master_dsdm', 'master-dsdm'],
|
||||||
]);
|
]);
|
||||||
|
|
@ -42,7 +42,11 @@ export function destinationForPageId(pageId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const routeParts = [PROGRAMMES.get(parts[1]), ...parts.slice(2).map(slug)];
|
const routeParts = [PROGRAMMES.get(parts[1]), ...parts.slice(2).map(slug)];
|
||||||
return `src/content/docs/${routeParts.join('/')}.md`;
|
const sharedCourse = parts[1] === 'bachelor' && parts[2] === 'year_1' && new Set([
|
||||||
|
'discrete_mathematics', 'procedural_programming', 'calculus', 'logic',
|
||||||
|
'objects_in_programming', 'data_structures_and_algorithms', 'linear_algebra',
|
||||||
|
]).has(parts.at(-1));
|
||||||
|
return `src/content/docs/${routeParts.join('/')}.${sharedCourse ? 'mdx' : 'md'}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function classifyPreviousPage(pageId, representedPageIds = new Set(), source = '') {
|
export function classifyPreviousPage(pageId, representedPageIds = new Set(), source = '') {
|
||||||
|
|
@ -61,7 +65,7 @@ function humanLabel(value) {
|
||||||
function markdownRouteForPageId(pageId) {
|
function markdownRouteForPageId(pageId) {
|
||||||
const destination = destinationForPageId(pageId);
|
const destination = destinationForPageId(pageId);
|
||||||
return destination
|
return destination
|
||||||
? `/${destination.replace(/^src\/content\/docs\//, '').replace(/\.md$/, '')}/`
|
? `/${destination.replace(/^src\/content\/docs\//, '').replace(/\.mdx?$/, '')}/`
|
||||||
: `https://msvincognito.nl/wiki/${pageId.replaceAll(':', '/')}`;
|
: `https://msvincognito.nl/wiki/${pageId.replaceAll(':', '/')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
15
src/components/ProgrammeSwitch.astro
Normal file
15
src/components/ProgrammeSwitch.astro
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
import { programmeForPathname, programmeSwitchTargets } from '../config/programme-navigation.mjs';
|
||||||
|
|
||||||
|
const active = programmeForPathname(Astro.url.pathname.replace(import.meta.env.BASE_URL.replace(/\/$/, ''), ''));
|
||||||
|
const targets = programmeSwitchTargets(Astro.url.pathname);
|
||||||
|
const withBase = (path: string) => `${import.meta.env.BASE_URL}${path.replace(/^\//, '')}`;
|
||||||
|
---
|
||||||
|
|
||||||
|
<nav class="programme-switch" aria-label="Bachelor programme">
|
||||||
|
<span class="programme-switch__label">Course information</span>
|
||||||
|
<div class="programme-switch__links">
|
||||||
|
<a href={withBase(targets.dataScience)} aria-current={active === 'data-science-and-ai' ? 'page' : undefined}>Data Science & AI</a>
|
||||||
|
<a href={withBase(targets.computerScience)} aria-current={active === 'computer-science' ? 'page' : undefined}>Computer Science</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
7
src/components/Sidebar.astro
Normal file
7
src/components/Sidebar.astro
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
import DefaultSidebar from '@astrojs/starlight/components/Sidebar.astro';
|
||||||
|
import ProgrammeSwitch from './ProgrammeSwitch.astro';
|
||||||
|
---
|
||||||
|
|
||||||
|
<ProgrammeSwitch />
|
||||||
|
<DefaultSidebar />
|
||||||
68
src/config/programme-navigation.mjs
Normal file
68
src/config/programme-navigation.mjs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { sidebar } from './sidebar.mjs';
|
||||||
|
|
||||||
|
const shared = {
|
||||||
|
'period-1/discrete-mathematics': 'block-1/discrete-mathematics',
|
||||||
|
'period-1/procedural-programming': 'block-1/procedural-programming',
|
||||||
|
'period-2/calculus': 'block-2/calculus',
|
||||||
|
'period-2/logic': 'block-2/logic',
|
||||||
|
'period-2/objects-in-programming': 'block-2/objects-in-programming',
|
||||||
|
'period-4/data-structures-and-algorithms': 'block-4/data-structures-and-algorithms',
|
||||||
|
'period-4/linear-algebra': 'block-4/linear-algebra',
|
||||||
|
};
|
||||||
|
|
||||||
|
const computerScience = {
|
||||||
|
label: 'Computer Science',
|
||||||
|
items: [
|
||||||
|
{ slug: 'computer-science' },
|
||||||
|
{ label: 'Year 1', items: [
|
||||||
|
{ slug: 'computer-science/year-1' },
|
||||||
|
{ label: 'Period 1', items: ['discrete-mathematics', 'introduction-to-computer-science', 'procedural-programming'].map((slug) => ({ slug: `computer-science/year-1/period-1/${slug}` })) },
|
||||||
|
{ label: 'Period 2', items: ['calculus', 'logic', 'objects-in-programming'].map((slug) => ({ slug: `computer-science/year-1/period-2/${slug}` })) },
|
||||||
|
{ label: 'Period 4', items: ['computer-architecture', 'data-structures-and-algorithms', 'linear-algebra'].map((slug) => ({ slug: `computer-science/year-1/period-4/${slug}` })) },
|
||||||
|
{ label: 'Period 5', items: ['algorithmic-design', 'databases', 'statistics'].map((slug) => ({ slug: `computer-science/year-1/period-5/${slug}` })) },
|
||||||
|
] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const compact = { label: 'Bachelor programmes', items: [{ slug: 'computer-science' }, { slug: 'data-science-and-ai' }] };
|
||||||
|
|
||||||
|
export function programmeForPathname(pathname) {
|
||||||
|
if (pathname.startsWith('/computer-science')) return 'computer-science';
|
||||||
|
if (pathname.startsWith('/data-science-and-ai') || pathname.startsWith('/bachelor')) return 'data-science-and-ai';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function programmeSwitchTargets(pathname) {
|
||||||
|
let computerScience = '/computer-science/';
|
||||||
|
let dataScience = '/data-science-and-ai/';
|
||||||
|
for (const [cs, ds] of Object.entries(shared)) {
|
||||||
|
if (pathname.includes(`/computer-science/year-1/${cs}`) || pathname.includes(`/data-science-and-ai/year-1/${ds}`)) {
|
||||||
|
computerScience = `/computer-science/year-1/${cs}/`;
|
||||||
|
dataScience = `/data-science-and-ai/year-1/${ds}/`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { computerScience, dataScience };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sidebarForPathname(pathname) {
|
||||||
|
const programme = programmeForPathname(pathname);
|
||||||
|
const withoutProgrammes = sidebar.filter(({ label }) => label !== 'Computer Science' && label !== 'Data Science & AI');
|
||||||
|
const dataScience = sidebar.find(({ label }) => label === 'Data Science & AI');
|
||||||
|
const undergraduate = programme === 'computer-science' ? computerScience : programme === 'data-science-and-ai' ? dataScience : compact;
|
||||||
|
return [...withoutProgrammes.slice(0, 3), undergraduate, ...withoutProgrammes.slice(3)];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterResolvedSidebar(entries, pathname) {
|
||||||
|
const programme = programmeForPathname(pathname);
|
||||||
|
return entries.filter((entry) => {
|
||||||
|
if (entry.label === 'Computer Science') return programme !== 'data-science-and-ai';
|
||||||
|
if (entry.label === 'Data Science & AI') return programme !== 'computer-science';
|
||||||
|
return true;
|
||||||
|
}).map((entry) => {
|
||||||
|
if (!programme && (entry.label === 'Computer Science' || entry.label === 'Data Science & AI') && 'entries' in entry) {
|
||||||
|
return { ...entry, entries: entry.entries.slice(0, 1) };
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,36 @@ export const sidebar = [
|
||||||
{ label: 'About Incognito', link: '/about-incognito/' },
|
{ label: 'About Incognito', link: '/about-incognito/' },
|
||||||
{ slug: 'previous-exams-and-documents' },
|
{ slug: 'previous-exams-and-documents' },
|
||||||
{
|
{
|
||||||
label: 'Bachelor',
|
label: 'Computer Science',
|
||||||
|
items: [
|
||||||
|
{ slug: 'computer-science' },
|
||||||
|
{ label: 'Year 1', items: [
|
||||||
|
{ slug: 'computer-science/year-1' },
|
||||||
|
{ label: 'Period 1', items: [
|
||||||
|
{ slug: 'computer-science/year-1/period-1/discrete-mathematics' },
|
||||||
|
{ slug: 'computer-science/year-1/period-1/introduction-to-computer-science' },
|
||||||
|
{ slug: 'computer-science/year-1/period-1/procedural-programming' },
|
||||||
|
] },
|
||||||
|
{ label: 'Period 2', items: [
|
||||||
|
{ slug: 'computer-science/year-1/period-2/calculus' },
|
||||||
|
{ slug: 'computer-science/year-1/period-2/logic' },
|
||||||
|
{ slug: 'computer-science/year-1/period-2/objects-in-programming' },
|
||||||
|
] },
|
||||||
|
{ label: 'Period 4', items: [
|
||||||
|
{ slug: 'computer-science/year-1/period-4/computer-architecture' },
|
||||||
|
{ slug: 'computer-science/year-1/period-4/data-structures-and-algorithms' },
|
||||||
|
{ slug: 'computer-science/year-1/period-4/linear-algebra' },
|
||||||
|
] },
|
||||||
|
{ label: 'Period 5', items: [
|
||||||
|
{ slug: 'computer-science/year-1/period-5/algorithmic-design' },
|
||||||
|
{ slug: 'computer-science/year-1/period-5/databases' },
|
||||||
|
{ slug: 'computer-science/year-1/period-5/statistics' },
|
||||||
|
] },
|
||||||
|
] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Data Science & AI',
|
||||||
items: [
|
items: [
|
||||||
{ slug: 'data-science-and-ai' },
|
{ slug: 'data-science-and-ai' },
|
||||||
{
|
{
|
||||||
|
|
|
||||||
12
src/middleware.ts
Normal file
12
src/middleware.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { defineMiddleware } from 'astro:middleware';
|
||||||
|
import { filterResolvedSidebar } from './config/programme-navigation.mjs';
|
||||||
|
|
||||||
|
export const onRequest = defineMiddleware((context, next) => {
|
||||||
|
try {
|
||||||
|
const route = context.locals.starlightRoute;
|
||||||
|
route.sidebar = filterResolvedSidebar(route.sidebar, context.url.pathname);
|
||||||
|
} catch {
|
||||||
|
// Non-Starlight routes such as the generated 404 page have no route data.
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
@ -132,3 +132,47 @@
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.programme-switch {
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin: 0 0.5rem 0.75rem;
|
||||||
|
border: 1px solid var(--sl-color-gray-5);
|
||||||
|
border-radius: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.programme-switch__label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.45rem;
|
||||||
|
color: var(--sl-color-gray-2);
|
||||||
|
font-size: var(--sl-text-xs);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.programme-switch__links {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.programme-switch__links a {
|
||||||
|
display: flex;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.35rem;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.programme-switch__links a[aria-current='page'] {
|
||||||
|
color: var(--sl-color-text-accent);
|
||||||
|
background: var(--sl-color-accent-low);
|
||||||
|
font-weight: 700;
|
||||||
|
box-shadow: inset 0 0 0 1px var(--sl-color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.programme-switch__links a:focus-visible {
|
||||||
|
outline: 2px solid var(--sl-color-accent-high);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ test('classifies course pages separately from represented and excluded pages', (
|
||||||
test('maps in-scope course IDs to stable Starlight destinations', () => {
|
test('maps in-scope course IDs to stable Starlight destinations', () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
destinationForPageId('study:bachelor:year_1:block_1:discrete_mathematics'),
|
destinationForPageId('study:bachelor:year_1:block_1:discrete_mathematics'),
|
||||||
'src/content/docs/data-science-and-ai/year-1/block-1/discrete-mathematics.md',
|
'src/content/docs/data-science-and-ai/year-1/block-1/discrete-mathematics.mdx',
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
destinationForPageId('study:master_ai:year_2:block_3:agents'),
|
destinationForPageId('study:master_ai:year_2:block_3:agents'),
|
||||||
|
|
@ -155,12 +155,17 @@ test('all 54 substantive live course pages are published and registered', async
|
||||||
const recoveredSidebar = await readFile('src/config/recovered-course-sidebar.mjs', 'utf8');
|
const recoveredSidebar = await readFile('src/config/recovered-course-sidebar.mjs', 'utf8');
|
||||||
for (const record of migrated) {
|
for (const record of migrated) {
|
||||||
await access(record.destination);
|
await access(record.destination);
|
||||||
const content = await readFile(record.destination, 'utf8');
|
let content = await readFile(record.destination, 'utf8');
|
||||||
|
const sharedImport = content.match(/import SharedCourse from '([^']+)'/);
|
||||||
|
if (sharedImport) {
|
||||||
|
const { dirname, resolve } = await import('node:path');
|
||||||
|
content += await readFile(resolve(dirname(record.destination), sharedImport[1]), 'utf8');
|
||||||
|
}
|
||||||
assert.match(content, /:::caution\[Historical information\]/, record.destination);
|
assert.match(content, /:::caution\[Historical information\]/, record.destination);
|
||||||
assert.match(content, new RegExp(record.sourceUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), record.destination);
|
assert.match(content, new RegExp(record.sourceUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), record.destination);
|
||||||
assert.equal(supplementalByDestination.get(record.destination)?.source, record.sourceUrl);
|
assert.equal(supplementalByDestination.get(record.destination)?.source, record.sourceUrl);
|
||||||
assert.equal(supplementalByDestination.get(record.destination)?.category, 'live-course-recovery');
|
assert.equal(supplementalByDestination.get(record.destination)?.category, 'live-course-recovery');
|
||||||
const slug = record.destination.replace(/^src\/content\/docs\//, '').replace(/\.md$/, '');
|
const slug = record.destination.replace(/^src\/content\/docs\//, '').replace(/\.mdx?$/, '');
|
||||||
assert.match(recoveredSidebar, new RegExp(`slug: '${slug}'`), slug);
|
assert.match(recoveredSidebar, new RegExp(`slug: '${slug}'`), slug);
|
||||||
}
|
}
|
||||||
const report = await readFile('docs/live-course-recovery.md', 'utf8');
|
const report = await readFile('docs/live-course-recovery.md', 'utf8');
|
||||||
|
|
|
||||||
21
tests/programme-navigation.test.mjs
Normal file
21
tests/programme-navigation.test.mjs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
import { programmeForPathname, programmeSwitchTargets, sidebarForPathname } from '../src/config/programme-navigation.mjs';
|
||||||
|
|
||||||
|
test('programme routes select the matching sidebar while retaining global groups', () => {
|
||||||
|
assert.equal(programmeForPathname('/computer-science/year-1/'), 'computer-science');
|
||||||
|
assert.equal(programmeForPathname('/data-science-and-ai/year-2/'), 'data-science-and-ai');
|
||||||
|
assert.equal(programmeForPathname('/useful-information/'), null);
|
||||||
|
for (const path of ['/computer-science/', '/data-science-and-ai/', '/useful-information/']) {
|
||||||
|
const text = JSON.stringify(sidebarForPathname(path));
|
||||||
|
assert.match(text, /Useful Information/);
|
||||||
|
assert.match(text, /Master AI/);
|
||||||
|
assert.match(text, /previous-exams-and-documents/);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shared course switches to its paired programme route', () => {
|
||||||
|
const targets = programmeSwitchTargets('/computer-science/year-1/period-2/calculus/');
|
||||||
|
assert.equal(targets.dataScience, '/data-science-and-ai/year-1/block-2/calculus/');
|
||||||
|
assert.equal(targets.computerScience, '/computer-science/year-1/period-2/calculus/');
|
||||||
|
});
|
||||||
12
tests/programme-switch.test.mjs
Normal file
12
tests/programme-switch.test.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
test('Sidebar override renders an accessible no-JavaScript programme switch', async () => {
|
||||||
|
const component = await readFile('src/components/ProgrammeSwitch.astro', 'utf8');
|
||||||
|
const wrapper = await readFile('src/components/Sidebar.astro', 'utf8');
|
||||||
|
assert.match(component, /aria-label="Bachelor programme"/);
|
||||||
|
assert.match(component, /aria-current/);
|
||||||
|
assert.doesNotMatch(component, /client:|<script/);
|
||||||
|
assert.match(wrapper, /@astrojs\/starlight\/components\/Sidebar\.astro/);
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue