diff --git a/src/components/papers/PaperActions.astro b/src/components/papers/PaperActions.astro new file mode 100644 index 0000000000..363853a197 --- /dev/null +++ b/src/components/papers/PaperActions.astro @@ -0,0 +1,135 @@ +--- +interface Props { + bibtex?: string; + preprintUrl?: string; + doiUrl?: string; + hasAbstract: boolean; + toggleId: string; +} + +const { bibtex, preprintUrl, doiUrl, hasAbstract, toggleId } = Astro.props; +--- + +
+ { + bibtex && ( + + ) + } + { + preprintUrl && ( + + {' '} + {/* source https://heroicons.com v1.0.6, MIT licensed */} + PREPRINT + + + + + ) + } + { + doiUrl && ( + + {' '} + {/* source https://heroicons.com v1.0.6, MIT licensed */} + DOI + + + + + ) + } + { + hasAbstract && ( + + ) + } +
+ + diff --git a/src/components/papers/PaperEntry.astro b/src/components/papers/PaperEntry.astro new file mode 100644 index 0000000000..3e77ab8e24 --- /dev/null +++ b/src/components/papers/PaperEntry.astro @@ -0,0 +1,98 @@ +--- +import type { Paper } from '../../types/papers'; +import PaperActions from './PaperActions.astro'; + +interface Props { + paper: Paper; + index: number; +} + +const { paper, index } = Astro.props; + +function getToggleAbstractId(paper: Paper) { + return `toggle-abstract-${index}`; +} + +function getVenueString(pubInfo: any) { + switch (pubInfo.type) { + case 'conference': + return `${pubInfo.conference}, ${pubInfo.location}`; + case 'journal': + return `${pubInfo.journal}${pubInfo.volume ? `, vol. ${pubInfo.volume}` : ''}${pubInfo.number ? `, no. ${pubInfo.number}` : ''}`; + case 'thesis': + return `${pubInfo.thesisType} thesis, ${pubInfo.institution}, ${pubInfo.location}`; + } +} +--- + +
+ + +
+
+

+ {paper.title} +

+

+ { + paper.authors.map((a, i) => ( + <> + {a.orcidUrl ? ( + + + {a.name} + {i < paper.authors.length - 1 ? ',' : ''} + + + ) : ( + + {a.name} + {i < paper.authors.length - 1 ? ',' : ''} + + )} + {i < paper.authors.length - 1 ? ' ' : ''} + + )) + } +

+

+ {getVenueString(paper.publicationInfo)} +

+
+ +
+ +
+
+ + { + paper.abstract && ( +
+
+ {paper.abstract.split('\n\n').map((paragraph, i) => ( +

0 ? 'mt-4' : ''}>{paragraph}

+ ))} +
+
+ ) + } +
+ + diff --git a/src/components/papers/YearSection.astro b/src/components/papers/YearSection.astro new file mode 100644 index 0000000000..1fb54084b3 --- /dev/null +++ b/src/components/papers/YearSection.astro @@ -0,0 +1,18 @@ +--- +import type { Paper } from '../../types/papers'; +import PaperEntry from './PaperEntry.astro'; + +interface Props { + year: string; + papers: Paper[]; +} + +const { year, papers } = Astro.props; +--- + +
+

{year}

+
+ {papers.map((paper, index) => )} +
+
diff --git a/src/pages/research/index.astro b/src/pages/research/index.astro new file mode 100644 index 0000000000..47c4bd5323 --- /dev/null +++ b/src/pages/research/index.astro @@ -0,0 +1,91 @@ +--- +import Container from '../../components/layout/Container.astro'; +import Divider from '../../components/layout/Divider.astro'; +import PageHeader from '../../components/layout/PageHeader.astro'; +import YearSection from '../../components/papers/YearSection.astro'; +import Layout from '../../layouts/Layout.astro'; +import { papers } from './papers'; +import { papersSchema } from './papersSchema'; + +let validatedPapers; +try { + validatedPapers = papersSchema.parse(papers); +} catch (error) { + if (error instanceof z.ZodError) { + console.error('Papers validation failed:'); + console.error(JSON.stringify(error.errors, null, 2)); + throw new Error( + 'Papers data validation failed. Check console for details.', + ); + } + throw error; +} + +const papersByYear = validatedPapers.reduce((acc, paper, index) => { + const year = paper.year; + if (!acc[year]) acc[year] = []; + paper.id = index; + acc[year].push(paper); + return acc; +}, {}); + +const sortedYears = Object.keys(papersByYear).sort((a, b) => b - a); +--- + + + + + +
+

+ Nix started as a research project by Eelco Dolstra and his collaborators + at Utrecht University around 2003. Since then, scientists from multiple + institutions have published their work on repeatable computation and + reliable, secure software distribution. +

+

+ This collection traces the continued exploration of theoretical + foundations and practical applications of the ideas underlying Nix and + its ecosystem. +

+
+
+ +
+
+ + + +
+ +

+ Scientific publications about Nix and associated projects +

+ +
+ { + sortedYears.map((year) => ( + + )) + } +
+
+
+
+ diff --git a/src/pages/research.astro b/src/pages/research/papers.ts similarity index 69% rename from src/pages/research.astro rename to src/pages/research/papers.ts index 112276c7b2..223b9b6b3a 100644 --- a/src/pages/research.astro +++ b/src/pages/research/papers.ts @@ -1,230 +1,4 @@ ---- -import { getEntry } from 'astro:content'; -import Container from '../components/layout/Container.astro'; -import PageHeader from '../components/layout/PageHeader.astro'; -import Layout from '../layouts/Layout.astro'; - -import Divider from '../components/layout/Divider.astro'; - -type Author = { - name: string; - orcidUrl?: string; -}; - -type ConferencePaper = { - type: 'conference'; - conference: string; - location: string; - publisher?: string; - pages?: string; -}; - -type JournalPaper = { - type: 'journal'; - journal: string; - volume?: string; - number?: string; - publisher?: string; - pages?: string; -}; - -type ThesisPaper = { - type: 'thesis'; - thesisType: 'PhD' | "Master's" | "Bachelor's" | 'Diplomarbeit'; - institution: string; - location: string; -}; - -type PublicationType = ConferencePaper | JournalPaper | ThesisPaper; - -type Paper = { - title: string; - authors: Author[]; - year: number; - abstract?: string; - doiOrPublisherUrl?: string; - preprintOrArchiveUrl?: string; - bibtex?: string; - publicationInfo: PublicationType; -}; - -function validatePaper(paper: any): void { - // Helper to check if value exists and is of type - const isType = (value: any, type: string, path: string) => { - if (value === undefined && type !== 'undefined') - throw new Error(`Missing required field: ${path}`); - if (value !== undefined && typeof value !== type) - throw new Error( - `Invalid type for ${path}: expected ${type}, got ${typeof value}`, - ); - }; - - // Validate basic paper fields - isType(paper.title, 'string', 'title'); - isType(paper.year, 'number', 'year'); - if (paper.abstract !== undefined) - isType(paper.abstract, 'string', 'abstract'); - if (paper.doiOrPublisherUrl !== undefined) - isType(paper.doiOrPublisherUrl, 'string', 'doiOrPublisherUrl'); - if (paper.preprintOrArchiveUrl !== undefined) - isType(paper.preprintOrArchiveUrl, 'string', 'preprintOrArchiveUrl'); - if (paper.bibtex !== undefined) isType(paper.bibtex, 'string', 'bibtex'); - - // Validate authors array - if (!Array.isArray(paper.authors)) - throw new Error('authors must be an array'); - if (paper.authors.length === 0) - throw new Error('authors array cannot be empty'); - - paper.authors.forEach((author, index) => { - isType(author.name, 'string', `authors[${index}].name`); - if (author.orcidUrl !== undefined) - isType(author.orcidUrl, 'string', `authors[${index}].orcidUrl`); - - const validAuthorProps = ['name', 'orcidUrl']; - const extraProps = Object.keys(author).filter( - (key) => !validAuthorProps.includes(key), - ); - if (extraProps.length > 0) { - throw new Error( - `Extra properties found in author[${index}]: ${extraProps.join(', ')}`, - ); - } - }); - - // Validate publicationInfo - if (!paper.publicationInfo) throw new Error('Missing publicationInfo'); - if (typeof paper.publicationInfo !== 'object') - throw new Error('publicationInfo must be an object'); - - const { publicationInfo } = paper; - - switch (publicationInfo.type) { - case 'conference': { - isType( - publicationInfo.conference, - 'string', - 'publicationInfo.conference', - ); - isType(publicationInfo.location, 'string', 'publicationInfo.location'); - if (publicationInfo.publisher !== undefined) - isType( - publicationInfo.publisher, - 'string', - 'publicationInfo.publisher', - ); - if (publicationInfo.pages !== undefined) - isType(publicationInfo.pages, 'string', 'publicationInfo.pages'); - - const validConfProps = [ - 'type', - 'conference', - 'location', - 'publisher', - 'pages', - ]; - const extraProps = Object.keys(publicationInfo).filter( - (key) => !validConfProps.includes(key), - ); - if (extraProps.length > 0) { - throw new Error( - `Extra properties found in conference publicationInfo: ${extraProps.join(', ')}`, - ); - } - break; - } - case 'journal': { - isType(publicationInfo.journal, 'string', 'publicationInfo.journal'); - if (publicationInfo.volume !== undefined) - isType(publicationInfo.volume, 'string', 'publicationInfo.volume'); - if (publicationInfo.number !== undefined) - isType(publicationInfo.number, 'string', 'publicationInfo.number'); - if (publicationInfo.publisher !== undefined) - isType( - publicationInfo.publisher, - 'string', - 'publicationInfo.publisher', - ); - if (publicationInfo.pages !== undefined) - isType(publicationInfo.pages, 'string', 'publicationInfo.pages'); - - const validJournalProps = [ - 'type', - 'journal', - 'volume', - 'number', - 'publisher', - 'pages', - ]; - const extraProps = Object.keys(publicationInfo).filter( - (key) => !validJournalProps.includes(key), - ); - if (extraProps.length > 0) { - throw new Error( - `Extra properties found in journal publicationInfo: ${extraProps.join(', ')}`, - ); - } - break; - } - case 'thesis': { - isType( - publicationInfo.institution, - 'string', - 'publicationInfo.institution', - ); - isType(publicationInfo.location, 'string', 'publicationInfo.location'); - if ( - !['PhD', "Master's", "Bachelor's", 'Diplomarbeit'].includes( - publicationInfo.thesisType, - ) - ) { - throw new Error(`Invalid thesis type: ${publicationInfo.thesisType}`); - } - - const validThesisProps = [ - 'type', - 'thesisType', - 'institution', - 'location', - ]; - const extraProps = Object.keys(publicationInfo).filter( - (key) => !validThesisProps.includes(key), - ); - if (extraProps.length > 0) { - throw new Error( - `Extra properties found in thesis publicationInfo: ${extraProps.join(', ')}`, - ); - } - break; - } - default: - throw new Error( - `Invalid publication type: ${(publicationInfo as any).type}`, - ); - } - - // Check for extra properties in Paper - const validPaperProps = [ - 'title', - 'authors', - 'year', - 'abstract', - 'doiOrPublisherUrl', - 'preprintOrArchiveUrl', - 'bibtex', - 'publicationInfo', - ]; - const extraProps = Object.keys(paper).filter( - (key) => !validPaperProps.includes(key), - ); - if (extraProps.length > 0) { - throw new Error( - `Extra properties found in paper: ${extraProps.join(', ')}`, - ); - } -} - -const papers: ReadonlyArray = [ +export const papers = [ { title: 'Extending Cloud Build Systems to Eliminate Transitive Trust', authors: [ @@ -793,339 +567,4 @@ const papers: ReadonlyArray = [ }, preprintOrArchiveUrl: 'https://nixos.org/~eelco/pubs/iscsd-scm11-final.pdf', }, -]; - -const sponsors = await getEntry('sponsors', 'info'); - -function getToggleAbstractId(paper) { - return `toggle-abstract-${paper.id}`; -} - -const papersByYear = papers.reduce((acc, paper, index) => { - validatePaper(paper); - const year = paper.year; - if (!acc[year]) acc[year] = []; - paper.id = index; - acc[year].push(paper); - return acc; -}, {}); - -const sortedYears = Object.keys(papersByYear).sort((a, b) => b - a); - -function getVenueString(pubInfo: PublicationType) { - switch (pubInfo.type) { - case 'conference': - return `${pubInfo.conference}, ${pubInfo.location}`; - case 'journal': - return `${pubInfo.journal}${pubInfo.volume ? `, vol. ${pubInfo.volume}` : ''}${pubInfo.number ? `, no. ${pubInfo.number}` : ''}`; - case 'thesis': - return `${pubInfo.thesisType} thesis, ${pubInfo.institution}, ${pubInfo.location}`; - } -} ---- - - - - - -
-

- Nix started as a research project by Eelco Dolstra and his collaborators - at Utrecht University around 2003. Since then, scientists from multiple - institutions have published their work on repeatable computation and - reliable, secure software distribution. -

-

- This collection traces the continued exploration of theoretical - foundations and practical applications of the ideas underlying Nix and - its ecosystem. -

-
-
- -
-
- -
- -

- Scientific publications about Nix and associated projects -

- -
- { - sortedYears.map((year) => ( -
-

{year}

-
- {papersByYear[year].map((paper) => ( -
- -
-
-

- {paper.title} -

-

- {paper.authors.map((a, i) => ( - <> - {a.orcidUrl ? ( - - - {a.name} - {i < paper.authors.length - 1 ? ',' : ''} - - - ) : ( - - {a.name} - {i < paper.authors.length - 1 ? ',' : ''} - - )} - {i < paper.authors.length - 1 ? ' ' : ''} - - ))} -

-

- {getVenueString(paper.publicationInfo)} -

-
-
- {paper.bibtex && ( - - )} - {paper.preprintOrArchiveUrl && ( - - Download preprint - - PREPRINT - - - {/* source https://heroicons.com v1.0.6, MIT licensed */} - - - - )} - {paper.doiOrPublisherUrl && ( - - View publisher version - - DOI - - - {/* source https://heroicons.com v1.0.6, MIT licensed */} - - - - )} - {paper.abstract && ( - - )} -
-
-
- {paper.abstract && - paper.abstract - .split('\n\n') - .map((paragraph, i) => ( -

0 ? 'mt-4' : ''}>{paragraph}

- ))} -
-
- ))} -
-
- )) - } -
-
-
-
- - - - +] as const; diff --git a/src/pages/research/papersSchema.ts b/src/pages/research/papersSchema.ts new file mode 100644 index 0000000000..265cdbbdba --- /dev/null +++ b/src/pages/research/papersSchema.ts @@ -0,0 +1,43 @@ +import { z } from 'astro:content'; + +export const authorSchema = z.object({ + name: z.string(), + orcidUrl: z.string().url().optional(), +}); + +export const publicationInfoSchema = z.discriminatedUnion('type', [ + z.object({ + type: z.literal('conference'), + conference: z.string(), + location: z.string(), + publisher: z.string().optional(), + pages: z.string().optional(), + }), + z.object({ + type: z.literal('journal'), + journal: z.string(), + volume: z.string().optional(), + number: z.string().optional(), + publisher: z.string().optional(), + pages: z.string().optional(), + }), + z.object({ + type: z.literal('thesis'), + thesisType: z.enum(['PhD', "Master's", "Bachelor's", 'Diplomarbeit']), + institution: z.string(), + location: z.string(), + }), +]); + +export const paperSchema = z.object({ + title: z.string(), + authors: z.array(authorSchema), + year: z.number(), + abstract: z.string().optional(), + doiOrPublisherUrl: z.string().optional(), + preprintOrArchiveUrl: z.string().optional(), + bibtex: z.string().optional(), + publicationInfo: publicationInfoSchema, +}); + +export const papersSchema = z.array(paperSchema);