diff --git a/astro.config.mjs b/astro.config.mjs index c2a79c3..48fec80 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -4,6 +4,7 @@ import { unified } from '@astrojs/markdown-remark'; import { sidebar } from './src/config/sidebar.mjs'; import { legacyBachelorRedirects } from './src/config/legacy-bachelor-redirects.mjs'; import { baseAwareLinks } from './src/config/base-aware-markdown.mjs'; +import { openExamLinksInNewTab } from './src/config/open-exam-links.mjs'; const site = process.env.SITE || 'http://localhost:4321'; const base = process.env.BASE || '/'; @@ -17,7 +18,12 @@ export default defineConfig({ base, redirects: Object.fromEntries(Object.entries(legacyBachelorRedirects).map(([from, to]) => [from, `${normalizedBase}${to}`])), markdown: { - processor: unified({ remarkPlugins: [[baseAwareLinks, { base }]] }), + processor: unified({ + remarkPlugins: [ + [baseAwareLinks, { base }], + openExamLinksInNewTab, + ], + }), }, integrations: [ starlight({ diff --git a/src/config/open-exam-links.mjs b/src/config/open-exam-links.mjs new file mode 100644 index 0000000..b2de36c --- /dev/null +++ b/src/config/open-exam-links.mjs @@ -0,0 +1,24 @@ +const examArchivePath = /(?:^|[/\\])previous-exams-and-documents\.md$/; + +export function openExamLinksInNewTab() { + return (tree, file) => { + if (!examArchivePath.test(file?.path ?? '')) return; + + const visit = (node) => { + if (node.type === 'link' && /\.pdf(?:[?#]|$)/i.test(node.url ?? '')) { + node.data = { + ...node.data, + hProperties: { + ...node.data?.hProperties, + target: '_blank', + rel: 'noopener noreferrer', + }, + }; + } + + for (const child of node.children ?? []) visit(child); + }; + + visit(tree); + }; +} diff --git a/tests/open-exam-links.test.mjs b/tests/open-exam-links.test.mjs new file mode 100644 index 0000000..5247715 --- /dev/null +++ b/tests/open-exam-links.test.mjs @@ -0,0 +1,35 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { openExamLinksInNewTab } from '../src/config/open-exam-links.mjs'; + +test('opens PDF links from the exam archive in a new tab', () => { + const tree = { + type: 'root', + children: [ + { type: 'link', url: '/media/exam.pdf', children: [] }, + { type: 'link', url: '/computer-science/', children: [] }, + ], + }; + + openExamLinksInNewTab()(tree, { + path: '/project/src/content/docs/previous-exams-and-documents.md', + }); + + assert.deepEqual(tree.children[0].data, { + hProperties: { + target: '_blank', + rel: 'noopener noreferrer', + }, + }); + assert.equal(tree.children[1].data, undefined); +}); + +test('leaves PDF links on other pages unchanged', () => { + const link = { type: 'link', url: '/media/notes.pdf', children: [] }; + + openExamLinksInNewTab()({ type: 'root', children: [link] }, { + path: '/project/src/content/docs/course.md', + }); + + assert.equal(link.data, undefined); +});