Introduction
Welcome to the documentation for the notebookinator, a Typst package meant to simplify the notebooking process for the Vex Robotics Competition. Its theming capabilities handle all of the styling for you, letting you jump right into writing documentation.
While it was designed with VRC in mind, it could just as easily be used for other competitor systems such as the First Robotics Competition and the First Tech Challenge.
If you're new here, we recommend you read the installation guide and the basic usage guide.
If you already know what you're doing, and just some quick information, check out the API reference.
If you're an advanced user, and want to extend / contribute to the Notebookinator, check out our developer documentation.
Installation
This installation process is temporary, as we wait for Typst to overhaul its process for packaging templates and packages.
Devcontainer / Codespaces
The easiest way to use the Notebookinator is to use our devcontainer/codespace. This will automatically install all of the needed dependencies inside of a Docker container. We recommend using our quick start template for the best experience.
While this approach is easier, you may encounter some performance issues as your notebook increases in size.
The only thing you'll need in preparation is a GitHub account (only if using Codespaces) and VSCode installed. Once you do that, just follow the instructions in the README.
Local Installation
This installation process is a little harder, and requires more software to be manually installed on your computer.
Make sure you have the following software installed:
Once you've installed everything, run the following commands:
If you're running this on Windows, you'll need to run these commands in a sh shell, like git-bash or the shell packaged with Cygwin or GitHub Desktop.
git clone --depth 1 --branch 1.0.1 https://github.com/The-Notebookinator/notebookinator
./notebookinator/scripts/package @local
rm -rf notebookinator
Once you do that you should be good to go!
Basic Usage
Now that you have the Notebookinator installed, you can start notebooking.
Setup
You can use our 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.
git clone https://github.com/The-Notebookinator/quick-start-template.git
# alternatively if you made your own repository you can clone it like this:
git clone <your-url-here>
Once you've done that, open the newly downloaded folder inside of VSCode or your editor of choice.
Editing Your Notebook
Adding New Entries
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.
Frontmatter
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.
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:
#create-frontmatter-entry(title: "About")[
Here's some info about this amazing notebook!
]
Frontmatter entries are rendered in the order they are created.
Body
The most common type of entry is the body entry. These entries store all of your notebook's main content.
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:
#include "./my-entry.typ"
Body entries will be displayed in the order they are included in the entries/entries.typ
file.
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.
You can create a new body entry like so:
// not all themes require every one of these options
#show: create-body-entry.with(
title: "My Awesome Entry",
type: "identify", // The type of the entry depends on which theme you're using
date: datetime(year: 2024, month: 1, day: 1),
)
Appendix
Appendix entries go at the end of the notebook, and are stored in the appendix.typ
file.
You can create a new appendix entry like this:
#create-appendix-entry(title: "Programming")[
Here's information about how we programmed the robot.
#lorem(500)
]
Changing the Theme
In order to change the theme you'll need to edit two files, packages.typ
and main.typ
.
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:
// packages.typ
// this file allows us to only specify package versions once
#import "@local/notebookinator:1.0.1": *
#import themes.linear: linear-theme, components // components is imported here so we don't have to specify which theme's components we're using.
Once you do that, you'll want to edit your main.typ
to use the linear-theme
instead of the radial-theme
.
// main.typ
#show: notebook.with(
// ...
theme: linear-theme,
)
Not all themes implement the same components, so you may encounter some issues when changing themes with a more developed notebook.
Using Components
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.
packages.typ
should already export this module, so you can access it just by import
ing packages.typ
#import "/packages.typ": *
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.
#components.pro-con(
pros: [
Here are the pros.
],
cons: [
Here are the cons.
]
)
You can see what components a theme implements by reading the API reference.
Compiling / Viewing Your Notebook
Once you're happy with your notebook, you'll want to render it into a PDF.
You can do that with either of the following commands:
typst compile main.typ
# or if you want live updates
typst watch main.typ
You can then open main.pdf
in any PDF viewer to see your rendered output.
API Reference
Check out the API reference here.
Developer Documentation
Welcome to the Notebookinator's developer documentation. This section is for advanced users who want to contribute to the Notebookinator's existing codebase, or want to use the existing tools at a more advanced level.
The section covers:
- project architecture
- creating your own theme
- contributing themes to the Notebookinator
Project Architecture
The Notebookinator is split into two sections, the base template, and the themes. The base template functions as the backend of the project. It handles all of the information processing, keeps track of global state, makes sure page numbers are correct, and so on. It exposes the main API that the user interacts for creating entries and creating glossary entries.
The themes act as the frontend to the project, and are what the user actually sees. The themes expose an API for components that need to be called directly inside of entries. This could include things like admonitions, charts, and decision matrices.
File Structure
lib.typ
: The entrypoint for the whole template.internals.typ
: All of the internal function calls that should not be used by theme authors or users.entries.typ
: Contains the user facing API for entries, as well as the internal template functions for printing out the entries and cover.glossary.typ
: Contains the user facing API for the glossary.globals.typ
: Contains all of the global variables for the entire project.utils.typ
: Utility functions intended to help implement themes.themes/
: The folder containing all of the themes.themes.typ
: An index of all the themes are contained in the template
docs.typ
: The entry point for the project documentation.docs-template.typ
: The template for the project documentation.
Making Your Own Theme
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 before trying to implement one yourself.
Themes consist of two different parts, the theme itself, and its corresponding components.
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 section.
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 section.
File Structure
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.
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
.
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.
Your final file structure should look like this:
foo/
foo.typ
entries.typ
rules.typ
components/
components.typ
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.
The Theme Variable
Now that you've created your files, you can begin writing your theme. 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.
// foo/foo.typ
#let foo-theme = (:) // currently a blank dictionary
Currently our theme is blank, and will do nothing. If we try to apply it right now, all functions will fall back onto the default-theme
.
Creating The Entries
Now that we actually have a place to put our theme functions, we can start implementing our entry functions.
Each of these functions has 2 requirements:
- it must return a
page
function as output - it must take a dictionary parameter named
ctx
as input, and a parameter called body.
The ctx
argument provides context about the current entry being created. This dictionary contains the following fields:
title
:str
type
:str
date
:datetime
author
:str
witness
:str
body
contains the content the user has written. It should be passed into the page
function in some shape or form.
We'll write these functions in the foo/entries.typ
file. Below are some minimal starting examples:
Frontmatter
// foo/entries.typ
#let frontmatter-entry(ctx: (:), body) = {
show: page.with( // pass the entire function scope into the `page` function
header: [ = ctx.title ],
footer: context counter(page).display("i")
)
body // display the users's written content
}
Body
// foo/entries.typ
#let body-entry(ctx: (:), body) = {
show: page.with(
header: [ = Body header ],
footer: counter(page).display("1")
)
body
}
Appendix
// foo/entries.typ
#let appendix-entry(ctx: (:), body) = {
show: page.with(
header: [ = Appendix header ],
footer: counter(page).display("i")
)
body
}
With the entry functions written, we can now add them to the theme variable.
// foo/foo.typ
// import the entry functions
#import "./entries.typ": frontmatter-entry, body-entry, appendix-entry
// store the entry functions in the theme variable
#let foo-theme = (
frontmatter-entry: frontmatter-entry,
body-entry: body-entry,
appendix-entry: appendix-entry,
)
Creating a Cover
Then you'll have to implement a cover. The only required parameter here is a context variable, which stores information like team number, game season and year.
Here's an example cover:
// foo/entries.typ
#let cover(ctx: (:)) = [
#set align(center)
*Foo Cover*
]
Then, we'll update the theme variable accordingly:
// foo/foo.typ
// import the cover along with the entry functions
#import "./entries.typ": cover frontmatter-entry, body-entry, appendix-entry
#let foo-theme = (
cover: cover, // store the cover in the theme variable
frontmatter-entry: frontmatter-entry,
body-entry: body-entry,
appendix-entry: appendix-entry,
)
Rules
Next you'll have to define the rules. This function defines all of the global configuration and styling for your entire theme. This function must take a doc parameter, and then return that parameter. The entire document will be passed into this function, and then returned. Here's and example of what this could look like:
// foo/rules.typ
#let rules(doc) = {
set text(fill: red) // Make all of the text red, across the entire document
doc // Return the entire document
}
Then, we'll update the theme variable accordingly:
// foo/foo.typ
#import "./rules.typ": rules // import the rules
#import "./entries.typ": cover frontmatter-entry, body-entry, appendix-entry
#let foo-theme = (
rules: rules, // store the rules in the theme variable
cover: cover,
frontmatter-entry: frontmatter-entry,
body-entry: body-entry,
appendix-entry: appendix-entry,
)
Writing Components
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:
- Table of contents
toc()
- Decision matrix:
decision-matrix()
- Pros and cons table:
pro-con()
- Glossary:
glossary()
We recommend creating a file for each of these components. After doing so, your file structure should look like this:
foo/components/
components.typ
toc.typ
decision-matrix.typ
pro-con.typ
glossary.typ
Once you make those files, import them all in your components.typ
file:
// foo/components.typ
// make sure to glob import every file
#import "./toc.typ": *
#import "./glossary.typ": *
#import "./pro-con.typ": *
#import "./decision-matrix.typ": *
Then, import your components.typ
into your theme's entry point:
// foo/foo.typ
#import "./components/components.typ" // make sure not to glob import here
Pro / Con Component
Pro / con components tend to be extremely simple. Define a function called pro-con
inside your foo/components/pro-con.typ
file:
#pro-con(pros: [], cons: []) = {
// implement your table here
}
For examples on how to create a pro / con table, check out out how other themes implement them.
TOC Component
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 has created some utility functions to abstract this.
To get started with your table of contents, first define a function called toc
in your foo/components/toc.typ
file.
However, unlike the pro-con
function, this function will depend on the print-toc
utility function. This looks like this:
// foo/components/toc.typ
// Use this import if you're developing in the Notebookinator directly
#import "/utils.typ"
// Use this import if you're using the notebookinator as an external dependency
#import "@local/notebookinator:1.0.1": utils
#let toc() = utils.print-toc((frontmatter, body, appendix) => {
// ...
}
This syntax might look a little weird, so lets break it down. We're defining the function toc
, which is equal to the utils.print-toc
function. The utils.print-toc
function takes 1 argument, and this argument is a function. We're choosing to pass in a lambda function here, since this function only makes sense in the context of the table of contents (we won't need to call it in multiple places).
Inside this lambda we have access to the frontmatter, body, and appendix variables. These variables are all arrays, which dictionaries which all contain the same information as the ctx
variables from this section, with the addition of a page-number
field, which is an integer.
With these variables, we can simply loop over each one, and print out another entry in the table of contents each time.
Here's what that looks like for the frontmatter entries:
// foo/components/toc.typ
#import "/utils.typ"
#let toc() = utils.print-toc((_, body, appendix) => {
// replace frontmatter with _
// to indicate we aren't using it
heading[Contents]
stack(spacing: 0.5em, ..for entry in body {
([
#entry.title
#box(width: 1fr, line(length: 100%, stroke: (dash: "dotted")))
#entry.page-number
],)
})
// TODO: do the same loop for the body entries as well
}
Decision Matrix Component
No data needs to be fetched for the decision matrix, however we do provide a helper function to calculate which choice has the highest score overall, and per category.
// foo/components/decision-matrix.typ
#import "/utils.typ"
#let decision-matrix(properties: (), ..choices) = {
let data = utils.calc-decision-matrix(properties: properties, ..choices)
}
Once you've calculated your results, you can render a table displaying them. Here's a simple example to get you started, copied from the default-theme
:
#let decision-matrix(properties: (), ..choices) = {
let data = utils.calc-decision-matrix(properties: properties, ..choices)
tablex( // table element
// the extra 2 columns account for the names of the choices and the total
columns: for _ in range(properties.len() + 2) {
(1fr,)
},
[], // Blank box
// first we'll display all of the names on the top row
..for property in properties {
([ *#property.name* ],)
},
// then we'll add an extra column for the total
[*Total*],
// then we'll add a row for each of the choices, and their scores
..for choice in data {
// Override the fill if the choice has the highest score
let cell = if choice.values.total.highest { cellx.with(fill: green) } else { cellx }
(cell[*#choice.name*], ..for value in choice.values {
(cell[#value.at(1).value],)
})
},
)
}
Glossary Component
The glossary component is similar to the table of components in that it requires information about the document to function.
To get access to the glossary entries, you can use the print-glossary
function provided by the utils
to fetch all of the glossary terms, in alphabetical order.
The function passed into the print-glossary
function has access to the glossary
variable, which is an array. Each entry in the array is a dictionary containing a word
field and a definition
field. Both are strings.
// foo/components/glossary.typ
// Use this import if you're developing in the Notebookinator directly
#import "/utils.typ"
// Use this import if you're using the notebookinator as an external dependency
#import "@local/notebookinator:1.0.1": utils
#let glossary() = utils.print-glossary(glossary => {
stack(spacing: 0.5em, ..for entry in glossary {
([
= #entry.word
#entry.definition
],)
})
}
Using the Theme
Now that you've written the theme, you can apply it to your notebook.
In the entry point of your notebook (most likely main.typ
), import your theme like this:
// main.typ
#import "foo/foo.typ": foo-theme, components
Once you do that, change the theme to foo-theme
:
// main.typ
#show: notebook.with(
theme: foo-theme
)
Contributing a Theme
Before you get started, we ask that you read our contributing guide.
Upon cloning your fork of the Notebookinator, you can find all of the existing themes in the themes/
folder.
Each theme in the themes/
folder follows the same file structure detailed in the previous guide. If you have a theme on hand, just copy it into the themes/
folder. For example, the foo theme's entry point should now be at themes/foo/foo.typ
If you refer to the Notebookinator as a local package anywhere in your theme, make sure to replace those imports with references to the files directly.
For example, if a file imports utils
like this:
#import "@local/notebookinator:1.0.1": utils
Replace it with this:
#import "/utils.typ"
If you don't currently have a theme written, follow the previous guide from the beginning, but place the theme folder in the themes/
folder.