diff --git a/README.md b/README.md index cdbaef4..b097262 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,18 @@ For protocols and essays see [subtypes module](#subtypes). ) ``` +Multi-author example: + +```typ +#show: project.with( + title: "Lorem ipsum dolor sit", + authors: ( + (name: "John Doe", email: "john@example.org"), + (name: "Jane Doe", email: "jane@example.org"), + ), +) +``` + ### Documentation | `project` | | @@ -92,6 +104,7 @@ For protocols and essays see [subtypes module](#subtypes). | `semester` | optional, content, default: `none` | | `docent` | optional, content, default: `none` | | `author` | optional, content, default: `none` | +| `authors` | optional, array, default: `none`, multi-author input (items can be `name` content or dictionaries with `name`, optional `email`, `student-number`, `address`) | | `date` | optional, datetime or content, default: `datetime.today()` | | `date-format` | optional, function, default: `(date) => if type(date) == type(datetime.today()) { date.display("[day].[month].[year]") } else { date }` | | `header-gutter` | optional, length, default: `20%`, overwrite header gutter | @@ -213,6 +226,18 @@ _Note:_ The template generates a German statement of authorship as the last page ) ``` +Multi-author example: + +```typ +#show: seminar-paper.project.with( + title: "Die Intensionalität von dass-Sätzen", + authors: ( + (name: "Max Muster", email: "max@uni-musterstadt.uni", student-number: "0123456789"), + (name: "Erika Muster", email: "erika@uni-musterstadt.uni", student-number: "1234567890"), + ), +) +``` + ### Documentation | `project` | | @@ -228,6 +253,7 @@ _Note:_ The template generates a German statement of authorship as the last page | `semester` | optional, content, default: `"SEMESTER"` | | `docent` | optional, content, default: `"DOCENT"` | | `author` | optional, content, default: `"AUTHOR"` | +| `authors` | optional, array, default: `none`, multi-author input (items can be `name` content or dictionaries with `name`, optional `email`, `student-number`, `address`) | | `student-number` | optional, content, default: `none` | | `email` | optional, content, default: `"EMAIL"` | | `address` | optional, content, default: `"ADDRESS"` | @@ -310,6 +336,18 @@ _Note:_ The template generates a German statement of authorship as the last page ) ``` +Multi-author example: + +```typ +#show: slides.with( + title: [Organisatorisches und Einführung in die Logik], + authors: ( + (name: "Tristan Pieper", email: "tristan.pieper@uni-rostock.de"), + (name: "Juan Pablo Sierra Useche", email: "juan.pablo@niuitmo.ru"), + ), +) +``` + ### Documentation | `slides` | | @@ -319,7 +357,8 @@ _Note:_ The template generates a German statement of authorship as the last page | `title` | optional, content, default: `none`, title of the presentation | | `topics` | optional, array, default: `()`, topics of the presentation | | `author` | optional, content, default: `none`, author | -| `email` | optional, content, default: `none`, author's email | +| `authors` | optional, array, default: `none`, multi-author input (items can be `name` content or dictionaries with `name`, optional `email`, `student-number`, `address`) | +| `email` | optional, content, default: `none`, legacy single-author email | | `head-replacement` | optional, content, default: `none`, replace head on title slide with given content | | `title-replacement` | optional, content, default: `none`, replace title below head on title slide with given content | | `footer` | optional, content, default: `none`, replace footer on slides with given content | @@ -408,7 +447,10 @@ Essay: seminar: [Seminar], semester: [Semester], docent: [Docent], - author: [Author], + authors: ( + (name: [Author 1], email: "author1@example.org"), + (name: [Author 2], email: "author2@example.org"), + ), date: [1#super[st] January 1970], ) ``` @@ -425,7 +467,10 @@ Protocol: seminar: [Seminar], semester: [Semester], docent: [Docent], - author: [Author], + authors: ( + (name: [Author 1], email: "author1@example.org"), + (name: [Author 2], email: "author2@example.org"), + ), date: [1#super[st] January 1970], ) ``` @@ -443,6 +488,7 @@ The base layout of these functions is provided by `exercise.project`. | `semester` | optional, content, default: `[#todo[Semester]]` | | `docent` | optional, content, default: `[#todo[Docent]]` | | `author` | optional, content, default: `[#todo[Author]]` | +| `authors` | optional, array, default: `none`, multi-author input (items can be `name` content or dictionaries with `name`, optional `email`, `student-number`, `address`) | | `date` | optional, content, default: `[#todo[Date]]` | | `body` | content, document content | @@ -455,6 +501,7 @@ The base layout of these functions is provided by `exercise.project`. | `semester` | optional, content, default: `[#todo[Semester]]` | | `docent` | optional, content, default: `[#todo[Docent]]` | | `author` | optional, content, default: `[#todo[Author]]` | +| `authors` | optional, array, default: `none`, multi-author input (items can be `name` content or dictionaries with `name`, optional `email`, `student-number`, `address`) | | `date` | optional, content, default: `[#todo[Date]]` | | `body` | content, document content | diff --git a/examples/exercise02-multi-author.pdf b/examples/exercise02-multi-author.pdf new file mode 100644 index 0000000..2baa8b8 Binary files /dev/null and b/examples/exercise02-multi-author.pdf differ diff --git a/examples/exercise02-multi-author.typ b/examples/exercise02-multi-author.typ new file mode 100644 index 0000000..732a5e6 --- /dev/null +++ b/examples/exercise02-multi-author.typ @@ -0,0 +1,116 @@ +#import "../src/exercise.typ": project, task, subtask + +#set text(lang: "GB") + +#show: project.with( + no: 4, + type: [Worksheet], + title: [Secure Systems Practice Sheet], + suffix-title: [Input Validation and Policy Checks], + show-outline: true, + abstract: [ + This is a complete example configuration for the `exercise.project` template + with fake metadata and multi-author input. + ], + document-title: [Systems Security Worksheet], + + show-hints: false, + show-solutions: false, + + show-namefield: true, + namefield: none, + show-timefield: true, + timefield: (time) => [Time budget: #time min], + max-time: 90, + + show-lines: true, + show-point-distribution-in-tasks: true, + show-point-distribution-in-solutions: false, + + solutions-as-matrix: false, + show-solution-matrix-comment-field: false, + solution-matrix-comment-field-value: [*Reviewer note:* #v(0.4cm)], + + university: [Northbridge Technical University], + faculty: [Faculty of Applied Computing], + institute: [Institute for Systems Engineering], + seminar: [Secure Software Seminar], + semester: [Spring 2026], + docent: [Dr. Avery Morgan], + + author: [Legacy Single Author], + authors: ( + (name: "Alex Rowan", email: "alex.rowan@example.edu"), + (name: "Samira Hale", email: "samira.hale@example.edu"), + ), + + date: datetime(year: 2026, month: 2, day: 19), + date-format: (d) => if type(d) == type(datetime.today()) { + d.display("[day].[month].[year]") + } else { + d + }, + + header: none, + header-gutter: 18%, + header-right: none, + header-middle: none, + header-left: none, + show-header-line: true, + + footer: none, + footer-right: none, + footer-middle: none, + footer-left: none, + show-footer-line: true, + + task-type: [Task], + extra-task-type: [Bonus task], + box-task-title: [Task], + box-hint-title: [Hint], + box-solution-title: [Suggested solution], + box-definition-title: [Definition], + box-notice-title: [Notice], + box-example-title: [Example], + + hint-type: [Hint], + hints-title: [Hints], + solution-type: [Suggested solution], + solutions-title: [Suggested solutions], + + solution-matrix-task-header: [Task], + solution-matrix-achieved-points-header: [Points], + distribution-header-point-value: [Points], + distribution-header-point-grade: [Grade], + + message: (points-sum, extrapoints-sum) => [ + Total available: #points-sum + #extrapoints-sum points. + ], + grade-scale: ( + ([excellent], 0.9), + ([good], 0.75), + ([pass], 0.6), + ([fail], 0.49), + ), + + page-margins: (top: 5.2cm, bottom: 2.8cm, left: 2.5cm, right: 2.5cm), + text-font: ("Atkinson Hyperlegible",), + math-font: ("New Computer Modern Math",), + fontsize: 11pt, + show-todolist: false, +) + += Multiple-Choice +#task(points: 4, [Input Validation], [ + Select the most robust validation strategy for untrusted input. +], []) + += Short Answer +#task(points: 6, lines: 4, [Authorization], [ + Explain why authorization checks should be performed server-side. +], []) + += Extra +#task(extra: true, points: 3, [Bonus], [ + Give one real-world example of policy drift. +], []) diff --git a/examples/seminar-paper02-multi-author.pdf b/examples/seminar-paper02-multi-author.pdf new file mode 100644 index 0000000..9840d8d Binary files /dev/null and b/examples/seminar-paper02-multi-author.pdf differ diff --git a/examples/seminar-paper02-multi-author.typ b/examples/seminar-paper02-multi-author.typ new file mode 100644 index 0000000..5020811 --- /dev/null +++ b/examples/seminar-paper02-multi-author.typ @@ -0,0 +1,79 @@ +#import "../src/seminar-paper.typ" as seminar-paper + +#set text(lang: "GB") + +#show: seminar-paper.project.with( + title: [A Comparative Survey of Sandbox Security Models], + subtitle: [Template showcase with fake metadata], + + submit-to: [Submitted to], + submit-by: [Submitted by], + + university: [Northbridge Technical University], + faculty: [Faculty of Applied Computing], + institute: [Institute for Systems Engineering], + seminar: [Secure Software Seminar], + semester: [Spring 2026], + docent: [Dr. Avery Morgan], + + author: "Legacy Single Author", + authors: ( + ( + name: "Alex Rowan", + email: "alex.rowan@example.edu", + student-number: "NTU-10482", + address: [11 Cedar Lane, Brookfield, MA 01010], + ), + ( + name: "Samira Hale", + email: "samira.hale@example.edu", + student-number: "NTU-10617", + address: [92 Pine Street, Lakeview, MA 01011], + ), + ), + student-number: "LEGACY-0000", + email: "legacy.author@example.edu", + address: [Legacy Address, Legacy City], + + title-page-part: none, + title-page-part-submit-date: none, + title-page-part-submit-to: none, + title-page-part-submit-by: none, + + sentence-supplement: [Example], + + date: datetime(year: 2026, month: 2, day: 19), + date-format: (d) => if type(d) == type(datetime.today()) { + d.display("[month repr:long] [day], [year]") + } else { + d + }, + + header: none, + header-left: none, + header-middle: none, + header-right: none, + show-header-line: true, + + footer: none, + footer-left: none, + footer-middle: none, + footer-right: none, + show-footer-line: true, + + show-outline: true, + show-todolist: true, + show-declaration-of-independent-work: true, + + page-margins: (top: 2.6cm, bottom: 2.6cm, left: 2.6cm, right: 3.8cm), + + fontsize: 11pt, +) + += Scope +This file is a full-feature example for `seminar-paper.project` using fake names and sample metadata. + +[Replace with your institution data.] + += Notes +The default header uses compact multi-author formatting (`first author, et al.`) when `authors` has more than one entry. diff --git a/examples/slides02-multi-author.pdf b/examples/slides02-multi-author.pdf new file mode 100644 index 0000000..ebdd7d1 Binary files /dev/null and b/examples/slides02-multi-author.pdf differ diff --git a/examples/slides02-multi-author.typ b/examples/slides02-multi-author.typ new file mode 100644 index 0000000..1a8a482 --- /dev/null +++ b/examples/slides02-multi-author.typ @@ -0,0 +1,74 @@ +#import "../src/slides.typ": slides, slide, focus-slide + +#set text(lang: "GB") + +#show: slides.with( + no: 7, + series: [Systems Security Colloquium], + title: [Policy-Driven Access Control], + topics: ([Motivation], [Architecture], [Threat Model], [Conclusions]), + + head-replacement: none, + title-replacement: none, + footer: none, + + author: [Legacy Single Author], + authors: ( + (name: "Alex Rowan", email: "alex.rowan@example.edu"), + (name: "Samira Hale", email: "samira.hale@example.edu"), + ), + email: "legacy.author@example.edu", + + page-numbering: (n, total) => { + text(size: 0.75em, strong[#n.first()]) + text(size: 0.5em, [ \/ #total.first()]) + }, + + show-title-slide: true, + show-author: true, + show-semester: true, + show-date: true, + show-outline: true, + show-todolist: false, + show-footer: true, + show-page-numbers: true, + + box-task-title: [Task], + box-hint-title: [Hint], + box-solution-title: [Solution], + box-definition-title: [Definition], + box-notice-title: [Notice], + box-example-title: [Example], + sentence-supplement: [Example], + + outline-title-text: [Outline], + outline-depth: 2, + heading-numbering: none, + + fontsize: 24pt, + text-font: ("Atkinson Hyperlegible",), + math-font: ("New Computer Modern Math",), + + date: datetime(year: 2026, month: 2, day: 19), + date-format: (d) => if type(d) == type(datetime.today()) { + [#d.display("[month repr:short] [day], [year]")] + } else { + d + }, +) + +#slide[ + = Why This Matters + Authorization bugs often appear when policy and implementation drift over time. +] + +#slide[ + = Model + - Inputs are untrusted. + - Policy decisions are centralized. + - Enforcement is explicit at each boundary. +] + +#focus-slide[ + Defense-in-depth beats single-point trust. +] diff --git a/src/authors.typ b/src/authors.typ new file mode 100644 index 0000000..df05db4 --- /dev/null +++ b/src/authors.typ @@ -0,0 +1,115 @@ +#let _str-type = type("") +#let _dict-type = type((:)) + +#let _dict-get(dict, key, default: none, aliases: ()) = { + if key in dict { + dict.at(key) + } else { + let value = none + for alias in aliases { + if alias in dict { + value = dict.at(alias) + } + } + + if value != none { value } else { default } + } +} + +#let _normalize-author-item(item) = { + if type(item) == _dict-type { + if not ("name" in item) { + panic("Each item in `authors` must include a `name` field.") + } + + ( + name: item.at("name"), + email: _dict-get(item, "email"), + student-number: _dict-get(item, "student-number", aliases: ("student_number",)), + address: _dict-get(item, "address"), + ) + } else { + ( + name: item, + email: none, + student-number: none, + address: none, + ) + } +} + +#let normalize-authors(authors, author: none, email: none, student-number: none, address: none) = { + if authors != none { + if type(authors) != type(()) { + panic("`authors` must be an array.") + } + + let normalized = () + for item in authors { + let normalized-item = _normalize-author-item(item) + if normalized-item.at("name") == none { + panic("Author name must not be `none`.") + } + normalized.push(normalized-item) + } + + normalized + } else if author != none { + ( + ( + name: author, + email: email, + student-number: student-number, + address: address, + ), + ) + } else { + () + } +} + +#let author-name(author-item, link-names: true) = { + let name = author-item.at("name") + let email = author-item.at("email") + + if link-names and email != none and type(email) == _str-type { + link("mailto:" + email, name) + } else { + name + } +} + +#let authors-full(authors, separator: [\ ]) = { + if authors.len() == 0 { + none + } else { + authors.map(author => author-name(author)).join(separator) + } +} + +#let authors-compact(authors, link-names: true) = { + if authors.len() == 0 { + none + } else if authors.len() == 1 { + author-name(authors.first(), link-names: link-names) + } else { + [#author-name(authors.first(), link-names: link-names), et al.] + } +} + +#let author-submit-block(authors, link-names: true) = { + if authors.len() == 0 { + none + } else { + authors.map(author => [ + #let name = author-name(author, link-names: link-names) + #let student-number = author.at("student-number") + #if student-number != none { + name + [ (#student-number)] + } else { + name + } + #if author.at("address") != none [\ #author.at("address")] + ]).join([\ \ ]) + } +} diff --git a/src/exercise.typ b/src/exercise.typ index dcaa577..92003a1 100644 --- a/src/exercise.typ +++ b/src/exercise.typ @@ -2,6 +2,7 @@ #import "elements.typ": big-heading #import "tasks.typ": * #import "todo.typ": todo, list-todos, todo-state, hide-todos +#import "authors.typ": normalize-authors, authors-compact #let standard-box-translations = ( "task": [Task], @@ -67,6 +68,7 @@ semester: none, docent: none, author: none, + authors: none, date: datetime.today(), date-format: (date) => if type(date) == type(datetime.today()) { date.display("[day].[month].[year]") } else { date }, @@ -129,6 +131,8 @@ body ) = { let ifnn-line(e) = if e != none [#e \ ] + let normalized-authors = normalize-authors(authors, author: author) + let compact-author = authors-compact(normalized-authors) if title == none { title = if type != none or no != none [ #type #no ] + if (type != none or no != none) and suffix-title != none [ --- ] + if suffix-title != none [#suffix-title] @@ -212,7 +216,7 @@ #let h-l = if header-right != none {header-right} else [ #show: align.with(top + right) #ifnn-line(document-title) - #ifnn-line(author) + #ifnn-line(compact-author) #ifnn-line(date-format(date)) #context { if state("grape-suite-timefield").at(here()) != 1 { diff --git a/src/seminar-paper.typ b/src/seminar-paper.typ index a72ef7c..fee8007 100644 --- a/src/seminar-paper.typ +++ b/src/seminar-paper.typ @@ -1,6 +1,7 @@ #import "colors.typ" as colors: * #import "todo.typ": todo, list-todos, hide-todos #import "elements.typ": * +#import "authors.typ": normalize-authors, authors-compact, author-submit-block #let project( title: none, @@ -17,6 +18,7 @@ docent: "DOCENT", author: "AUTHOR", + authors: none, student-number: none, email: "EMAIL", address: "ADDRESS", @@ -57,6 +59,14 @@ body ) = { let ifnn-line(e) = if e != none [#e \ ] + let normalized-authors = normalize-authors( + authors, + author: author, + email: email, + student-number: student-number, + address: address, + ) + let compact-author = authors-compact(normalized-authors) set text(font: text-font, size: fontsize) show math.equation: set text(font: math-font) @@ -103,9 +113,13 @@ #if title-page-part-submit-by == none { ifnn-line(text(size: 0.6em, upper(strong(submit-by)))) - ifnn-line(author + if student-number != none [ (#student-number)]) - ifnn-line(email) - ifnn-line(address) + if authors != none { + ifnn-line(author-submit-block(normalized-authors)) + } else { + ifnn-line(author + if student-number != none [ (#student-number)]) + ifnn-line(email) + ifnn-line(address) + } } else { title-page-part-submit-by } @@ -134,7 +148,7 @@ #title ], align(center, if header-middle != none {header-middle} else []), if header-right != none {header-right} else [ #show: align.with(top + right) - #author, #date-format(date) + #compact-author, #date-format(date) ]) ] + if show-header-line { v(-0.5em) + line(length: 100%, stroke: purple) }, ) @@ -209,24 +223,55 @@ // declaration of independent work if show-declaration-of-independent-work { + let is-multi-author = normalized-authors.len() > 1 pagebreak(weak: true) set page(footer: []) heading(outlined: false, numbering: none, [Selbstständigkeitserklärung]) - [Hiermit versichere ich, dass ich die vorliegende schriftliche Hausarbeit (Seminararbeit, Belegarbeit) selbstständig verfasst und keine anderen als die von mir angegebenen Quellen und Hilfsmittel benutzt habe. Die Stellen der Arbeit, die anderen Werken wörtlich oder sinngemäß entnommen sind, wurden in jedem Fall unter Angabe der Quellen (einschließlich des World Wide Web und anderer elektronischer Text- und Datensammlungen) kenntlich gemacht. Dies gilt auch für beigegebene Zeichnungen, bildliche Darstellungen, Skizzen und dergleichen. Ich versichere weiter, dass die Arbeit in gleicher oder ähnlicher Fassung noch nicht Bestandteil einer Prüfungsleistung oder einer schriftlichen Hausarbeit (Seminararbeit, Belegarbeit) war. Mir ist bewusst, dass jedes Zuwiderhandeln als Täuschungsversuch zu gelten hat, aufgrund dessen das Seminar oder die Übung als nicht bestanden bewertet und die Anerkennung der Hausarbeit als Leistungsnachweis/Modulprüfung (Scheinvergabe) ausgeschlossen wird. Ich bin mir weiter darüber im Klaren, dass das zuständige Lehrerprüfungsamt/Studienbüro über den Betrugsversuch informiert werden kann und Plagiate rechtlich als Straftatbestand gewertet werden.] + if is-multi-author { + [Hiermit versichern wir, dass wir die vorliegende schriftliche Hausarbeit (Seminararbeit, Belegarbeit) selbstständig verfasst und keine anderen als die von uns angegebenen Quellen und Hilfsmittel benutzt haben. Die Stellen der Arbeit, die anderen Werken wörtlich oder sinngemäß entnommen sind, wurden in jedem Fall unter Angabe der Quellen (einschließlich des World Wide Web und anderer elektronischer Text- und Datensammlungen) kenntlich gemacht. Dies gilt auch für beigegebene Zeichnungen, bildliche Darstellungen, Skizzen und dergleichen. Wir versichern weiter, dass die Arbeit in gleicher oder ähnlicher Fassung noch nicht Bestandteil einer Prüfungsleistung oder einer schriftlichen Hausarbeit (Seminararbeit, Belegarbeit) war. Uns ist bewusst, dass jedes Zuwiderhandeln als Täuschungsversuch zu gelten hat, aufgrund dessen das Seminar oder die Übung als nicht bestanden bewertet und die Anerkennung der Hausarbeit als Leistungsnachweis/Modulprüfung (Scheinvergabe) ausgeschlossen wird. Wir sind uns weiter darüber im Klaren, dass das zuständige Lehrerprüfungsamt/Studienbüro über den Betrugsversuch informiert werden kann und Plagiate rechtlich als Straftatbestand gewertet werden.] + } else { + [Hiermit versichere ich, dass ich die vorliegende schriftliche Hausarbeit (Seminararbeit, Belegarbeit) selbstständig verfasst und keine anderen als die von mir angegebenen Quellen und Hilfsmittel benutzt habe. Die Stellen der Arbeit, die anderen Werken wörtlich oder sinngemäß entnommen sind, wurden in jedem Fall unter Angabe der Quellen (einschließlich des World Wide Web und anderer elektronischer Text- und Datensammlungen) kenntlich gemacht. Dies gilt auch für beigegebene Zeichnungen, bildliche Darstellungen, Skizzen und dergleichen. Ich versichere weiter, dass die Arbeit in gleicher oder ähnlicher Fassung noch nicht Bestandteil einer Prüfungsleistung oder einer schriftlichen Hausarbeit (Seminararbeit, Belegarbeit) war. Mir ist bewusst, dass jedes Zuwiderhandeln als Täuschungsversuch zu gelten hat, aufgrund dessen das Seminar oder die Übung als nicht bestanden bewertet und die Anerkennung der Hausarbeit als Leistungsnachweis/Modulprüfung (Scheinvergabe) ausgeschlossen wird. Ich bin mir weiter darüber im Klaren, dass das zuständige Lehrerprüfungsamt/Studienbüro über den Betrugsversuch informiert werden kann und Plagiate rechtlich als Straftatbestand gewertet werden.] + } v(1cm) - table(columns: (auto, auto, auto, auto), - stroke: white, - inset: 0cm, + if is-multi-author { + table(columns: (auto, auto), + stroke: white, + inset: 0cm, + strong([Ort:]) + h(0.5cm), + repeat("."+hide("'")), + ) - strong([Ort:]) + h(0.5cm), - repeat("."+hide("'")), - h(0.5cm) + strong([Unterschrift:]) + h(0.5cm), - repeat("."+hide("'")), - v(0.75cm) + strong([Datum:]) + h(0.5cm), - v(0.75cm) + repeat("."+hide("'")),) + for idx in range(normalized-authors.len()) { + table(columns: (auto, auto), + stroke: white, + inset: (x: 0cm, y: 0.25cm), + strong([Unterschrift #str(idx + 1):]) + h(0.5cm), + repeat("."+hide("'")), + ) + } + + table(columns: (auto, auto), + stroke: white, + inset: (top: 0.5cm), + strong([Datum:]) + h(0.5cm), + repeat("."+hide("'")), + ) + } else { + table(columns: (auto, auto, auto, auto), + stroke: white, + inset: 0cm, + + strong([Ort:]) + h(0.5cm), + repeat("."+hide("'")), + h(0.5cm) + strong([Unterschrift:]) + h(0.5cm), + repeat("."+hide("'")), + v(0.75cm) + strong([Datum:]) + h(0.5cm), + v(0.75cm) + repeat("."+hide("'")), + ) + } } } @@ -237,4 +282,4 @@ k.push((loc: (pos.page(), pos.position()), body: body)) return k }) -} \ No newline at end of file +} diff --git a/src/slides.typ b/src/slides.typ index 0cd7846..da0a294 100644 --- a/src/slides.typ +++ b/src/slides.typ @@ -4,6 +4,7 @@ #import "colors.typ": * #import "todo.typ": todo, list-todos, todo-state, hide-todos #import "elements.typ": * +#import "authors.typ": normalize-authors, authors-compact, authors-full #let uncover = polylux.uncover #let only = polylux.uncover @@ -46,6 +47,7 @@ footer: none, author: none, + authors: none, email: none, page-numbering: (n, total) => { @@ -83,6 +85,10 @@ body ) = { + let normalized-authors = normalize-authors(authors, author: author, email: email) + let compact-author = authors-compact(normalized-authors) + let full-authors = authors-full(normalized-authors) + let left-footer = if footer != none { footer } else { @@ -90,7 +96,7 @@ if show-semester [#semester(short: true, date)], [#series] + if no != none [ \##no], title, - if show-author { author }).filter(e => e != none).join[ --- ] + if show-author { compact-author }).filter(e => e != none).join[ --- ] ) } @@ -147,7 +153,13 @@ ] else { title-replacement } #set text(size: 0.75em) - #if show-author [#author #if email != none [--- #email ] \ ] + #if show-author { + if authors != none { + if full-authors != none [#full-authors \ ] + } else { + [#author #if email != none [--- #email ] \ ] + } + } #if show-semester [#semester(date) \ ] #if show-date { date-format(date) } ] diff --git a/src/subtypes.typ b/src/subtypes.typ index d929bd9..c606aab 100644 --- a/src/subtypes.typ +++ b/src/subtypes.typ @@ -1,6 +1,7 @@ #import "colors.typ": purple #import "exercise.typ" as exercise #import "todo.typ": todo +#import "authors.typ": normalize-authors, authors-full, authors-compact #let essay( title: [#todo[Title]], @@ -10,10 +11,14 @@ semester: [#todo[Semester]], docent: [#todo[Docent]], author: [#todo[Author]], + authors: none, date: [#todo[Date]], body) = { let ifnn(b) = if b != none [#b\ ] + let normalized-authors = normalize-authors(authors, author: author) + let full-authors = authors-full(normalized-authors) + let compact-authors = authors-compact(normalized-authors) show: exercise.project.with( page-margins: (right: 4cm), @@ -24,7 +29,7 @@ header: context if counter(page).get().first() > 1 [ #set text(size: 0.75em) - #title #h(1fr) #author + #title #h(1fr) #compact-authors #v(-0.5em) #line(length: 100%, stroke: purple) ], @@ -45,7 +50,7 @@ ][ #set align(right) #ifnn(semester) - #ifnn(author) + #ifnn(full-authors) #date ] @@ -71,10 +76,13 @@ semester: [#todo[Semester]], docent: [#todo[Docent]], author: [#todo[Author]], + authors: none, date: [#todo[Date]], body) = { let ifnn(b) = if b != none [#b\ ] + let normalized-authors = normalize-authors(authors, author: author) + let full-authors = authors-full(normalized-authors) show: exercise.project.with( title: none, type: none, @@ -103,7 +111,7 @@ ][ #set align(right) #ifnn(semester) - #ifnn(author) + #ifnn(full-authors) #date ] @@ -117,4 +125,4 @@ set par(first-line-indent: 1.5em, spacing: 0.65em) show heading: set par(leading: 0.65em) body -} \ No newline at end of file +}