"
50 | # stripping surrounding quotes.
51 | perl -lne "print \"\$1\" if /^${key}\\s*=\\s*\"(.*)\"/" < "$file"
52 | }
53 |
54 | SOURCE="$(cd "$(dirname "$0")"; pwd -P)/.." # macOS has no realpath
55 | TARGET="${1:?Missing target path or @local}"
56 | VERSION="$(read-toml "$SOURCE/typst.toml" "version")"
57 |
58 | if [[ "$TARGET" == "@local" ]] || [[ "$TARGET" == "install" ]]; then
59 | TARGET="${DATA_DIR}/typst/packages/local/"
60 | echo "Install dir: $TARGET"
61 | fi
62 |
63 | TMP="$(mktemp -d)"
64 |
65 | for f in "${files[@]}"; do
66 | mkdir -p "$TMP/$(dirname "$f")" 2>/dev/null
67 | cp -r "$SOURCE/$f" "$TMP/$f"
68 | done
69 |
70 | TARGET="${TARGET:?}/${PKG_PREFIX:?}/${VERSION:?}"
71 | echo "Packaged to: $TARGET"
72 | if rm -rf "${TARGET:?}" 2>/dev/null; then
73 | echo "Overwriting existing version."
74 | fi
75 | mkdir -p "$TARGET"
76 | mv "$TMP"/* "$TARGET"
77 |
--------------------------------------------------------------------------------
/themes/linear/components/decision-matrix.typ:
--------------------------------------------------------------------------------
1 | #import "../colors.typ": *
2 | #import "/utils.typ"
3 |
4 | #let decision-matrix = utils.make-decision-matrix((properties, data) => {
5 | let title-cell(body) = table.cell(
6 | fill: surface-2,
7 | inset: 0.8em,
8 | text(size: 13pt, body),
9 | )
10 |
11 | let body-cell(total: false, highest: none, body) = table.cell(
12 | fill: if highest == none {
13 | white
14 | } else if highest {
15 | pro-green
16 | } else {
17 | white
18 | },
19 | inset: 0.8em,
20 | text[#body],
21 | )
22 |
23 | let table-height = data.len() + 2
24 | let table-width = properties.len() + 2
25 |
26 | set table.cell(align: center + horizon)
27 | table(
28 | stroke: none,
29 | columns: for _ in range(properties.len() + 2) {
30 | (1fr,)
31 | },
32 |
33 | // draw the lines for the graph
34 |
35 | // draw vertical lines
36 | table.vline(start: 1, end: table-height - 1),
37 | ..for num in range(1, table-width + 1) {
38 | (table.vline(x: num, end: table-height - 1),)
39 | },
40 |
41 | // draw horizontal lines
42 | table.hline(start: 1),
43 | ..for num in range(1, table-height) {
44 | (table.hline(y: num),)
45 | },
46 |
47 | // draw weight lines
48 | ..for num in range(0, table-width) {
49 | (table.vline(stroke: gray, x: num, start: table-height - 1, end: table-height),)
50 | },
51 | table.hline(stroke: gray,y: table-height, end: table-width - 1),
52 |
53 |
54 | // draw the actual graph
55 |
56 | // title row
57 | [],
58 | ..for property in properties {
59 | (title-cell(property.name),)
60 | },
61 | title-cell[Total],
62 |
63 | // score rows
64 | ..for (index, result) in data {
65 | (
66 | [#index],
67 | ..for property in properties {
68 | let value = result.at(property.name)
69 | (body-cell(highest: value.highest, value.weighted),)
70 | },
71 | body-cell(highest: result.total.highest, result.total.weighted)
72 | )
73 | },
74 |
75 | // weights row
76 | text(fill: gray)[Weight],
77 | ..for property in properties {
78 | (body-cell[
79 | #property.at("weight", default: 1)x
80 | ],)
81 | },
82 | )
83 | })
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
4 |
5 |
6 |
7 |
8 |
9 | 
10 | 
11 | 
12 | 
13 | 
14 |
15 |
16 |
17 | This is the Notebookinator, a [Typst](https://github.com/typst/typst) template designed for the Vex Robotics Competition. This template aims to make it as easy as possible to get you up and running with a clean and organized notebooking environment, with minimal overhead. It provides multiple themes, and can even be extended with your own.
18 |
19 | To get started, read the [documentation](https://the-notebookinator.github.io/notebookinator/). If you have questions, or just want to hang out, feel free to join our [Discord server](https://discord.gg/sUpcVPtBDg).
20 |
21 | ## Gallery
22 |
23 | ### Radial Theme
24 |
25 |
45 |
46 | ## Linear Theme
47 |
48 |
68 |
69 | ## Special Thanks
70 |
71 | - Maqmoon (logo drawing)
72 | - CeTZ (inspiration for a lot of different things)
73 |
--------------------------------------------------------------------------------
/themes/radial/components/tournament.typ:
--------------------------------------------------------------------------------
1 | #import "../colors.typ": *
2 |
3 | /// A Series of tables displaying match data from a tournament. Useful for tournament analysis entries.
4 | /// - ..matches (dictionary): A list of all of the matches at the tournament.
5 | /// Each dictionary must contain the following fields:
6 | /// - match (string) The name of the match
7 | /// - red-alliance `` The red alliance
8 | /// - teams ``
9 | /// - score ``
10 | /// - blue-alliance `` The blue alliance
11 | /// - teams ``
12 | /// - score ``
13 | /// - won `` Whether you won the match
14 | /// - auton `` Whether you got the autonomous bonus
15 | /// - awp `` Whether you scored the autonomous win point
16 | /// - notes `` Any additional notes you have about the match
17 | /// -> content
18 | #let tournament(..matches) = {
19 | for match in matches.pos() {
20 | let color = if match.won {
21 | green
22 | } else {
23 | red
24 | }
25 | let cell = rect.with(fill: color.lighten(80%), width: 100%, height: 30pt)
26 | let header-cell = cell.with(fill: color, height: 20pt)
27 | let alliance-info(alliance: none) = {
28 | cell[
29 | #grid(
30 | columns: (1fr, 1fr),
31 | [
32 | #alliance.teams.at(0) \
33 | #alliance.teams.at(1) \
34 | ], [
35 | #set text(size: 15pt)
36 | #set align(horizon + center)
37 | #alliance.score
38 | ],
39 |
40 | )
41 | ]
42 | }
43 |
44 | let bool-icon(input) = {
45 | cell[
46 | #set align(horizon + center)
47 | #if input {
48 | image("../icons/check.svg", width: 1.5em)
49 | } else {
50 | image("../icons/x.svg", width: 1.5em)
51 | }
52 | ]
53 | }
54 |
55 | box(
56 | grid(
57 | columns: (1fr, 1fr, 1fr, 1fr, 1fr),
58 | header-cell(radius: (top-left: 1.5pt))[*Match*],
59 | header-cell[*Red Alliance*],
60 | header-cell[*Blue Alliance*],
61 | header-cell[*Auton Bonus*],
62 | header-cell(radius: (top-right: 1.5pt))[*AWP*],
63 | cell[#match.match],
64 | alliance-info(alliance: match.red-alliance),
65 | alliance-info(alliance: match.blue-alliance),
66 | bool-icon(match.auton),
67 | bool-icon(match.awp),
68 | ),
69 | )
70 |
71 | if not match.at("notes", default: none) == none [
72 | === Notes
73 |
74 | #match.notes
75 | ] else [
76 |
77 | ]
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/docs/src/reference.typ:
--------------------------------------------------------------------------------
1 | // ------------- Template -------------------------
2 |
3 | #import "@preview/tidy:0.3.0"
4 | #import "@preview/gentle-clues:0.6.0": *
5 | #import "@preview/codly:0.2.0": *
6 |
7 | #let show-module = tidy.show-module.with(
8 | show-outline: false,
9 | sort-functions: none,
10 | first-heading-level: 1,
11 | )
12 |
13 | #let def-arg(term, t, default: none, description) = {
14 | if type(t) == str {
15 | t = t.replace("?", "|none")
16 | t = `<` + t.split("|").map(s => {
17 | if s == "b" {
18 | `boolean`
19 | } else if s == "s" {
20 | `string`
21 | } else if s == "i" {
22 | `integer`
23 | } else if s == "f" {
24 | `float`
25 | } else if s == "c" {
26 | `coordinate`
27 | } else if s == "d" {
28 | `dictionary`
29 | } else if s == "a" {
30 | `array`
31 | } else if s == "n" {
32 | `number`
33 | } else {
34 | raw(s)
35 | }
36 | }).join(`|`) + `>`
37 | }
38 |
39 | stack(
40 | dir: ltr,
41 | [/ #term: #t \ #description],
42 | align(
43 | right,
44 | if default != none {
45 | [(default: #default)]
46 | },
47 | ),
48 | )
49 | }
50 |
51 | #set heading(numbering: "1.")
52 |
53 | #show: codly-init.with()
54 | #codly()
55 |
56 | // ------------------ Document content -------------------------
57 |
58 | #outline(title: none, indent: true, depth: 2)
59 |
60 | = Template
61 |
62 | #let template-module = tidy.parse-module(read("../../lib.typ"))
63 | #show-module(template-module)
64 |
65 | = Entries
66 |
67 | #let entries-module = tidy.parse-module(read("../../entries.typ"))
68 | #show-module(entries-module)
69 |
70 | = Glossary
71 |
72 | #let glossary-module = tidy.parse-module(read("../../glossary.typ"))
73 | #show-module(glossary-module)
74 |
75 | = Components
76 |
77 | All of the components across each theme share the same API, so changing themes should be guaranteed to work.
78 |
79 | #info[
80 | All of the examples show the default theme, other theme's components will look differently.
81 | ]
82 |
83 | #import "/lib.typ": *
84 | #import themes.default: default-theme, components
85 |
86 | #let default-components-module = tidy.parse-module(
87 | read("../../themes/default/components.typ"),
88 | scope: (
89 | create-body-entry: create-body-entry,
90 | glossary: glossary,
91 | components: components,
92 | ),
93 | )
94 |
95 | #show-module(default-components-module)
96 |
97 | = Utils
98 |
99 | #let utils-module = tidy.parse-module(read("../../utils/misc.typ") + read("../../utils/theme.typ") + read("../../utils/components.typ"))
100 |
101 | #show-module(utils-module)
102 |
--------------------------------------------------------------------------------
/docs/typst-doc.css:
--------------------------------------------------------------------------------
1 | /* Parameter Description */
2 | .parameter-details,
3 | .main-details {
4 | display: flex;
5 | align-items: center;
6 | font-family: Cascadia Mono, Courier New, Courier, monospace;
7 | }
8 |
9 | .main-details h4 {
10 | margin: 0;
11 | }
12 |
13 | .parameter-details {
14 | justify-content: space-between;
15 | }
16 |
17 | .parameter-default {
18 | background: none !important
19 | }
20 |
21 | .main-details {
22 | gap: 0.5rem;
23 | }
24 |
25 | .parameter-description {
26 | /* margin-top: 1em; */
27 | /* description indent */
28 | margin-inline-start: 2rem;
29 | }
30 |
31 | .parameter > p {
32 | margin: 0;
33 | }
34 |
35 | .parameter > code {
36 | background: none;
37 | }
38 |
39 | /* Types */
40 | .type > a {
41 | text-decoration: none;
42 | color: black !important;
43 | }
44 |
45 | .type > a:hover {
46 | text-decoration: none;
47 | }
48 |
49 | .type > a[href=""] {
50 | pointer-events: none;
51 | }
52 |
53 | h1 .type {
54 | font-size: inherit !important
55 | }
56 |
57 | h1 .type > a {
58 | pointer-events: none;
59 | }
60 |
61 | .type {
62 | border-radius: 4px;
63 | font-size: 14px;
64 | padding: 2px 4px;
65 | font-family: Cascadia Mono, Courier New, Courier, monospace;
66 | white-space: nowrap;
67 | display: inline-block;
68 | font-weight: 400;
69 | color: black;
70 | }
71 |
72 | .type-con {
73 | background: #a6ebe6;
74 | }
75 |
76 | .type-bool {
77 | background: #ffedc1;
78 | }
79 |
80 | .type-str {
81 | background: #d1ffe2;
82 | }
83 |
84 | .type-keyword {
85 | background: #ffcbc4;
86 | }
87 |
88 | .type-num {
89 | background: #e7d9ff;
90 | }
91 |
92 | .type-obj {
93 | background: #eff0f3;
94 | }
95 |
96 | .type-fn {
97 | background: #f9dfff;
98 | }
99 |
100 | .type-color {
101 | background: #7cd5ff;
102 | background: linear-gradient(83deg,
103 | #7cd5ff,
104 | #a6fbca 33%,
105 | #fff37c 66%,
106 | #ffa49d);
107 | }
108 |
109 | .typ-comment {
110 | color: #8a8a8a;
111 | }
112 |
113 | .typ-escape {
114 | color: #1d6c76;
115 | }
116 |
117 | .typ-strong {
118 | font-weight: bold;
119 | }
120 |
121 | .typ-emph {
122 | font-style: italic;
123 | }
124 |
125 | .typ-link {
126 | text-decoration: underline;
127 | }
128 |
129 | .typ-raw {
130 | color: #818181;
131 | }
132 |
133 | .typ-label {
134 | color: #1d6c76;
135 | }
136 |
137 | .typ-ref {
138 | color: #1d6c76;
139 | }
140 |
141 | .typ-heading {
142 | font-weight: bold;
143 | text-decoration: underline;
144 | }
145 |
146 | .typ-marker {
147 | color: #8b41b1;
148 | }
149 |
150 | .typ-term {
151 | font-weight: bold;
152 | }
153 |
154 | .typ-math-delim {
155 | color: #298e0d;
156 | }
157 |
158 | .typ-math-op {
159 | color: #1d6c76;
160 | }
161 |
162 | .typ-key {
163 | color: #d73a49;
164 | }
165 |
166 | .typ-num {
167 | color: #b60157;
168 | }
169 |
170 | .typ-str {
171 | color: #298e0d;
172 | }
173 |
174 | .typ-func {
175 | color: #4b69c6;
176 | }
177 |
178 | .typ-pol {
179 | color: #8b41b1;
180 | }
181 |
--------------------------------------------------------------------------------
/entries.typ:
--------------------------------------------------------------------------------
1 | #import "/globals.typ"
2 | #import "./utils.typ"
3 | #import "./themes/themes.typ"
4 |
5 | /// The generic entry creation function. This function is not meant to be called by the user. Instead, use the three entry variants, frontmatter, body, and appendix, to create entries.
6 | ///
7 | /// - section (string): The type of entry. Takes either "frontmatter", "body", or "appendix".
8 | /// - title (string): The title of the entry.
9 | /// - type (string): The type of entry. The possible values for this are decided by the theme.
10 | /// - date (datetime): The date that the entry occured at.
11 | /// - author (str): The author of the entry.
12 | /// - witness (str): The witness of the entry.
13 | /// - participants (array): The people who participated in the entry.
14 | /// - body (content): The content of the entry.
15 | #let create-entry(
16 | section: none,
17 | title: "",
18 | type: none,
19 | date: none,
20 | author: "",
21 | witness: "",
22 | participants: (),
23 | body,
24 | ) = {
25 | let (state, entry-label) = if section == "frontmatter" {
26 | (globals.frontmatter-entries, label("notebook-frontmatter"))
27 | } else if section == "body" {
28 | (globals.entries, label("notebook-body"))
29 | } else if section == "appendix" {
30 | (globals.appendix-entries, label("notebook-appendix"))
31 | } else {
32 | panic("No valid entry type selected")
33 | }
34 |
35 | state.update(entries => {
36 | // Inject the proper labels and settings changes into the user's entry body
37 | let final-body = if entries.len() == 0 {
38 | [#counter(page).update(1)] // Correctly set the page number for each section
39 | } + [
40 | #metadata(none) #entry-label
41 | #counter(footnote).update(0)
42 | ] + body // Place a label on blank content to the table of contents can find each entry
43 |
44 | entries.push((
45 | ctx: (
46 | title: title,
47 | type: type,
48 | date: date,
49 | author: author,
50 | witness: witness,
51 | participants: participants
52 | ),
53 | body: final-body,
54 | ))
55 | entries
56 | })
57 | }
58 |
59 | /// Variant of the `#create-entry()` function that creates a frontmatter entry.
60 | ///
61 | /// *Example Usage:*
62 | ///
63 | /// ```typ
64 | /// #create-frontmatter-entry(title: "Frontmatter")[
65 | /// #lorem(50)
66 | /// ]
67 | /// ```
68 | ///
69 | #let create-frontmatter-entry = create-entry.with(section: "frontmatter")
70 |
71 | /// Variant of the `#create-entry()` function that creates a body entry.
72 | ///
73 | /// *Example Usage:*
74 | ///
75 | /// ```typ
76 | /// #create-body-entry(
77 | /// title: "Title",
78 | /// date: datetime(year: 2024, month: 1, day: 1),
79 | /// type: "identify", // Change this depending on what your theme allows
80 | /// author: "Bobert",
81 | /// witness: "Bobernius",
82 | /// )[
83 | /// #lorem(50)
84 | /// ]
85 | /// ```
86 | #let create-body-entry = create-entry.with(section: "body")
87 |
88 | /// Variant of the `#create-entry()` function that creates an appendix entry.
89 | ///
90 | /// *Example Usage:*
91 | ///
92 | /// ```typ
93 | /// #create-appendix-entry(title: "Appendix")[
94 | /// #lorem(50)
95 | /// ]
96 | /// ```
97 | #let create-appendix-entry = create-entry.with(section: "appendix")
98 |
--------------------------------------------------------------------------------
/gallery/radial.typ:
--------------------------------------------------------------------------------
1 | #import "/lib.typ": *
2 | #import themes.radial: radial-theme, components, colors
3 | #import colors: *
4 |
5 | #show: notebook.with(
6 | theme: radial-theme,
7 | team-name: "53E",
8 | season: "Over Under",
9 | )
10 |
11 | #create-frontmatter-entry(
12 | title: "test",
13 | type: "decide",
14 | date: datetime(year: 2024, month: 1, day: 1),
15 | )[
16 | #components.toc()
17 | ]
18 |
19 | #create-body-entry(
20 | title: "Title",
21 | type: "decide",
22 | date: datetime(year: 2024, month: 1, day: 1),
23 | )[
24 | = Heading
25 |
26 | #lorem(20)
27 |
28 | #grid(
29 | columns: (1fr, 1fr),
30 | gutter: 20pt,
31 | lorem(40),
32 | components.pie-chart(
33 | (value: 8, color: green, name: "wins"),
34 | (value: 2, color: red, name: "losses"),
35 | ),
36 | )
37 |
38 | #lorem(23)
39 |
40 | = Heading
41 |
42 | #lorem(40)
43 |
44 | #components.decision-matrix(
45 | properties: (
46 | (name: "property 1", weight: 2),
47 | (name: "property 2", weight: 0.5),
48 | (name: "property 3", weight: 0.33),
49 | (name: "property 4", weight: 0.01),
50 | ),
51 | ("choice 1", 5, 2, 3, 4),
52 | ("choice 2", 1, 2, 3, 1),
53 | ("choice 3", 1, 3, 3, 2),
54 | ("choice 4", 1, 2, 3, 5),
55 | ("choice 5", 1, 2, 3, 1),
56 | )
57 |
58 | #lorem(20)
59 |
60 | #components.admonition(type: "decision")[#lorem(20)]
61 |
62 | = Heading
63 |
64 | ```cpp
65 | #include
66 |
67 | int main() {
68 | printf("hello world\n")
69 | return 0;
70 | }
71 | ```
72 |
73 | ]
74 |
75 | #create-body-entry(
76 | title: "Title",
77 | type: "test",
78 | date: datetime(year: 2024, month: 1, day: 1),
79 | )[
80 | = Heading
81 | #lorem(20)
82 | #components.admonition(type: "note")[#lorem(50)]
83 |
84 | = Heading
85 |
86 | #lorem(20)
87 |
88 | #components.plot(
89 | title: "My Epic Graph",
90 | (name: "thing 1", data: ((1, 2), (2, 5), (3, 5))),
91 | (name: "thing 2", data: ((1, 1), (2, 7), (3, 6))),
92 | (name: "thing 3", data: ((1, 1), (2, 3), (3, 8))),
93 | )
94 |
95 | #grid(
96 | columns: (1fr, 1fr),
97 | gutter: 20pt,
98 | components.admonition(type: "warning")[#lorem(20)],
99 | lorem(20),
100 | )
101 |
102 | ]
103 |
104 | #create-body-entry(
105 | title: "Title",
106 | type: "management",
107 | date: datetime(year: 2024, month: 1, day: 1),
108 | )[
109 | = Heading
110 |
111 | #lorem(50)
112 |
113 | #align(
114 | center,
115 | components.pie-chart(
116 | (value: 2985, color: yellow, name: "Competitions"),
117 | (value: 3000, color: blue, name: "Travel"),
118 | (value: 2400, color: red, name: "Materials"),
119 | ),
120 | )
121 |
122 | #lorem(50)
123 |
124 | = Heading
125 |
126 | #components.gantt-chart(
127 | start: datetime(year: 2024, month: 1, day: 27),
128 | end: datetime(year: 2024, month: 2, day: 3),
129 | tasks: (
130 | ("Build Robot", (0, 4)),
131 | ("Code Robot", (3, 6)),
132 | ("Drive Robot", (5, 7)),
133 | ("Destroy Robot", (7, 8)),
134 | ),
135 | goals: (("Tournament", 4),),
136 | )
137 |
138 | #lorem(40)
139 |
140 | #components.admonition(type: "example")[#lorem(50)]
141 |
142 | ]
143 |
--------------------------------------------------------------------------------
/themes/radial/entries.typ:
--------------------------------------------------------------------------------
1 | #import "./colors.typ": *
2 | #import "./icons/icons.typ"
3 | #import "./components/components.typ"
4 | #import "./components/title.typ": *
5 | #import "/utils.typ"
6 | #import "./metadata.typ": entry-type-metadata
7 |
8 | // TODO: make an actual cover
9 | #let cover = utils.make-cover(ctx =>{
10 | import components: label
11 | let label = label.with(size: 4.7em, radius: 6pt)
12 |
13 | align(center + horizon, text(size: 24pt)[
14 | Radial Theme
15 | ])
16 |
17 | place(dx: 90pt, dy: -340pt, label("identify"))
18 | place(dx: 52pt, dy: -295pt, label("brainstorm"))
19 |
20 | place(dx: 520pt, dy: 190pt, label("decide"))
21 | place(dx: 490pt, dy: 240pt, label("build"))
22 | place(dx: 460pt, dy: 290pt, label("test"))
23 |
24 | place(dx: 150pt, dy: -160pt, rect(width: 50%, height: 300pt, fill: rgb("#eeeeeeff"), radius: (right: 20pt, left: 20pt)))
25 |
26 | place(dx: 125pt, dy: -180pt, label("management"))
27 | place(dx: 425pt, dy: 105pt, label("management"))
28 |
29 | place(dx: 520pt, dy: -270pt, rect(width: 9%, height: 55pt, fill: rgb("#eeeeeeff"), radius: (right: 5pt, left: 5pt)))
30 | place(dx: 455pt, dy: -335pt, rect(width: 9%, height: 55pt, fill: rgb("#eeeeeeff"), radius: (right: 5pt, left: 5pt)))
31 | place(dx: 490pt, dy: -300pt, label("program"))
32 |
33 | place(dx: 55pt, dy: 205pt, rect(width: 9%, height: 55pt, fill: rgb("#eeeeeeff"), radius: (right: 5pt, left: 5pt)))
34 | place(dx: 120pt, dy: 275pt, rect(width: 9%, height: 55pt, fill: rgb("#eeeeeeff"), radius: (right: 5pt, left: 5pt)))
35 | place(dx: 90pt, dy: 240pt, label("notebook"))
36 |
37 | place(dx: 165pt, dy: 200pt, line(length: 45%, stroke: 3.5pt + black))
38 |
39 | place(dx: 165pt, dy: -215pt, line(length: 45%, stroke: 3.5pt + black))
40 |
41 | place(dx: 250pt, dy: -280pt, text(size: 18pt)[
42 | *Radial Theme*
43 | ])
44 |
45 | place(dx: 225pt, dy: -250pt, text(size: 24pt)[
46 | *[Game Season]*
47 | ])
48 |
49 | place(dx: 235pt, dy: 165pt, text(size: 24pt)[
50 | *[Team Name]*
51 | ])
52 |
53 | place(dx: 0pt, dy: -370pt, figure(
54 | image("Mediamodifier-Design.svg", width: 118%)
55 | ))
56 |
57 |
58 |
59 | })
60 |
61 |
62 | #let frontmatter-entry = utils.make-frontmatter-entry((ctx, body) => {
63 | show: page.with(
64 | header: title(ctx.title),
65 | footer: align(right, context counter(page).display("i")),
66 | )
67 | body
68 | })
69 |
70 | #let body-entry = utils.make-body-entry((ctx, body) => {
71 | let metadata = entry-type-metadata.at(ctx.type)
72 | show: page.with(
73 | header: title(
74 | beginning: image.decode(
75 | utils.change-icon-color(raw-icon: metadata.icon, fill: white),
76 | height: 1em,
77 | ),
78 | end: ctx.date.display("[year]/[month]/[day]"),
79 | color: metadata.color,
80 | ctx.title,
81 | ),
82 | footer: [
83 | #line(length: 100%)
84 | #align(
85 | left,
86 | [
87 | *Designed by:* #ctx.author #h(2pt) \
88 | *Witnessed by:* #ctx.witness
89 | #h(1fr) #context counter(page).display()
90 | ],
91 | )
92 | ],
93 | )
94 | body
95 | })
96 |
97 | #let appendix-entry = utils.make-appendix-entry((ctx, body) => {
98 | show: page.with(
99 | header: title(ctx.title),
100 | footer: align(right, context counter(page).display()),
101 | )
102 |
103 | body
104 | })
105 |
--------------------------------------------------------------------------------
/themes/linear/entries.typ:
--------------------------------------------------------------------------------
1 | #import "format.typ": *
2 | #import "./colors.typ": *
3 | #import "/utils.typ"
4 |
5 | #let cover = utils.make-cover(ctx => {
6 | v(50pt)
7 |
8 | line(
9 | length: 100%,
10 | stroke: 2pt,
11 | )
12 | h(5pt)
13 | rect(
14 | inset: 30pt,
15 | fill: surface-0,
16 | width: 100%,
17 | )[
18 | #grid(
19 | columns: (1fr, 3fr),
20 | gutter: 2fr,
21 | [
22 | #set text(72pt)
23 | #ctx.team-name
24 | ],
25 | [
26 | #align(
27 | right,
28 | [
29 | #set text(20pt)
30 | #ctx.season
31 |
32 | Engineering Design Notebook
33 | ],
34 | )
35 | ],
36 | )
37 | ]
38 | h(5pt)
39 | line(
40 | length: 100%,
41 | stroke: 2pt,
42 | )
43 |
44 | place(
45 | center + bottom,
46 | dy: -50pt,
47 | [
48 | #set text(20pt)
49 | #box(
50 | width: 150pt,
51 | stroke: (
52 | top: white,
53 | bottom: white,
54 | left: black,
55 | right: black,
56 | ),
57 | ctx.year,
58 | )
59 | ],
60 | )
61 | })
62 |
63 | #let frontmatter-entry = utils.make-frontmatter-entry((
64 | ctx,
65 | body,
66 | ) => {
67 | show: page.with(header: {
68 | set text(size: 25pt)
69 | set line(stroke: 1.5pt)
70 | set align(center + horizon)
71 | grid(
72 | columns: (1fr, auto, 1fr),
73 | line(length: 100%),
74 | {
75 | h(20pt)
76 | ctx.title
77 | h(20pt)
78 | },
79 | line(length: 100%),
80 | )
81 | })
82 |
83 | set-border(ctx.type)
84 |
85 | body
86 | })
87 |
88 | #let body-entry = utils.make-body-entry((
89 | ctx,
90 | body,
91 | ) => {
92 | show: page.with(
93 | margin: (top: 88pt),
94 | header: {
95 | set text(size: 30pt)
96 | set line(stroke: 1.5pt)
97 |
98 | set align(center + horizon)
99 | grid(
100 | columns: (1fr, auto, 1fr),
101 | line(length: 100%),
102 | {
103 | h(20pt)
104 | box(
105 | fill: entry-type-metadata.at(ctx.type),
106 | outset: 10pt,
107 | ctx.title,
108 | )
109 | h(20pt)
110 | },
111 | line(length: 100%),
112 | )
113 | },
114 | footer: {
115 | grid(
116 | columns: (2fr, 2fr, 1fr),
117 | [Written by: #h(10pt) #ctx.author],
118 | [Witnessed by: #h(10pt) #ctx.witness],
119 | align(
120 | right,
121 | box(
122 | fill: surface-1,
123 | outset: 8pt,
124 | context counter(page).display(),
125 | ),
126 | ),
127 | )
128 | },
129 | )
130 | set-border(ctx.type)
131 |
132 | show heading: it => {
133 | set-heading(
134 | it,
135 | ctx.type,
136 | )
137 | }
138 |
139 | show raw.where(block: false): box.with(
140 | fill: surface-1,
141 | inset: (
142 | x: 4pt,
143 | y: 0pt,
144 | ),
145 | outset: (
146 | x: 0pt,
147 | y: 4pt,
148 | ),
149 | )
150 | show raw.where(block: true): block.with(
151 | fill: surface-1,
152 | inset: 8pt,
153 | width: 100%,
154 | )
155 |
156 | body
157 | })
158 |
159 | #let appendix-entry = utils.make-appendix-entry((
160 | ctx,
161 | body,
162 | ) => {
163 | show: page.with(header: [
164 | #set text(size: 25pt)
165 | #set line(stroke: 1.5pt)
166 | #align(
167 | center + horizon,
168 | grid(
169 | columns: (
170 | 1fr,
171 | auto,
172 | 1fr,
173 | ),
174 | [
175 | #line(length: 100%)
176 | ],
177 | [
178 | #h(20pt)
179 | #ctx.title
180 | #h(20pt)
181 | ],
182 | [
183 | #line(length: 100%)
184 | ],
185 | ),
186 | )
187 | ])
188 |
189 | set-border(ctx.type)
190 |
191 | body
192 | })
193 |
--------------------------------------------------------------------------------
/themes/default/components.typ:
--------------------------------------------------------------------------------
1 | #import "/utils.typ"
2 |
3 | /// Prints the table of contents.
4 | ///
5 | /// *Example Usage*
6 | ///
7 | /// ```typ
8 | /// #create-frontmatter-entry(title: "Table Of Contents")[
9 | /// #components.toc()
10 | /// ]
11 | /// ```
12 | /// -> content
13 | #let toc = utils.make-toc((_, body, appendix) => {
14 | heading[Contents]
15 | stack(
16 | spacing: 0.5em,
17 | ..for entry in body {
18 | (
19 | [
20 | #entry.title
21 | #box(
22 | width: 1fr,
23 | line(
24 | length: 100%,
25 | stroke: (dash: "dotted"),
26 | ),
27 | )
28 | #entry.page-number
29 | ],
30 | )
31 | },
32 | )
33 |
34 | heading[Appendix]
35 |
36 | stack(
37 | spacing: 0.5em,
38 | ..for entry in appendix {
39 | (
40 | [
41 | #entry.title
42 | #box(
43 | width: 1fr,
44 | line(
45 | length: 100%,
46 | stroke: (
47 | dash: "dotted",
48 | ),
49 | ),
50 | )
51 | #entry.page-number
52 | ],
53 | )
54 | })
55 | })
56 |
57 | /// Prints out the glossary.
58 | ///
59 | /// *Example Usage*
60 | ///
61 | /// ```typ
62 | /// #glossary.add-term("Foo", lorem(10))
63 | /// #glossary.add-term("Bar", lorem(5))
64 | /// #components.glossary()
65 | /// ```
66 | /// -> content
67 | #let glossary = utils.make-glossary(glossary => {
68 | stack(
69 | spacing: 0.5em,
70 | ..for entry in glossary {
71 | (
72 | [
73 | = #entry.word
74 |
75 | #entry.definition
76 | ],
77 | )
78 | },
79 | )
80 | })
81 |
82 | /// Prints a decision matrix table.
83 | ///
84 | /// *Example Usage*
85 | ///
86 | /// #example(
87 | /// `components.decision-matrix(
88 | /// properties: (
89 | /// "Cat. 1", // weights will default to 1
90 | /// "Cat. 2",
91 | /// "Cat. 3",
92 | /// ),
93 | /// ("Choice 1", 4, 3, 2),
94 | /// ("Choice 2", 1, 2, 3),
95 | /// )`,
96 | /// scale-preview: 100%
97 | /// )
98 | ///
99 | /// #example(
100 | /// `components.decision-matrix(
101 | /// properties: (
102 | /// (name: "Flavor", weight: 2),
103 | /// (name: "Crunch", weight: 1),
104 | /// ),
105 | /// ("Sweet Potato", 1, 2),
106 | /// ("Baked Potato", 2, 1)
107 | /// )
108 | /// `,
109 | /// scale-preview: 100%
110 | /// )
111 | /// - properties (array): A list of the properties that each choice will be rated by and the weight of each property
112 | /// - ..choices (array): An array containing the name of the choices as its first member,
113 | /// and values for each of the properties at its following indices
114 | ///
115 | /// -> content
116 | #let decision-matrix = utils.make-decision-matrix((properties, data) => {
117 | table(
118 | columns: for _ in range(properties.len() + 2) {
119 | (1fr,)
120 | },
121 | [],
122 | ..for property in properties {
123 | ([ *#property.name* ],)
124 | },
125 | [*Total*],
126 | ..for (index, choice) in data {
127 | let cell = if choice.total.highest {
128 | table.cell.with(fill: green)
129 | } else {
130 | table.cell
131 | }
132 | (
133 | cell[*#index*],
134 | ..for value in choice.values() {
135 | (cell[#value.weighted],)
136 | },
137 | )
138 | },
139 | )
140 | })
141 |
142 | /// Prints a pros and cons table.
143 | ///
144 | /// *Example Usage*
145 | ///
146 | /// #example(`components.pro-con(pros: lorem(10), cons: lorem(5))`, scale-preview: 100%)
147 | ///
148 | /// #example(
149 | /// `components.pro-con(
150 | /// pros: [
151 | /// #list(
152 | /// "Sweet potato",
153 | /// "Baked potato"
154 | /// )
155 | /// ],
156 | /// cons: [
157 | /// #list(
158 | /// "Fries",
159 | /// "Wedges"
160 | /// )
161 | /// ]
162 | /// )
163 | /// `, scale-preview: 100%)
164 | /// - pros (content): The positive aspects
165 | /// - cons (content): The negative aspects
166 | /// -> content
167 | #let pro-con = utils.make-pro-con((pros, cons) => {
168 | table(
169 | columns: (
170 | 1fr,
171 | 1fr,
172 | ),
173 | table.cell(fill: green)[*Pros*],
174 | table.cell(fill: red)[*Cons*],
175 | pros,
176 | cons,
177 | )
178 | })
179 |
--------------------------------------------------------------------------------
/utils/theme.typ:
--------------------------------------------------------------------------------
1 | /*
2 | This file contains all of the constructors for core theme related constructors.
3 | All of the constructors also perform checks to ensure that all of the types are correct.
4 | */
5 |
6 | #let check-type(ctx, field, expected-type) = {
7 | let given-type = type(
8 | ctx.at(field),
9 | )
10 |
11 | assert.eq(
12 | given-type,
13 | expected-type,
14 | message: "Expected " + field + " to be of type " + str(expected-type) + ", but got: " + given-type,
15 | )
16 | }
17 |
18 | #let check-multiple-types(ctx, fields, expected-type) = {
19 | for field in fields {
20 | check-type(
21 | ctx,
22 | field,
23 | expected-type,
24 | )
25 | }
26 | }
27 |
28 | /// A constructor for theme variables
29 | ///
30 | /// - rules (function): A function constructed by `make-rules`
31 | /// - cover (function): A function constructed by `make-cover`
32 | /// - frontmatter-entry (function): A function constructed by `make-frontmatter-entry`
33 | /// - body-entry (function): A function constructed by `make-body-entry`
34 | /// - appendix-entry (function): A function constructed by `make-appendix-entry`
35 | /// -> dictionary
36 | #let make-theme(
37 | rules: none,
38 | cover: none,
39 | frontmatter-entry: none,
40 | body-entry: none,
41 | appendix-entry: none,
42 | ) = {
43 | for input in (
44 | rules,
45 | cover,
46 | frontmatter-entry,
47 | body-entry,
48 | appendix-entry,
49 | ) {
50 | assert.eq(
51 | type(input),
52 | function,
53 | )
54 | }
55 |
56 | return (
57 | rules: rules,
58 | cover: cover,
59 | frontmatter-entry: frontmatter-entry,
60 | body-entry: body-entry,
61 | appendix-entry: appendix-entry,
62 | )
63 | }
64 |
65 | /// A constructor for a rules function. The resulting function will take the whole document as input, and can modify it in any arbitrary way.
66 | ///
67 | /// - callback (function): A function that returns the content of the document, and takes a `doc` parameter as input.
68 | /// -> function
69 | #let make-rules(callback) = {
70 | assert.eq(
71 | type(
72 | callback([test]),
73 | ),
74 | content,
75 | message: "The callback function does not return content. Make sure that you've properly returned the document.",
76 | )
77 |
78 | return doc => {
79 | callback(doc)
80 | }
81 | }
82 |
83 | /// A constructor for a cover function. The resulting function will be displayed inside of a page with no margins, as the cover of the notebook.
84 | ///
85 | /// - callback (function): A function that returns a cover, and takes a named `ctx` argument.
86 | /// -> function
87 | #let make-cover(callback) = {
88 | return (ctx: (:)) => {
89 | check-multiple-types(
90 | ctx,
91 | (
92 | "team-name",
93 | "season",
94 | "year",
95 | ),
96 | str,
97 | )
98 |
99 | callback(ctx)
100 | }
101 | }
102 |
103 | /// A constructor for frontmatter entry function. The resulting function should return the content of an entry as output.
104 | ///
105 | /// - callback (function): A function that returns an entry, and takes a named `ctx` argument, and a `body` positional argument.
106 | /// -> function
107 | #let make-frontmatter-entry(callback) = {
108 | assert.eq(type(callback), function)
109 |
110 | return (ctx: (:), body) => {
111 | check-type(ctx, "title", str)
112 |
113 | callback(ctx, body)
114 | }
115 | }
116 |
117 | /// A constructor for a body entry function. The resulting function should return the content of an entry as output.
118 | ///
119 | /// - callback (function): A function that returns an entry, and takes a named `ctx` argument, and a `body` positional argument.
120 | /// -> function
121 | #let make-body-entry(callback) = {
122 | assert.eq(type(callback), function)
123 |
124 | return (ctx: (:), body) => {
125 | let valid-entry-types = (
126 | "identify",
127 | "brainstorm",
128 | "decide",
129 | "build",
130 | "program",
131 | "test",
132 | "management",
133 | "notebook",
134 | )
135 |
136 | let valid-types-printable = valid-entry-types.fold(
137 | "",
138 | (base, value) => {
139 | base + " '" + value + "'"
140 | },
141 | )
142 |
143 | check-multiple-types(
144 | ctx,
145 | (
146 | "title",
147 | "type",
148 | "author",
149 | "witness",
150 | ),
151 | str,
152 | )
153 |
154 | check-type(ctx, "date", datetime)
155 | check-type(ctx, "participants", array)
156 |
157 | if not valid-entry-types.contains(ctx.type) {
158 | panic("Entry type '" + str(ctx.type) + "' is not valid. Valid types include:" + valid-types-printable)
159 | }
160 |
161 | callback(ctx, body)
162 | }
163 | }
164 |
165 | // All of the check logic is exactly the same, so we can just use the frontmatter-entry here
166 |
167 | /// A constructor for an appendix entry function. The resulting function should return the content of an entry as output.
168 | ///
169 | /// - callback (function): A function that returns an entry, and takes a named `ctx` argument, and a `body` positional argument.
170 | /// -> function
171 | #let make-appendix-entry = make-frontmatter-entry
172 |
--------------------------------------------------------------------------------
/themes/radial/components/gantt-chart.typ:
--------------------------------------------------------------------------------
1 | #import "/packages.typ": timeliney
2 | #import "../colors.typ": *
3 |
4 | /// A gantt chart for task management
5 | ///
6 | /// Example Usage:
7 | ///
8 | /// ```typ
9 | /// #gantt-chart(
10 | /// start: datetime(year: 2024, month: 1, day: 27),
11 | /// end: datetime(year: 2024, month: 2, day: 3),
12 | /// tasks: (
13 | /// ("Build Robot", (0,4)),
14 | /// ("Code Robot", (3,6)),
15 | /// ("Drive Robot", (5,7)),
16 | /// ("Destroy Robot", (7,8)),
17 | /// ),
18 | /// goals: (
19 | /// ("Tournament", 4),
20 | /// )
21 | /// )
22 | /// ```
23 | /// - start (datetime): Start date using datetime object
24 | /// - year: ``
25 | /// - month: ``
26 | /// - day: ``
27 | /// Example usage: ```typ datetime(year: 2024, month: 7, day: 16)```
28 | /// - end (datetime): End date using datetime object
29 | /// - year: ``
30 | /// - month: ``
31 | /// - day: ``
32 | /// Example usage: ```typ datetime(year: 2024, month: 5, day: 2)```
33 | /// - date-interval (integer): The interval between dates, seven would make it weekly
34 | /// - date-format (string): The way the date is formatted using the `` method
35 | /// - tasks (array): Specify tasks using an array of arrays that have three fields each
36 | /// + `` or `` The name of the task
37 | /// + ``(`` or ``, `` or ``) The start and end point of the task
38 | /// + `` The color of the task line (optional)
39 | /// Example sub-array: ```typ ("Build Catapult", (1,5), red)```
40 | /// - goals (array): Add goal markers using an array of arrays that have three fields each
41 | /// + `` or `` The name of the goal
42 | /// + `` or `` The position of the goal
43 | /// + `` The color of the goal box (optional)
44 | /// - Default is grey, but put none for no box
45 | /// Example sub-array: ```typ ("Worlds", 6, red)```
46 | /// -> content
47 | #let gantt-chart(
48 | start: datetime,
49 | end: datetime,
50 | date-interval: 1,
51 | date-format: "[month]/[day]",
52 | tasks: (),
53 | goals: none,
54 | ) = {
55 | timeliney.timeline(
56 | spacing: 5pt,
57 | show-grid: true,
58 | grid-style: (stroke: (dash: none, thickness: .2pt, paint: black)),
59 | tasks-vline: true,
60 | line-style: (stroke: 0pt),
61 | milestone-overhang: 3pt,
62 | milestone-layout: "in-place",
63 | box-milestones: true,
64 | milestone-line-style: (stroke: (dash: none, thickness: 1pt, paint: black)),
65 | {
66 | import timeliney: *
67 |
68 | let difference = end - start
69 | let dates-array = ()
70 | let months-array = ()
71 | let month-len = 0
72 | let last-month = start.month()
73 | let next
74 |
75 | for value in range(int((difference.days()) / date-interval) + 1) {
76 | next = start + duration(days: (value * date-interval))
77 | dates-array.push(group(((next).display(date-format), 1)))
78 | if next.month() == last-month {
79 | month-len += 1
80 | last-month = next.month()
81 | } else {
82 | months-array.push(
83 | group((
84 | (
85 | datetime(year: next.year(), month: last-month, day: 1)
86 | ).display("[month repr:long]"),
87 | month-len,
88 | )),
89 | )
90 | month-len = 1
91 | last-month = next.month()
92 | }
93 | }
94 | months-array.push(
95 | group((
96 | (
97 | datetime(year: next.year(), month: last-month, day: 1)
98 | ).display("[month repr:long]"),
99 | month-len,
100 | )),
101 | )
102 |
103 | headerline(..months-array)
104 | headerline(..dates-array)
105 |
106 | let goal-color
107 |
108 | if goals != none {
109 | for goal in goals {
110 | if goal.len() == 2 {
111 | goal-color = surface-2
112 | } else {
113 | goal-color = goal.at(2)
114 | }
115 | milestone(
116 | [#box(fill: goal-color, outset: 3pt, radius: 1.5pt)[#goal.at(0)]],
117 | at: goal.at(1),
118 | )
119 | }
120 | }
121 |
122 | let colors = (red, orange, yellow, green, blue, violet)
123 | let index = 0
124 | let size = difference.days() / date-interval
125 | let pos = ()
126 |
127 | taskgroup({
128 | for item in tasks {
129 | pos = (
130 | item.at(1).at(0) + size * 0.007,
131 | item.at(1).at(1) - size * 0.007,
132 | )
133 | if item.len() == 2 {
134 | if index == colors.len() - 1 {
135 | index = 0
136 | }
137 | task(
138 | item.at(0),
139 | pos,
140 | style: (
141 | stroke: (paint: colors.at(index), thickness: 5pt, cap: "round"),
142 | ),
143 | )
144 | index += 1
145 | } else {
146 | task(
147 | item.at(0),
148 | pos,
149 | style: (
150 | stroke: (paint: item.at(2), thickness: 5pt, cap: "round"),
151 | ),
152 | )
153 | }
154 | }
155 | })
156 | },
157 | )
158 | }
159 |
--------------------------------------------------------------------------------
/docs/src/basic_usage.md:
--------------------------------------------------------------------------------
1 | # Basic Usage
2 |
3 | Now that you have the Notebookinator installed, you can start notebooking.
4 |
5 | ## Setup
6 |
7 | You can use our [template](https://github.com/The-Notebookinator/quick-start-template) either by creating a GitHub repository based on it with GitHub's official template feature, or just by downloading it. You can download the template simply by cloning it.
8 |
9 | ```sh
10 | git clone https://github.com/The-Notebookinator/quick-start-template.git
11 | # alternatively if you made your own repository you can clone it like this:
12 | git clone
13 | ```
14 |
15 | Once you've done that, open the newly downloaded folder inside of VSCode or your editor of choice.
16 |
17 | ## Editing Your Notebook
18 |
19 | ### Adding New Entries
20 |
21 | The Notebookinator allows for three different types of entries, frontmatter, body, and appendix. Each will be rendered as its own section, and has its own page count.
22 |
23 | #### Frontmatter
24 |
25 | Frontmatter entries, as their name implies, are shown at the beginning of the notebook. Entries of this type typically contain things like introductions, and the table of contents.
26 |
27 | The template stores all of the frontmatter entries into the `frontmatter.typ` file by default. To add more frontmatter entries, simply call the `create-frontmatter-entry` function inside of the file like so:
28 |
29 | ```typ
30 | #create-frontmatter-entry(title: "About")[
31 | Here's some info about this amazing notebook!
32 | ]
33 | ```
34 |
35 | Frontmatter entries are rendered in the order they are created.
36 |
37 | #### Body
38 |
39 | The most common type of entry is the body entry. These entries store all of your notebook's main content.
40 |
41 | The template puts all of the body entries inside of the `entries/` folder. To make a new entry, make a new file in that folder. Then, `#include` that file in the `entries/entries.typ` file. For example, if you created a file called `entries/my-entry.typ`, then you'd add this line to your `entries/entries.typ` file:
42 |
43 | ```typ
44 | #include "./my-entry.typ"
45 | ```
46 |
47 | Body entries will be displayed in the order they are included in the `entries/entries.typ` file.
48 |
49 | Once you've done that, you'll need to create a new entry inside of that file. This can be done with the `create-body-entry` function. If the file only contains a single entry, we recommend using a show rule to wrap the function as well, which will pass all of the `content` in the file into the `create-body-entry` function.
50 |
51 | You can create a new body entry like so:
52 |
53 | ```typ
54 | // not all themes require every one of these options
55 | #show: create-body-entry.with(
56 | title: "My Awesome Entry",
57 | type: "identify", // The type of the entry depends on which theme you're using
58 | date: datetime(year: 2024, month: 1, day: 1),
59 | )
60 | ```
61 |
62 | #### Appendix
63 |
64 | Appendix entries go at the end of the notebook, and are stored in the `appendix.typ` file.
65 |
66 | You can create a new appendix entry like this:
67 |
68 | ```typ
69 | #create-appendix-entry(title: "Programming")[
70 | Here's information about how we programmed the robot.
71 |
72 | #lorem(500)
73 | ]
74 | ```
75 |
76 | ### Changing the Theme
77 |
78 | In order to change the theme you'll need to edit two files, `packages.typ` and `main.typ`.
79 |
80 | The first thing you'll need to do is edit which theme is being imported in `packages.typ`. For example, if you wanted to switch to the `linear` theme from the `radial` theme, you'd change `packages.typ` to look like this:
81 |
82 | ```typ
83 | // packages.typ
84 |
85 | // this file allows us to only specify package versions once
86 | #import "@local/notebookinator:1.0.1": *
87 | #import themes.linear: linear-theme, components // components is imported here so we don't have to specify which theme's components we're using.
88 | ```
89 |
90 | Once you do that, you'll want to edit your `main.typ` to use the `linear-theme` instead of the `radial-theme`.
91 |
92 | ```typ
93 | // main.typ
94 |
95 | #show: notebook.with(
96 | // ...
97 | theme: linear-theme,
98 | )
99 | ```
100 |
101 | ```admonish note
102 | Not all themes implement the same components, so you may encounter some issues when changing themes with a more developed notebook.
103 | ```
104 |
105 | ### Using Components
106 |
107 | Components are reusable elements created by themes. These are just functions stored inside a `components` module. Each theme should expose its own separate `components` module.
108 |
109 | `packages.typ` should already export this module, so you can access it just by `import`ing `packages.typ`
110 |
111 | ```typ
112 | #import "/packages.typ": *
113 | ```
114 |
115 | Now you can use any of the components in the theme by just calling them like you would a normal function. Here's how you would create a simple `pro-con` table.
116 |
117 | ```typ
118 | #components.pro-con(
119 | pros: [
120 | Here are the pros.
121 | ],
122 | cons: [
123 | Here are the cons.
124 | ]
125 | )
126 | ```
127 |
128 | You can see what components a theme implements by reading the [API reference](./reference.md).
129 |
130 | ## Compiling / Viewing Your Notebook
131 |
132 | Once you're happy with your notebook, you'll want to render it into a PDF.
133 |
134 | You can do that with either of the following commands:
135 |
136 | ```sh
137 | typst compile main.typ
138 | # or if you want live updates
139 | typst watch main.typ
140 | ```
141 |
142 | You can then open `main.pdf` in any PDF viewer to see your rendered output.
143 |
--------------------------------------------------------------------------------
/themes/radial/components/graphs.typ:
--------------------------------------------------------------------------------
1 | #import "../colors.typ": *
2 | #import "/packages.typ": cetz
3 | #import "/utils.typ"
4 |
5 | /// Creates a labeled pie chart.
6 | ///
7 | /// Example Usage:
8 | ///
9 | /// ```typ
10 | /// #pie-chart(
11 | /// (value: 8, color: green, name: "wins"),
12 | /// (value: 2, color: red, name: "losses")
13 | /// )
14 | /// ```
15 | ///
16 | /// - ..data (dictionary): Each dictionary must contain 3 fields.
17 | /// - value: `` The value of the section
18 | /// - color: `` The value of the section
19 | /// - name: `` The name of the section
20 | /// -> content
21 | #let pie-chart = utils.make-pie-chart(data => {
22 | let total
23 | let percentages = ()
24 |
25 | for value in data.pos() {
26 | total += value.value
27 | }
28 |
29 | for value in data.pos() {
30 | percentages.push(calc.round(value.value / total * 100))
31 | }
32 |
33 | cetz.canvas({
34 | import cetz.draw: *
35 |
36 | let chart(..values, name: none) = {
37 | let values = values.pos()
38 | let anchor-angles = ()
39 |
40 | let offset = 0
41 | let total = values.fold(0, (s, v) => s + v.value)
42 |
43 | let segment(from, to) = {
44 | merge-path(
45 | close: true,
46 | {
47 | stroke((paint: black, join: "round", thickness: 0pt))
48 | line((0, 0), (rel: (360deg * from, 2)))
49 | arc((), start: from * 360deg, stop: to * 360deg, radius: 2)
50 | },
51 | )
52 | }
53 |
54 | let chart = group(
55 | name: name,
56 | {
57 | stroke((paint: black, join: "round"))
58 |
59 | for v in values {
60 | fill(v.color)
61 | let value = v.value / total
62 |
63 | // Draw the segment
64 | segment(offset, offset + value)
65 |
66 | // Place an anchor for each segment
67 | let angle = offset * 360deg + value * 180deg
68 | anchor(v.name, (angle, 1.75))
69 | anchor-angles.push(angle)
70 |
71 | offset += value
72 | }
73 | },
74 | )
75 |
76 | return (chart, anchor-angles)
77 | }
78 |
79 | // Draw the chart
80 | let (chart, angles) = chart(..data, name: "chart")
81 |
82 | chart
83 |
84 | set-style(
85 | mark: (fill: white, start: "o", stroke: black),
86 | content: (padding: .1),
87 | )
88 | for (index, value) in data.pos().enumerate() {
89 | let anchor = "chart." + value.name
90 | let angle = angles.at(index)
91 |
92 | let (line-end, anchor-direction) = if angle > 90deg and angle < 275deg {
93 | ((-0.5, 0), "east")
94 | } else {
95 | ((0.5, 0), "west")
96 | }
97 |
98 | line(anchor, (to: anchor, rel: (angle, 0.5)), (rel: line-end))
99 |
100 | content((), [#value.name], anchor: "south-" + anchor-direction)
101 | content(
102 | (),
103 | [ #percentages.at(index)% ],
104 | anchor: "north-" + anchor-direction,
105 | )
106 | }
107 | })
108 | })
109 |
110 | /// Example Usage:
111 | /// ```typ
112 | /// #plot(
113 | /// title: "My Epic Graph",
114 | /// (name: "thingy", data: ((1,2), (2,5), (3,5))),
115 | /// (name: "stuff", data: ((1,1), (2,7), (3,6))),
116 | /// (name: "potato", data: ((1,1), (2,3), (3,8))),
117 | /// )
118 | /// ```
119 | ///
120 | /// - title (string): The title of the graph
121 | /// - x-label (string): The label on the x axis
122 | /// - y-label (string): The label on the y axis
123 | /// - ..data (dictionary):
124 | /// -> content
125 | #let plot = utils.make-plot((title, x-label, y-label, length, data) => {
126 | // The length of the whole plot is 8.5 units.
127 | let length = if length == auto {
128 | 8.5%
129 | } else {
130 | length / 8.5
131 | }
132 |
133 | // Will be populated by the names of the rows of data, and their corresponding colors
134 | let legend = ()
135 |
136 | set align(center)
137 | cetz.canvas(
138 | length: length,
139 | {
140 | import cetz.draw: *
141 | import cetz.plot
142 | import cetz.palette
143 |
144 | // Style for the data lines
145 | let plot-colors = (blue, red, green, yellow, pink, orange)
146 | let plot-style(i) = {
147 | let color = plot-colors.at(calc.rem(i, plot-colors.len()))
148 | return (
149 | stroke: (thickness: 1pt, paint: color, cap: "round", join: "round"),
150 | fill: color.lighten(75%),
151 | )
152 | }
153 |
154 | set-style(axes: (
155 | grid: (
156 | stroke: (paint: luma(66.67%), dash: "loosely-dotted", cap: "round"),
157 | fill: none,
158 | ),
159 | tick: (stroke: (cap: "round")),
160 | stroke: (cap: "round"),
161 | ))
162 |
163 | plot.plot(
164 | name: "plot",
165 | plot-style: plot-style,
166 | size: (9, 5),
167 | axis-style: "left",
168 | x-grid: "both",
169 | y-grid: "both",
170 | {
171 | for (index, row) in data.pos().enumerate() {
172 | plot.add(row.data)
173 | legend.push((color: plot-colors.at(index), name: row.name))
174 | }
175 | },
176 | )
177 |
178 | content("plot.north", [*#title*])
179 | content((to: "plot.south", rel: (0, -0.5)), [#x-label])
180 | content((to: "plot.west", rel: (-0.5, 0)), [#y-label], angle: 90deg)
181 | },
182 | )
183 |
184 | // legend time
185 | for label in legend [
186 | #box(rect(fill: label.color, radius: 1.5pt), width: 0.7em, height: 0.7em)
187 | #label.name
188 | #h(10pt)
189 | ]
190 | })
191 |
--------------------------------------------------------------------------------
/gallery/linear.typ:
--------------------------------------------------------------------------------
1 | #import "../lib.typ": *
2 | #import themes.linear: linear-theme
3 | #import themes.linear.components
4 |
5 | #show: notebook.with(
6 | theme: linear-theme,
7 | team-name: "53C",
8 | season: "Over Under",
9 | year: "2023-2024",
10 | )
11 |
12 | #create-frontmatter-entry(title: "Color Coding Guide")[
13 | This key represents each step of the Engineering Design Process with a color that corresponds to the Engineering Notebooking Rubric categories. In the table of contents, each page is assigned to a color which summarizes the content on that page. However, if a page highlights multiple sections of the Engineering Design Process, headings on the page will be colored accordingly.
14 |
15 | #grid(
16 | columns: (1fr, 5fr),
17 | gutter: 10pt,
18 | // Row 1
19 | square(size: 1in, fill: components.dark-red),
20 | align(horizon,
21 | [
22 | #set text(size: 14pt)
23 | Identify the Problem
24 |
25 | #set text(size: 12pt)
26 | Identifies the game and robot design challenges in detail at the start of each design process cycle with words and pictures. States the goals for accomplishing the challenge.
27 | ]),
28 | // Row 2
29 | square(size: 1in, fill: components.dark-yellow),
30 | align(horizon,
31 | [
32 | #set text(size: 14pt)
33 | Brainstorm, Diagram, or Prototype Solutions
34 |
35 | #set text(size: 12pt)
36 | Lists three or more possible solutions to the challenge with labeled diagrams. Citations provided for ideas that came from outside sources such as online videos or other teams.
37 | ]),
38 | // Row 3
39 | square(size: 1in, fill: components.dark-green),
40 | align(horizon,
41 | [
42 | #set text(size: 14pt)
43 | Select the Best Solution and Plan
44 |
45 | #set text(size: 12pt)
46 | Explains why the solution was selected through testing and/or a decision matrix. Fully describes the plan to implement the solution.
47 | ]),
48 | // Row 4
49 | square(size: 1in, fill: components.dark-blue),
50 | align(horizon,
51 | [
52 | #set text(size: 14pt)
53 | Build the Solution
54 |
55 | #set text(size: 12pt)
56 | Records the steps to build the solution. Includes enough detail that the reader can follow the logic used by the team to develop their robot design, as well as recreate the robot design from the documentation.
57 | ]),
58 | // Row 5
59 | square(size: 1in, fill: components.dark-purple),
60 | align(horizon,
61 | [
62 | #set text(size: 14pt)
63 | Program the Solution
64 |
65 | #set text(size: 12pt)
66 | Records the steps to program the solution. Includes enough detail that the reader can follow the logic used by the team to develop their robot code, as well as recreate the robot code from the documentation.
67 | ]),
68 | // Row 6
69 | square(size: 1in, fill: components.dark-pink),
70 | align(horizon,
71 | [
72 | #set text(size: 14pt)
73 | Test the Solution
74 |
75 | #set text(size: 12pt)
76 | Records all the steps to test the solution, including test results.
77 | ]),
78 | // Row 7
79 | square(size: 1in, fill: components.surface-4),
80 | align(horizon,
81 | [
82 | #set text(size: 14pt)
83 | Reflect on Prior Solutions
84 |
85 | #set text(size: 12pt)
86 | Evaluates the decisions and mistakes made in the past to better prepare and overcome challenges in the future.
87 | ])
88 | )
89 | We also wanted to create a new category: Reflect on Prior Solutions. We believe this is an important step in the Engineering Design Process and we want to highlight our reflections using our color coding guide.
90 | ]
91 |
92 | #create-frontmatter-entry(title: "Table of Contents")[
93 | #components.toc()
94 | ]
95 |
96 | #create-body-entry(
97 | title: "Title",
98 | type: "identify",
99 | date: datetime(year: 1111, month: 11, day: 1),
100 | )[
101 | = Heading 1
102 | #lorem(50)
103 | #grid(
104 | columns: (1fr, 1fr),
105 | gutter: 15pt,
106 | lorem(30),
107 | align(
108 | center + horizon,
109 | image("../logo.png"),
110 | ),
111 | )
112 | == Subheading 1
113 | #components.pro-con(
114 | pros: [
115 | #list(
116 | [#lorem(10)],
117 | [#lorem(12)],
118 | [#lorem(15)],
119 | )
120 | ],
121 | cons: [
122 | #list(
123 | [#lorem(12)],
124 | [#lorem(10)],
125 | )
126 | ],
127 | )
128 | == Subheading 2
129 | #components.decision-matrix(
130 | properties: (
131 | (name: "Category 1"),
132 | (name: "Category 2"),
133 | (name: "Category 3", weight: 2),
134 | ),
135 | ("Decision", 3, 1, 4),
136 | ("Matrix", 2, 3, 5),
137 | )
138 | = Heading 2
139 | ```cpp
140 | #include
141 | #include
142 |
143 | int main() {
144 | printf("Hello world!");
145 |
146 | return 0;
147 | }
148 | ```
149 | ]
150 |
151 | #create-body-entry(
152 | title: "Title",
153 | type: "brainstorm",
154 | date: datetime(year: 1111, month: 11, day: 2),
155 | )[]
156 | #create-body-entry(
157 | title: "Title",
158 | type: "brainstorm",
159 | date: datetime(year: 1111, month: 11, day: 2),
160 | )[]
161 | #create-body-entry(
162 | title: "Title",
163 | type: "brainstorm",
164 | date: datetime(year: 1111, month: 11, day: 2),
165 | )[]
166 | #create-body-entry(
167 | title: "Title",
168 | type: "brainstorm",
169 | date: datetime(year: 1111, month: 11, day: 2),
170 | )[]
171 | #create-body-entry(
172 | title: "Title",
173 | type: "decide",
174 | date: datetime(year: 1111, month: 11, day: 3),
175 | )[]
176 | #create-body-entry(
177 | title: "Title",
178 | type: "build",
179 | date: datetime(year: 1111, month: 11, day: 3),
180 | )[]
181 | #create-body-entry(
182 | title: "Title",
183 | type: "build",
184 | date: datetime(year: 1111, month: 11, day: 3),
185 | )[]
186 | #create-body-entry(
187 | title: "Title",
188 | type: "build",
189 | date: datetime(year: 1111, month: 11, day: 3),
190 | )[]
191 | #create-body-entry(
192 | title: "Title",
193 | type: "program",
194 | date: datetime(year: 1111, month: 11, day: 3),
195 | )[]
196 | #create-body-entry(
197 | title: "Title",
198 | type: "program",
199 | date: datetime(year: 1111, month: 11, day: 3),
200 | )[]
201 | #create-body-entry(
202 | title: "Title",
203 | type: "test",
204 | date: datetime(year: 1111, month: 11, day: 4),
205 | )[]
206 | #create-body-entry(
207 | title: "Title",
208 | type: "test",
209 | date: datetime(year: 1111, month: 11, day: 4),
210 | )[]
211 | #create-body-entry(
212 | title: "Title",
213 | type: "build",
214 | date: datetime(year: 1111, month: 11, day: 5),
215 | )[]
216 | #create-body-entry(
217 | title: "Title",
218 | type: "build",
219 | date: datetime(year: 1111, month: 11, day: 5),
220 | )[]
221 | #create-body-entry(
222 | title: "Title",
223 | type: "brainstorm",
224 | date: datetime(year: 1111, month: 11, day: 5),
225 | )[]
226 | #create-body-entry(
227 | title: "Title",
228 | type: "brainstorm",
229 | date: datetime(year: 1111, month: 11, day: 5),
230 | )[]
231 | #create-body-entry(
232 | title: "Title",
233 | type: "decide",
234 | date: datetime(year: 1111, month: 11, day: 5),
235 | )[]
236 | #create-body-entry(
237 | title: "Title",
238 | type: "program",
239 | date: datetime(year: 1111, month: 11, day: 5),
240 | )[]
241 | #create-body-entry(
242 | title: "Title",
243 | type: "test",
244 | date: datetime(year: 1111, month: 11, day: 5),
245 | )[]
246 |
247 | #glossary.add-term("Lorem")[
248 | #lorem(10)
249 | ]
250 |
251 | #glossary.add-term("Ipsum")[
252 | #lorem(25)
253 | ]
254 |
255 | #glossary.add-term("Dolor")[
256 | #lorem(11)
257 | ]
258 |
259 | #glossary.add-term("Sit")[
260 | #lorem(5)
261 | ]
262 |
263 | #create-appendix-entry(title: "Glossary")[
264 | #lorem(50)
265 | #components.glossary()
266 | ]
--------------------------------------------------------------------------------
/utils/components.typ:
--------------------------------------------------------------------------------
1 | #import "/globals.typ"
2 |
3 | /// A constructor for a table of contents component.
4 | ///
5 | /// *Example Usage:*
6 | /// ```typ
7 | /// #let toc = utils.make-toc((frontmatter, body, appendix) => {
8 | /// for entry in body [
9 | /// #entry.title
10 | /// #box(width: 1fr, line(length: 100%, stroke: (dash: "dotted")))
11 | /// #entry.page-number
12 | /// ]
13 | /// })
14 | /// ```
15 | /// - callback (function): A function which returns the content of the toc. The function must take `frontmatter`, `body`, and `appendix` arguments.
16 | /// -> function
17 | #let make-toc(callback) = {
18 | let helper(type) = {
19 | let (state, markers) = if type == "frontmatter" {
20 | (
21 | globals.frontmatter-entries,
22 | query(
23 | selector(),
24 | ),
25 | )
26 | } else if type == "body" {
27 | (
28 | globals.entries,
29 | query(
30 | selector(),
31 | ),
32 | )
33 | } else if type == "appendix" {
34 | (
35 | globals.appendix-entries,
36 | query(
37 | selector(),
38 | ),
39 | )
40 | } else {
41 | panic("No valid entry type selected.")
42 | }
43 |
44 | let result = ()
45 |
46 | for (index, entry) in state.final().enumerate() {
47 | let page-number = counter(page).at(
48 | markers.at(index).location(),
49 | ).at(0)
50 | let ctx = entry.ctx
51 | ctx.page-number = page-number
52 | result.push(ctx)
53 | }
54 | return result
55 | }
56 |
57 | return () => context {
58 | let frontmatter-entries = helper("frontmatter")
59 | let body-entries = helper("body")
60 | let appendix-entries = helper("appendix")
61 |
62 | callback(
63 | frontmatter-entries,
64 | body-entries,
65 | appendix-entries,
66 | )
67 | }
68 | }
69 |
70 | /// Constructor for a glossary component
71 | ///
72 | /// *Example Usage:*
73 | /// ```typ
74 | /// #let glossary = utils.make-glossary(glossary => {
75 | /// stack(
76 | /// spacing: 0.5em,
77 | /// ..for entry in glossary {
78 | /// (
79 | /// [
80 | /// = #entry.word
81 | ///
82 | /// #entry.definition
83 | /// ],
84 | /// )
85 | /// },
86 | /// )
87 | /// })
88 | /// ```
89 | /// - callback (function): A function that returns the content of the glossary. The function must take a `glossary` argument.
90 | /// -> function
91 | #let make-glossary(callback) = {
92 | return () => context {
93 | let sorted-glossary = globals.glossary-entries.final().sorted(key: (
94 | (word: word, definition: definition),
95 | ) => word)
96 | callback(sorted-glossary)
97 | }
98 | }
99 |
100 | /// Constructor for a pro / con component
101 | ///
102 | /// *Example Usage:*
103 | /// ```typ
104 | /// #let pro-con = utils.make-pro-con((pros, cons) => {
105 | /// table(
106 | /// columns: (
107 | /// 1fr,
108 | /// 1fr,
109 | /// ),
110 | /// table.cell(fill: green)[*Pros*],
111 | /// table.cell(fill: red)[*Cons*],
112 | /// pros,
113 | /// cons,
114 | /// )
115 | /// })
116 | /// ```
117 | ///
118 | /// - callback (function): A function that returns the content of the pro / con table. The function must take `pros` and `cons` arguments.
119 | /// -> function
120 | #let make-pro-con(callback) = {
121 | return (pros: [], cons: []) => {
122 | callback(pros, cons)
123 | }
124 | }
125 |
126 | /// Constructor for a decision matrix
127 | ///
128 | /// *Example Usage:*
129 | /// ```typ
130 | /// #let decision-matrix = utils.make-decision-matrix((properties, data) => {
131 | /// // ...
132 | /// })
133 | /// ```
134 | /// - callback (function): A function that returns the content of the matrix. The function must `properties` and `data` arguments.
135 | /// -> function
136 | #let make-decision-matrix(callback) = {
137 | return (properties: (), ..choices) => {
138 | // ensure the properties are passed in correctly
139 | //
140 | // this variable tracks whether the user
141 | // is using the alternate mode of passing in arguments,
142 | // where each property is a str instead of a dictionary
143 | let alternate-format = false
144 | for property in properties {
145 | if type(property) == str {
146 | alternate-format = true
147 | } else {
148 | assert(
149 | not alternate-format,
150 | message: "Property should be of type 'str'",
151 | )
152 |
153 | if property.at("weight", default: none) == none {
154 | property.insert("weight", 1)
155 | }
156 |
157 | assert.eq(type(property.name), str)
158 | assert(type(property.weight) == float or type(property.weight) == int)
159 | }
160 | }
161 |
162 | // ensure the choices are passed in correctly
163 | for choice in choices.pos() {
164 | for (index, rating) in choice.enumerate() {
165 | if index == 0 {
166 | assert.eq(type(rating), str)
167 | continue
168 | }
169 |
170 | assert(
171 | type(rating) == int or type(rating) == float,
172 | message: "Values for decision matrices must be of type 'float' or 'int'",
173 | )
174 | }
175 |
176 | assert.eq(
177 | choice.len() - 1,
178 | properties.len(),
179 | message: "The number of supplied values did not match the number of properties.",
180 | )
181 | }
182 |
183 | // the calculation should only need to parse data in one format,
184 | // so if the user passed in the alternate, format we'll just convert it to the standard one
185 | properties = if alternate-format {
186 | properties.map(property => (name: property, weight: 1))
187 | } else {
188 | properties
189 | }
190 |
191 | // now we can actually calculate the data
192 | let data = (:)
193 |
194 | for (index, choice) in choices.pos().enumerate() {
195 | let name = choice.at(0)
196 |
197 | let values = choice.slice(1)
198 | let unweighted-total = values.sum()
199 |
200 | let weighted-values = values.enumerate().map((
201 | (index, value),
202 | ) => value * properties.at(index).at(
203 | "weight",
204 | default: 1,
205 | ))
206 | let weighted-total = weighted-values.sum()
207 |
208 | let property-data = (:)
209 |
210 | for (index, property) in properties.enumerate() {
211 | property-data.insert(
212 | property.name,
213 | (
214 | unweighted: values.at(index),
215 | weighted: weighted-values.at(index),
216 | highest: false,
217 | ),
218 | )
219 | }
220 |
221 | property-data.insert(
222 | "total",
223 | (
224 | unweighted: unweighted-total,
225 | weighted: weighted-total,
226 | highest: false,
227 | ),
228 | )
229 |
230 | data.insert(
231 | name,
232 | property-data,
233 | )
234 | }
235 |
236 | // now that we've filled in all of the data, we can calculate which choice won
237 |
238 | // we're going to treat total like another property for the sake of calculating if it won
239 | properties.push((name: "total"))
240 |
241 | for property in properties {
242 | let highest = ( // Records the index of the choice which had the highest total
243 | index: 0,
244 | value: 0,
245 | )
246 |
247 | for (index, choice) in data {
248 | let property-value = choice.at(property.name).weighted
249 | if property-value > highest.value {
250 | highest.index = index
251 | highest.value = property-value
252 | }
253 | }
254 | data.at(highest.index).at(property.name).highest = true
255 | }
256 | properties.pop()
257 |
258 |
259 | return callback(properties, data)
260 | }
261 | }
262 |
263 | /// A constructor for an admonition component.
264 | ///
265 | /// *Example Usage:*
266 | /// ```typ
267 | /// #let admonition = utils.make-admonition((type, body) => {
268 | /// //..
269 | /// }
270 | /// ```
271 | /// - callback (function): A function that returns the content for the admonition. The function must take `type` and `body` arguments.
272 | /// Valid types include:
273 | /// - `"note"`,
274 | /// - `"example"`,
275 | /// - `"warning"`,
276 | /// - `"quote"`,
277 | /// - `"equation"`,
278 | ///
279 | /// - `"identify"`,
280 | /// - `"brainstorm"`,
281 | /// - `"decide"`,
282 | /// - `"decision"`, // DEPRECATED
283 | /// - `"build"`,
284 | /// - `"program"`,
285 | /// - `"test"`,
286 | /// - `"management"`,
287 | /// - `"notebook"`,
288 | ///
289 | /// -> function
290 | #let make-admonition(callback) = {
291 | let valid-types = (
292 | "note",
293 | "example",
294 | "warning",
295 | "quote",
296 | "equation",
297 |
298 | "identify",
299 | "brainstorm",
300 | "decide",
301 | "decision",
302 | "build",
303 | "program",
304 | "test",
305 | "management",
306 | "notebook",
307 | )
308 |
309 | let valid-types-printable = valid-types.fold(
310 | "",
311 | (
312 | base,
313 | value,
314 | ) => {
315 | base + " '" + value + "'"
316 | },
317 | )
318 |
319 | return (type: none, body) => {
320 | if not valid-types.contains(type) {
321 | panic("Entry type '" + str(type) + "' is not valid. Valid types include:" + valid-types-printable)
322 | }
323 |
324 | callback(
325 | type,
326 | body,
327 | )
328 | }
329 | }
330 |
331 | /// A constructor for a pie chart component
332 | ///
333 | /// *Example Usage:*
334 | ///
335 | /// ```typ
336 | /// #let pie-chart = utils.make-pie-chart(data => {
337 | /// // ...
338 | /// })
339 | /// ```
340 | /// - callback (function): A function that returns the content for the pie chart. The function must take a `data` argument.
341 | /// -> function
342 | #let make-pie-chart(callback) = {
343 | return (..data) => {
344 | callback(data)
345 | }
346 | }
347 |
348 | /// A constructor for a plot component
349 | ///
350 | /// *Example Usage:*
351 | /// ```typ
352 | /// #let plot = utils.make-plot((title, x-label, y-label, length, data) => {
353 | /// // ...
354 | /// })
355 | /// ```
356 | ///
357 | /// - callback (function): A function that returns the content for the plot.
358 | /// The function must take `title`, `x-label`, `y-label`, `length`, and `data` arguments.
359 | /// -> function
360 | #let make-plot(callback) = {
361 | return (title: "", x-label: "", y-label: "", length: auto, ..data) => {
362 | callback(title, x-label, y-label, length, data)
363 | }
364 | }
365 |
366 | // TODO: add method for these extra components:
367 | // - gantt chart
368 | // - tournament
369 | // - team
370 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Contributing to the Notebookinator
4 |
5 | First off, thanks for taking the time to contribute! ❤️
6 |
7 | All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
8 |
9 | > And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
10 | >
11 | > - Star the project
12 | > - Refer this project in your project's readme
13 | > - Mention the project to other competitors
14 |
15 |
16 |
17 | ## Table of Contents
18 |
19 | - [I Have a Question](#i-have-a-question)
20 | - [I Want To Contribute](#i-want-to-contribute)
21 | - [Reporting Bugs](#reporting-bugs)
22 | - [Suggesting Enhancements](#suggesting-enhancements)
23 | - [Your First Code Contribution](#your-first-code-contribution)
24 | - [Improving The Documentation](#improving-the-documentation)
25 | - [Style Guides](#style-guides)
26 | - [Typst Code](#typst-code)
27 | - [Commit Messages](#commit-messages)
28 | - [Branches](#branches)
29 |
30 | ## I Have a Question
31 |
32 | > If you want to ask a question, we assume that you have read the available [Documentation](https://the-notebookinator.github.io/notebookinator/).
33 |
34 | Before you ask a question, it is best to search for existing [Issues](https://github.com/BattleCh1cken/notebookinator.git/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
35 |
36 | If you then still feel the need to ask a question and need clarification, we recommend the following:
37 |
38 | - Open an [Issue](https://github.com/BattleCh1cken/notebookinator.git/issues/new).
39 | - Provide as much context as you can about what you're running into.
40 | - Provide project and platform versions (typst, other packages, etc), depending on what seems relevant.
41 |
42 | We will then take care of the issue as soon as possible.
43 |
44 | ## I Want To Contribute
45 |
46 | > ### Legal Notice
47 | >
48 | > When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project licence.
49 |
50 | ### Reporting Bugs
51 |
52 |
53 |
54 | #### Before Submitting a Bug Report
55 |
56 | A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
57 |
58 | - Make sure that you are using the latest version.
59 | - Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](../docs.pdf). If you are looking for support, you might want to check [this section](#i-have-a-question)).
60 | - To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/BattleCh1cken/notebookinator.git/issues?q=label%3Abug).
61 | - Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
62 | - Collect information about the bug:
63 | - Stack trace (Traceback)
64 | - OS, Platform and Version (Windows, Linux, MacOS, x86, ARM)
65 | - Typst version
66 | - Notebookinator version
67 | - Versions of any other relevant libraries
68 | - Relevant code snippets
69 | - Can you reliably reproduce the issue? And can you also reproduce it with older versions?
70 |
71 |
72 |
73 | #### How Do I Submit a Good Bug Report?
74 |
75 | > You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
76 |
77 | We use GitHub issues to track bugs and errors. If you run into an issue with the project:
78 |
79 | - Open an [Issue](https://github.com/BattleCh1cken/notebookinator.git/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
80 | - Explain the behaviour you would expect and the actual behaviour.
81 | - Please provide as much context as possible and describe the _reproduction steps_ that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
82 | - Provide the information you collected in the previous section.
83 |
84 | Once it's filed:
85 |
86 | - The project team will label the issue accordingly.
87 | - A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs reproduction`. Bugs with the `needs reproduction` tag will not be addressed until they are reproduced.
88 | - If the team is able to reproduce the issue, it will be marked `bug`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
89 |
90 |
91 |
92 | ### Suggesting Enhancements
93 |
94 | This section guides you through submitting an enhancement suggestion for the Notebookinator, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
95 |
96 |
97 |
98 | #### Before Submitting an Enhancement
99 |
100 | - Make sure that you are using the latest version.
101 | - Read the [documentation](../docs.pdf) carefully and find out if the functionality is already covered, maybe by an individual configuration.
102 | - Perform a [search](https://github.com/BattleCh1cken/notebookinator.git/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
103 | - Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
104 |
105 |
106 |
107 | #### How Do I Submit a Good Enhancement Suggestion?
108 |
109 | Enhancement suggestions are tracked as [GitHub issues](https://github.com/BattleCh1cken/notebookinator.git/issues).
110 |
111 | - Use a **clear and descriptive title** for the issue to identify the suggestion.
112 | - Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
113 | - **Describe the current behaviour** and **explain which behaviour you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
114 | - You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on MacOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
115 | - **Explain why this enhancement would be useful** to most of the Notebookinator's users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
116 |
117 |
118 |
119 | ### Your First Code Contribution
120 |
121 | > Using a development environment different from the one recommended below is fine, but you will have to adjust the steps yourself.
122 |
123 | We recommend using [Visual Studio Code](https://code.visualstudio.com/) as your IDE. It is free, open-source, and cross-platform.
124 |
125 | We also recommend the following extensions:
126 |
127 | - [Typst LSP](https://marketplace.visualstudio.com/items?itemName=nvarner.typst-lsp)
128 | - [Typst Preview](https://marketplace.visualstudio.com/items?itemName=mgt19937.typst-preview)
129 | - [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) to check for spelling mistakes
130 |
131 | Of course, you'll also need to install [Typst](https://github.com/typst/typst#installation).
132 |
133 | In order to contribute to the Notebookinator, you will need to [fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) the repository and clone it to your local machine.
134 |
135 | Once you've cloned your repository, you can make your changes. When you're ready to test your changes, create a file called `test.typ` in the root of the project, and import `lib.typ`. View the output with Typst Preview, or render it yourself with `typst compile test.typ`
136 |
137 | You can then [commit](#commit-messages) your changes to your fork. Once you are done, you can [create a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) to the master branch. You can use the [Pull Request Template](PULL_REQUEST_TEMPLATE.md) to structure your pull request.
138 |
139 | ### Improving The Documentation
140 |
141 | We use a combination of [mdBook](https://github.com/rust-lang/mdBook) and [Tidy](https://github.com/Mc-Zen/tidy/tree/main) to generate our documentation.
142 |
143 | All of the documentation can be found inside of the [`docs/`](../docs) directory.
144 |
145 | - The guides located in [`docs/src/`](../docs/src)
146 | - The comments directly in the code
147 | - The [README](../README.md)
148 | - The [contributing guide](./CONTRIBUTING.md)(this file)
149 |
150 | You can preview your changes locally with the mdbook cli. The first thing you'll need to do is install all of the dependences.
151 |
152 | ```bash
153 | cargo install --git https://github.com/typst/typst --locked typst-cli
154 | cargo install mdbook
155 | cargo install mdbook-admonish
156 | cargo install --git https://github.com/fenjalien/mdbook-typst-doc.git
157 | ```
158 |
159 | Once everything has installed, render the documentation with these commands:
160 |
161 | ```bash
162 | typst compile docs/src/reference.typ --root ./
163 | cd docs
164 | mdbook serve
165 | ```
166 |
167 | You can then view your notebook by visiting .
168 |
169 | Once you've made your changes, submit your changes as a pull request, as described above.
170 |
171 | ## Style Guides
172 |
173 | ### Typst Code
174 |
175 | Currently we do not have an established coding style. In the future we'll use the [Typstfmt](https://github.com/astrale-sharp/typstfmt) formatter to enforce our style, but it is not robust enough to meet our needs as of now.
176 |
177 | ### Commit Messages
178 |
179 | Commit messages should be short and descriptive. This project follows the [Gitmoji](https://gitmoji.dev/about) commit style.
180 |
181 | Use the unicode version of the emojis wherever possible. This means you should write 🔥 instead of :fire:.
182 |
183 | ### Branches
184 |
185 | Branches must have labels depending on what the branch changes to the project.
186 |
187 | - Fix: improves a pre-existing feature
188 | - Feature: adds a new feature
189 | - Theme: adds a new theme
190 |
191 | A complete branch name would be **theme/your-theme-name** or **feature/theme-cover**.
192 |
193 | ## Attribution
194 |
195 | This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)!
196 |
--------------------------------------------------------------------------------
/themes/radial/Mediamodifier-Design.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Created with Fabric.js 5.2.4
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/docs/src/developer_documentation/custom_themes.md:
--------------------------------------------------------------------------------
1 | # Making Your Own Theme
2 |
3 | If you're unhappy with the existing themes, or just want to add your own flair to your notebook, the Notebookinator supports custom themes. We highly recommend you familiarize yourself with the [Typst Documentation](typst.app/docs) before trying to implement one yourself.
4 |
5 | Themes consist of two different parts, the theme itself, and its corresponding components.
6 |
7 | The theme is just a dictionary containing functions. These functions specify how the entries and cover should look, as well as global rules for the whole document. We'll cover the required structure of this variable in a [later](#the-theme-variable) section.
8 |
9 | Components are simply functions stored in a module. These functions contain things like pro/con tables and decision-matrices. Most components are just standalone, but the Notebookinator does supply some utility functions to help with implementing harder components. Implementing components will be covered in [this](#writing-components) section.
10 |
11 | ## File Structure
12 |
13 | The first thing you'll need to do is create a folder for your theme, somewhere in your notebook. As an example, lets create a theme called `foo`. The first thing we'll want to do is create a folder called `foo/`. Then, inside that folder, we'll want to create a file called `foo.typ` inside the `foo/` folder. This will be the entry point for your theme, and will contain your theme variable.
14 |
15 | Then, we'll want to create a `foo/components/components.typ` file. This file will contain all of your components. We recommend having each component inside of its own file. For example, an `example-component` might be defined in `foo/components/example-component.typ`.
16 |
17 | You'll also want to create an `entries.typ` file to contain all of your entry functions for your theme variable, and a `rules.typ` to store your global rules.
18 |
19 | Your final file structure should look like this:
20 |
21 | - `foo/`
22 | - `foo.typ`
23 | - `entries.typ`
24 | - `rules.typ`
25 | - `components/`
26 | - `components.typ`
27 |
28 | ```admonish info
29 | This is just the recommended file structure, as long as you expose a theme variable and components, your theme will work just like all the others. You can also add any additional files as you wish.
30 | ```
31 |
32 | ## The Theme Variable
33 |
34 | The first thing you should do is create a theme variable. Going back to our `foo` example, lets create a `foo-theme` variable in our `foo/foo.typ` file.
35 |
36 | ```typ
37 | // foo/foo.typ
38 |
39 | // use this if you're developing inside the notebookinator
40 | #import "/utils.typ"
41 |
42 | // use this if you're developing a private theme
43 | #import "@local/notebookinator:1.0.1": *
44 |
45 | #let foo-theme = utils.make-theme() // will not currently compile
46 | ```
47 |
48 | Themes are defined with the `make-theme` function found in the `utils` namespace. This function verifies that all of your inputs are correct, and will return a correctly structured theme. However, it requires that all of your theme functions are specified in order to compile, so that's the next thing we'll be doing.
49 |
50 | ### Creating The Entries
51 |
52 | Now that we actually have a place to put our theme functions, we can start implementing our entry functions.
53 |
54 | Each of these functions has 2 requirements:
55 |
56 | - it must return a `page` function as output
57 | - it must take a dictionary parameter named `ctx` as input, and a parameter called body.
58 |
59 | The `ctx` argument provides context about the current entry being created. This dictionary contains the following fields:
60 |
61 | - `title`: `str`
62 | - `type`: `str`
63 | - `date`: `datetime`
64 | - `author`: `str`
65 | - `witness`: `str`
66 |
67 | `body` contains the content the user has written. It should be passed into the `page` function in some shape or form.
68 |
69 | We'll write these functions in the `foo/entries.typ` file. Below are some minimal starting examples:
70 |
71 | ### Frontmatter
72 |
73 | ```typ
74 | // foo/entries.typ
75 |
76 | // to import utils, see The Theme Variable section
77 |
78 | #let frontmatter-entry = utils.make-frontmatter-entry((ctx, body) => {
79 | show: page.with( // pass the entire function scope into the `page` function
80 | header: [ = ctx.title ],
81 | footer: context counter(page).display("i")
82 | )
83 |
84 | body // display the users's written content
85 | })
86 | ```
87 |
88 | ### Body
89 |
90 | ```typ
91 | // foo/entries.typ
92 |
93 | // to import utils, see The Theme Variable section
94 |
95 | #let body-entry = utils.make-body-entry((ctx, body) => {
96 | show: page.with( // pass the entire function scope into the `page` function
97 | header: [ = ctx.title ],
98 | footer: context counter(page).display("i")
99 | )
100 |
101 | body // display the users's written content
102 | })
103 | ```
104 |
105 | ### Appendix
106 |
107 | ```typ
108 | // foo/entries.typ
109 |
110 | // to import utils, see The Theme Variable section
111 |
112 | #let appendix-entry = utils.make-appendix-entry((ctx, body) => {
113 | show: page.with( // pass the entire function scope into the `page` function
114 | header: [ = ctx.title ],
115 | footer: context counter(page).display("i")
116 | )
117 |
118 | body // display the users's written content
119 | })
120 |
121 | ```
122 |
123 | With the entry functions written, we can now add them to the theme variable.
124 |
125 | ```typ
126 | // foo/foo.typ
127 |
128 | // to import utils, see The Theme Variable section
129 |
130 | // import the entry functions
131 | #import "./entries.typ": frontmatter-entry, body-entry, appendix-entry
132 |
133 | // add the entry functions to the theme
134 | #let foo-theme = utils.make-theme(
135 | frontmatter-entry: frontmatter-entry,
136 | body-entry: body-entry,
137 | appendix-entry: appendix-entry,
138 | )
139 | ```
140 |
141 | ### Creating a Cover
142 |
143 | Then you'll have to implement a cover. The only required parameter here is a
144 | context variable, which stores information like team number, game season and
145 | year.
146 |
147 | Here's an example cover:
148 |
149 | ```typ
150 | // foo/entries.typ
151 |
152 | // to import utils, see The Theme Variable section
153 |
154 | #let cover = utils.make-cover(ctx => [
155 | #set align(center + horizon)
156 | *Default Cover*
157 | ])
158 |
159 | ```
160 |
161 | Then, we'll update the theme variable accordingly:
162 |
163 | ```typ
164 | // foo/foo.typ
165 |
166 | // to import utils, see The Theme Variable section
167 |
168 | // import the cover along with the entry functions
169 | #import "./entries.typ": cover, frontmatter-entry, body-entry, appendix-entry
170 |
171 | #let foo-theme = utils.make-theme(
172 | cover: cover, // add the cover to the theme
173 | frontmatter-entry: frontmatter-entry,
174 | body-entry: body-entry,
175 | appendix-entry: appendix-entry,
176 | )
177 | ```
178 |
179 | ### Rules
180 |
181 | Next you'll have to define the rules. This function defines all of the global
182 | configuration and styling for your entire theme. This function must take a doc
183 | parameter, and then return that parameter. The entire document will be passed
184 | into this function, and then returned. Here's and example of what this could
185 | look like:
186 |
187 | ```typ
188 | // foo/rules.typ
189 |
190 | // to import utils, see The Theme Variable section
191 |
192 | #let rules = utils.make-rules((doc) => {
193 | set text(fill: red) // Make all of the text red, across the entire document
194 |
195 | doc // Return the entire document
196 | })
197 | ```
198 |
199 | Then, we'll update the theme variable accordingly:
200 |
201 | ```typ
202 | // foo/foo.typ
203 | #import "./rules.typ": rules // import the rules
204 | #import "./entries.typ": cover, frontmatter-entry, body-entry, appendix-entry
205 |
206 | // to import utils, see The Theme Variable section
207 |
208 | #let foo-theme = utils.make-theme(
209 | rules: rules, // store the rules in the theme variable
210 | cover: cover,
211 | frontmatter-entry: frontmatter-entry,
212 | body-entry: body-entry,
213 | appendix-entry: appendix-entry,
214 | )
215 | ```
216 |
217 | ## Writing Components
218 |
219 | With your base theme done, you may want to create some additional components for you to use while writing your entries. This could be anything, including graphs, tables, Gantt charts, or anything else your heart desires. We recommend including the following components by default:
220 |
221 | - Table of contents `toc`
222 | - Decision matrix: `decision-matrix`
223 | - Pros and cons table: `pro-con`
224 | - Glossary: `glossary`
225 |
226 | We recommend creating a file for each of these components. After doing so, your file structure should look like this:
227 |
228 | - `foo/components/`
229 | - `components.typ`
230 | - `toc.typ`
231 | - `decision-matrix.typ`
232 | - `pro-con.typ`
233 | - `glossary.typ`
234 |
235 | Once you make those files, import them all in your `components.typ` file:
236 |
237 | ```typ
238 | // foo/components.typ
239 |
240 | // make sure to glob import every file
241 | #import "./toc.typ": *
242 | #import "./glossary.typ": *
243 | #import "./pro-con.typ": *
244 | #import "./decision-matrix.typ": *
245 | ```
246 |
247 | Then, import your `components.typ` into your theme's entry point:
248 |
249 | ```typ
250 | // foo/foo.typ
251 |
252 | #import "./components/components.typ" // make sure not to glob import here
253 | ```
254 |
255 | All components are defined with constructors from the `utils` module.
256 |
257 | ### Pro / Con Component
258 |
259 | Pro / con components tend to be extremely simple. Define a function called `pro-con` inside your `foo/components/pro-con.typ` file with the `make-pro-con` from `utils`:
260 |
261 | ```typ
262 | // foo/components/pro-con.typ
263 |
264 | // to import utils, see The Theme Variable section
265 |
266 | #let pro-con = utils.make-pro-con((pros, cons) => {
267 | // return content here
268 | })
269 | ```
270 |
271 | This syntax might look a little weird if you aren't familiar with functional programming. `make-pro-con` takes a [`lambda`](https://typst.app/docs/reference/foundations/function/#unnamed) function as input, which is just a function without a name. This function takes two inputs: `pros` and `cons`, which are available inside the scope of the function like normal arguments would be on a named function.
272 |
273 | For examples on how to create a pro / con table, check out how [other themes](https://github.com/The-Notebookinator/notebookinator/tree/main/themes) implement them.
274 |
275 | ### TOC Component
276 |
277 | The next three components are a bit more complicated, so we'll be spending a little more time explaining how they work. Each of these components requires some information about the document itself (things like the page numbers of entries, etc.). Normally fetching this data can be rather annoying, but fortunately the Notebookinator's constructors fetch this data for you.
278 |
279 | To get started with your table of contents, first define a function called `toc` in your `foo/components/toc.typ` file with the `make-toc` constructor.
280 |
281 | Using the `make-toc` constructor we can make a `toc` like this:
282 |
283 | ```typ
284 | // foo/components/toc.typ
285 |
286 | // to import utils, see The Theme Variable section
287 |
288 | #let toc = utils.make-toc((frontmatter, body, appendix) => {
289 | // ...
290 | })
291 | ```
292 |
293 | Using the constructor gives us access to three variables, `frontmatter`, `body`, and `appendix`. These variables are all [arrays](https://typst.app/docs/reference/foundations/array/), which are dictionaries that all contain the same information as the `ctx` variables from [this](#creating-the-entries) section, with the addition of a `page-number` field, which is an [integer](https://typst.app/docs/reference/foundations/int/).
294 |
295 | With these variables, we can simply loop over each one, and print out another entry in the table of contents each time.
296 |
297 | Here's what that looks like for the frontmatter entries:
298 |
299 | ```typ
300 | // foo/components/toc.typ
301 |
302 | // to import utils, see The Theme Variable section
303 |
304 | #let toc = utils.make-toc((_, body, appendix) => {
305 | // We replace the 'frontmatter' parameter with _ to indicate that we will not use it.
306 | // _ replaces frontmatter to indicate we aren't using it
307 | heading[Contents]
308 | stack(
309 | spacing: 0.5em,
310 | ..for entry in body {
311 | (
312 | [
313 | #entry.title
314 | #box(
315 | width: 1fr,
316 | line(
317 | length: 100%,
318 | stroke: (dash: "dotted"),
319 | ),
320 | )
321 | #entry.page-number
322 | ],
323 | )
324 | },
325 | )
326 |
327 | // You'll need to do something similar for the
328 | // appendix entries as well, if you want to display them
329 | })
330 | ```
331 |
332 | ### Decision Matrix Component
333 |
334 | The decision matrix code works similarly. You can define one like this:
335 |
336 | ```typ
337 | #let decision-matrix = utils.make-decision-matrix((properties, data) => {
338 | // ...
339 | })
340 | ```
341 |
342 | Inside of the function you have access to two variables, `properties`, and `data`. Properties contains a list of the properties the choices are being rated by, while `data` contains the choices alongside the scores for those choices. You can run a [`repr()`](https://typst.app/docs/reference/foundations/repr/) on either of those variables to get a better understanding of how they're structured.
343 |
344 | Here's a simple example to get you started, copied from the `default-theme`:
345 |
346 | ```typ
347 | // to import utils, see The Theme Variable section
348 |
349 | #let decision-matrix = utils.make-decision-matrix((properties, data) => {
350 | table(
351 | columns: for _ in range(properties.len() + 2) {
352 | (1fr,)
353 | },
354 | [],
355 | ..for property in properties {
356 | ([ *#property.name* ],)
357 | },
358 | [*Total*],
359 | ..for (index, choice) in data {
360 | let cell = if choice.total.highest {
361 | table.cell.with(fill: green)
362 | } else {
363 | table.cell
364 | }
365 | (
366 | cell[*#index*],
367 | ..for value in choice.values() {
368 | (cell[#value.weighted],)
369 | },
370 | )
371 | },
372 | )
373 | })
374 | ```
375 |
376 | ### Glossary Component
377 |
378 | The glossary component is similar to the table of components in that it requires information about the document to function.
379 |
380 | To get access to the glossary entries, you can use the `make-glossary` function provided by the `utils` to fetch all of the glossary terms, in alphabetical order.
381 |
382 | The function passed into the `print-glossary` function has access to the `glossary` variable, which is an [array](https://typst.app/docs/reference/foundations/array/). Each entry in the array is a dictionary containing a `word` field and a `definition` field. Both are [strings](https://typst.app/docs/reference/foundations/str/).
383 |
384 | Here's an example from the `default-theme`:
385 |
386 | ```typ
387 | // foo/components/glossary.typ
388 |
389 | // to import utils, see The Theme Variable section
390 |
391 | #let glossary() = utils.print-glossary(glossary => {
392 | stack(spacing: 0.5em, ..for entry in glossary {
393 | ([
394 | = #entry.word
395 |
396 | #entry.definition
397 | ],)
398 | })
399 | })
400 | ```
401 |
402 | ## Using the Theme
403 |
404 | Now that you've written the theme, you can apply it to your notebook.
405 |
406 | In the entry point of your notebook (most likely `main.typ`), import your theme like this:
407 |
408 | ```typ
409 | // main.typ
410 |
411 | #import "foo/foo.typ": foo-theme, components
412 | ```
413 |
414 | Once you do that, change the theme to `foo-theme`:
415 |
416 | ```
417 | // main.typ
418 |
419 | #show: notebook.with(
420 | theme: foo-theme
421 | )
422 | ```
423 |
--------------------------------------------------------------------------------
/docs/mdbook-admonish.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | :is(.admonition) {
3 | display: flow-root;
4 | margin: 1.5625em 0;
5 | padding: 0 1.2rem;
6 | color: var(--fg);
7 | page-break-inside: avoid;
8 | background-color: var(--bg);
9 | border: 0 solid black;
10 | border-inline-start-width: 0.4rem;
11 | border-radius: 0.2rem;
12 | box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1);
13 | }
14 | @media print {
15 | :is(.admonition) {
16 | box-shadow: none;
17 | }
18 | }
19 | :is(.admonition) > * {
20 | box-sizing: border-box;
21 | }
22 | :is(.admonition) :is(.admonition) {
23 | margin-top: 1em;
24 | margin-bottom: 1em;
25 | }
26 | :is(.admonition) > .tabbed-set:only-child {
27 | margin-top: 0;
28 | }
29 | html :is(.admonition) > :last-child {
30 | margin-bottom: 1.2rem;
31 | }
32 |
33 | a.admonition-anchor-link {
34 | display: none;
35 | position: absolute;
36 | left: -1.2rem;
37 | padding-right: 1rem;
38 | }
39 | a.admonition-anchor-link:link, a.admonition-anchor-link:visited {
40 | color: var(--fg);
41 | }
42 | a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover {
43 | text-decoration: none;
44 | }
45 | a.admonition-anchor-link::before {
46 | content: "§";
47 | }
48 |
49 | :is(.admonition-title, summary.admonition-title) {
50 | position: relative;
51 | min-height: 4rem;
52 | margin-block: 0;
53 | margin-inline: -1.6rem -1.2rem;
54 | padding-block: 0.8rem;
55 | padding-inline: 4.4rem 1.2rem;
56 | font-weight: 700;
57 | background-color: rgba(68, 138, 255, 0.1);
58 | print-color-adjust: exact;
59 | -webkit-print-color-adjust: exact;
60 | display: flex;
61 | }
62 | :is(.admonition-title, summary.admonition-title) p {
63 | margin: 0;
64 | }
65 | html :is(.admonition-title, summary.admonition-title):last-child {
66 | margin-bottom: 0;
67 | }
68 | :is(.admonition-title, summary.admonition-title)::before {
69 | position: absolute;
70 | top: 0.625em;
71 | inset-inline-start: 1.6rem;
72 | width: 2rem;
73 | height: 2rem;
74 | background-color: #448aff;
75 | print-color-adjust: exact;
76 | -webkit-print-color-adjust: exact;
77 | mask-image: url('data:image/svg+xml;charset=utf-8, ');
78 | -webkit-mask-image: url('data:image/svg+xml;charset=utf-8, ');
79 | mask-repeat: no-repeat;
80 | -webkit-mask-repeat: no-repeat;
81 | mask-size: contain;
82 | -webkit-mask-size: contain;
83 | content: "";
84 | }
85 | :is(.admonition-title, summary.admonition-title):hover a.admonition-anchor-link {
86 | display: initial;
87 | }
88 |
89 | details.admonition > summary.admonition-title::after {
90 | position: absolute;
91 | top: 0.625em;
92 | inset-inline-end: 1.6rem;
93 | height: 2rem;
94 | width: 2rem;
95 | background-color: currentcolor;
96 | mask-image: var(--md-details-icon);
97 | -webkit-mask-image: var(--md-details-icon);
98 | mask-repeat: no-repeat;
99 | -webkit-mask-repeat: no-repeat;
100 | mask-size: contain;
101 | -webkit-mask-size: contain;
102 | content: "";
103 | transform: rotate(0deg);
104 | transition: transform 0.25s;
105 | }
106 | details[open].admonition > summary.admonition-title::after {
107 | transform: rotate(90deg);
108 | }
109 |
110 | :root {
111 | --md-details-icon: url("data:image/svg+xml;charset=utf-8, ");
112 | }
113 |
114 | :root {
115 | --md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8, ");
116 | --md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8, ");
117 | --md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8, ");
118 | --md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8, ");
119 | --md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8, ");
120 | --md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8, ");
121 | --md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8, ");
122 | --md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8, ");
123 | --md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8, ");
124 | --md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8, ");
125 | --md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8, ");
126 | --md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8, ");
127 | }
128 |
129 | :is(.admonition):is(.admonish-note) {
130 | border-color: #448aff;
131 | }
132 |
133 | :is(.admonish-note) > :is(.admonition-title, summary.admonition-title) {
134 | background-color: rgba(68, 138, 255, 0.1);
135 | }
136 | :is(.admonish-note) > :is(.admonition-title, summary.admonition-title)::before {
137 | background-color: #448aff;
138 | mask-image: var(--md-admonition-icon--admonish-note);
139 | -webkit-mask-image: var(--md-admonition-icon--admonish-note);
140 | mask-repeat: no-repeat;
141 | -webkit-mask-repeat: no-repeat;
142 | mask-size: contain;
143 | -webkit-mask-repeat: no-repeat;
144 | }
145 |
146 | :is(.admonition):is(.admonish-abstract, .admonish-summary, .admonish-tldr) {
147 | border-color: #00b0ff;
148 | }
149 |
150 | :is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title) {
151 | background-color: rgba(0, 176, 255, 0.1);
152 | }
153 | :is(.admonish-abstract, .admonish-summary, .admonish-tldr) > :is(.admonition-title, summary.admonition-title)::before {
154 | background-color: #00b0ff;
155 | mask-image: var(--md-admonition-icon--admonish-abstract);
156 | -webkit-mask-image: var(--md-admonition-icon--admonish-abstract);
157 | mask-repeat: no-repeat;
158 | -webkit-mask-repeat: no-repeat;
159 | mask-size: contain;
160 | -webkit-mask-repeat: no-repeat;
161 | }
162 |
163 | :is(.admonition):is(.admonish-info, .admonish-todo) {
164 | border-color: #00b8d4;
165 | }
166 |
167 | :is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title) {
168 | background-color: rgba(0, 184, 212, 0.1);
169 | }
170 | :is(.admonish-info, .admonish-todo) > :is(.admonition-title, summary.admonition-title)::before {
171 | background-color: #00b8d4;
172 | mask-image: var(--md-admonition-icon--admonish-info);
173 | -webkit-mask-image: var(--md-admonition-icon--admonish-info);
174 | mask-repeat: no-repeat;
175 | -webkit-mask-repeat: no-repeat;
176 | mask-size: contain;
177 | -webkit-mask-repeat: no-repeat;
178 | }
179 |
180 | :is(.admonition):is(.admonish-tip, .admonish-hint, .admonish-important) {
181 | border-color: #00bfa5;
182 | }
183 |
184 | :is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title) {
185 | background-color: rgba(0, 191, 165, 0.1);
186 | }
187 | :is(.admonish-tip, .admonish-hint, .admonish-important) > :is(.admonition-title, summary.admonition-title)::before {
188 | background-color: #00bfa5;
189 | mask-image: var(--md-admonition-icon--admonish-tip);
190 | -webkit-mask-image: var(--md-admonition-icon--admonish-tip);
191 | mask-repeat: no-repeat;
192 | -webkit-mask-repeat: no-repeat;
193 | mask-size: contain;
194 | -webkit-mask-repeat: no-repeat;
195 | }
196 |
197 | :is(.admonition):is(.admonish-success, .admonish-check, .admonish-done) {
198 | border-color: #00c853;
199 | }
200 |
201 | :is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title) {
202 | background-color: rgba(0, 200, 83, 0.1);
203 | }
204 | :is(.admonish-success, .admonish-check, .admonish-done) > :is(.admonition-title, summary.admonition-title)::before {
205 | background-color: #00c853;
206 | mask-image: var(--md-admonition-icon--admonish-success);
207 | -webkit-mask-image: var(--md-admonition-icon--admonish-success);
208 | mask-repeat: no-repeat;
209 | -webkit-mask-repeat: no-repeat;
210 | mask-size: contain;
211 | -webkit-mask-repeat: no-repeat;
212 | }
213 |
214 | :is(.admonition):is(.admonish-question, .admonish-help, .admonish-faq) {
215 | border-color: #64dd17;
216 | }
217 |
218 | :is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title) {
219 | background-color: rgba(100, 221, 23, 0.1);
220 | }
221 | :is(.admonish-question, .admonish-help, .admonish-faq) > :is(.admonition-title, summary.admonition-title)::before {
222 | background-color: #64dd17;
223 | mask-image: var(--md-admonition-icon--admonish-question);
224 | -webkit-mask-image: var(--md-admonition-icon--admonish-question);
225 | mask-repeat: no-repeat;
226 | -webkit-mask-repeat: no-repeat;
227 | mask-size: contain;
228 | -webkit-mask-repeat: no-repeat;
229 | }
230 |
231 | :is(.admonition):is(.admonish-warning, .admonish-caution, .admonish-attention) {
232 | border-color: #ff9100;
233 | }
234 |
235 | :is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title) {
236 | background-color: rgba(255, 145, 0, 0.1);
237 | }
238 | :is(.admonish-warning, .admonish-caution, .admonish-attention) > :is(.admonition-title, summary.admonition-title)::before {
239 | background-color: #ff9100;
240 | mask-image: var(--md-admonition-icon--admonish-warning);
241 | -webkit-mask-image: var(--md-admonition-icon--admonish-warning);
242 | mask-repeat: no-repeat;
243 | -webkit-mask-repeat: no-repeat;
244 | mask-size: contain;
245 | -webkit-mask-repeat: no-repeat;
246 | }
247 |
248 | :is(.admonition):is(.admonish-failure, .admonish-fail, .admonish-missing) {
249 | border-color: #ff5252;
250 | }
251 |
252 | :is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title) {
253 | background-color: rgba(255, 82, 82, 0.1);
254 | }
255 | :is(.admonish-failure, .admonish-fail, .admonish-missing) > :is(.admonition-title, summary.admonition-title)::before {
256 | background-color: #ff5252;
257 | mask-image: var(--md-admonition-icon--admonish-failure);
258 | -webkit-mask-image: var(--md-admonition-icon--admonish-failure);
259 | mask-repeat: no-repeat;
260 | -webkit-mask-repeat: no-repeat;
261 | mask-size: contain;
262 | -webkit-mask-repeat: no-repeat;
263 | }
264 |
265 | :is(.admonition):is(.admonish-danger, .admonish-error) {
266 | border-color: #ff1744;
267 | }
268 |
269 | :is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title) {
270 | background-color: rgba(255, 23, 68, 0.1);
271 | }
272 | :is(.admonish-danger, .admonish-error) > :is(.admonition-title, summary.admonition-title)::before {
273 | background-color: #ff1744;
274 | mask-image: var(--md-admonition-icon--admonish-danger);
275 | -webkit-mask-image: var(--md-admonition-icon--admonish-danger);
276 | mask-repeat: no-repeat;
277 | -webkit-mask-repeat: no-repeat;
278 | mask-size: contain;
279 | -webkit-mask-repeat: no-repeat;
280 | }
281 |
282 | :is(.admonition):is(.admonish-bug) {
283 | border-color: #f50057;
284 | }
285 |
286 | :is(.admonish-bug) > :is(.admonition-title, summary.admonition-title) {
287 | background-color: rgba(245, 0, 87, 0.1);
288 | }
289 | :is(.admonish-bug) > :is(.admonition-title, summary.admonition-title)::before {
290 | background-color: #f50057;
291 | mask-image: var(--md-admonition-icon--admonish-bug);
292 | -webkit-mask-image: var(--md-admonition-icon--admonish-bug);
293 | mask-repeat: no-repeat;
294 | -webkit-mask-repeat: no-repeat;
295 | mask-size: contain;
296 | -webkit-mask-repeat: no-repeat;
297 | }
298 |
299 | :is(.admonition):is(.admonish-example) {
300 | border-color: #7c4dff;
301 | }
302 |
303 | :is(.admonish-example) > :is(.admonition-title, summary.admonition-title) {
304 | background-color: rgba(124, 77, 255, 0.1);
305 | }
306 | :is(.admonish-example) > :is(.admonition-title, summary.admonition-title)::before {
307 | background-color: #7c4dff;
308 | mask-image: var(--md-admonition-icon--admonish-example);
309 | -webkit-mask-image: var(--md-admonition-icon--admonish-example);
310 | mask-repeat: no-repeat;
311 | -webkit-mask-repeat: no-repeat;
312 | mask-size: contain;
313 | -webkit-mask-repeat: no-repeat;
314 | }
315 |
316 | :is(.admonition):is(.admonish-quote, .admonish-cite) {
317 | border-color: #9e9e9e;
318 | }
319 |
320 | :is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title) {
321 | background-color: rgba(158, 158, 158, 0.1);
322 | }
323 | :is(.admonish-quote, .admonish-cite) > :is(.admonition-title, summary.admonition-title)::before {
324 | background-color: #9e9e9e;
325 | mask-image: var(--md-admonition-icon--admonish-quote);
326 | -webkit-mask-image: var(--md-admonition-icon--admonish-quote);
327 | mask-repeat: no-repeat;
328 | -webkit-mask-repeat: no-repeat;
329 | mask-size: contain;
330 | -webkit-mask-repeat: no-repeat;
331 | }
332 |
333 | .navy :is(.admonition) {
334 | background-color: var(--sidebar-bg);
335 | }
336 |
337 | .ayu :is(.admonition),
338 | .coal :is(.admonition) {
339 | background-color: var(--theme-hover);
340 | }
341 |
342 | .rust :is(.admonition) {
343 | background-color: var(--sidebar-bg);
344 | color: var(--sidebar-fg);
345 | }
346 | .rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited {
347 | color: var(--sidebar-fg);
348 | }
349 |
--------------------------------------------------------------------------------