├── .envrc ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.md │ └── FEATURE_REQUEST.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ └── docs.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── book.toml ├── mdbook-admonish.css ├── src │ ├── SUMMARY.md │ ├── basic_usage.md │ ├── developer_documentation │ │ ├── custom_themes.md │ │ ├── developer_documentation.md │ │ ├── project_architecture.md │ │ └── theme_contribution.md │ ├── installation.md │ ├── introduction.md │ ├── reference.md │ └── reference.typ └── typst-doc.css ├── entries.typ ├── flake.lock ├── flake.nix ├── gallery ├── linear-03.png ├── linear-04.png ├── linear-05.png ├── linear.typ ├── radial-4.png ├── radial-5.png ├── radial-6.png └── radial.typ ├── globals.typ ├── glossary.typ ├── internals.typ ├── justfile ├── lib.typ ├── logo.png ├── packages.typ ├── scripts ├── format └── package ├── themes ├── default │ ├── components.typ │ └── default.typ ├── linear │ ├── colors.typ │ ├── components │ │ ├── components.typ │ │ ├── decision-matrix.typ │ │ ├── glossary.typ │ │ ├── pro-con.typ │ │ └── toc.typ │ ├── entries.typ │ ├── entry-types.typ │ ├── format.typ │ ├── linear.typ │ └── rules.typ ├── radial │ ├── Mediamodifier-Design.svg │ ├── colors.typ │ ├── components │ │ ├── admonitions.typ │ │ ├── components.typ │ │ ├── decision-matrix.typ │ │ ├── gantt-chart.typ │ │ ├── glossary.typ │ │ ├── graphs.typ │ │ ├── label.typ │ │ ├── pro-con.typ │ │ ├── team.typ │ │ ├── title.typ │ │ ├── toc.typ │ │ └── tournament.typ │ ├── entries.typ │ ├── format.typ │ ├── icons │ │ ├── bar-chart.svg │ │ ├── build.svg │ │ ├── check.svg │ │ ├── flask.svg │ │ ├── function.svg │ │ ├── hammer.svg │ │ ├── icons.typ │ │ ├── info.svg │ │ ├── light-bulb.svg │ │ ├── page.svg │ │ ├── pencil.svg │ │ ├── question-mark.svg │ │ ├── quotes.svg │ │ ├── refresh.svg │ │ ├── target.svg │ │ ├── terminal.svg │ │ ├── warning.svg │ │ ├── web.svg │ │ └── x.svg │ ├── metadata.typ │ ├── radial.tmTheme │ ├── radial.typ │ └── rules.typ └── themes.typ ├── typst.toml ├── utils.typ └── utils ├── components.typ ├── misc.typ └── theme.typ /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug in the project 4 | title: "🐛" 5 | labels: "repro:required" 6 | assignees: "" 7 | --- 8 | 9 | #### Expected Behavior 10 | 11 | 12 | #### Observed Behavior 13 | 14 | 15 | #### Steps to Reproduce 16 | 17 | 18 | #### Environment 19 | 20 | 21 | - Typst Version: 22 | - Notebookinator Version: 23 | - Operating System: 24 | 25 | #### Additional Information 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for the project 4 | title: "✨" 5 | labels: "type:enhancement" 6 | assignees: "" 7 | --- 8 | 9 | #### Requested Feature 10 | 11 | 12 | 13 | - a 14 | - b 15 | - c 16 | 17 | #### Current Implementation 18 | 19 | 20 | #### Motivation 21 | 22 | 23 | #### (Optional) Possible Implementations and Alternatives 24 | 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### Summary 2 | 3 | 4 | #### Checklist 5 | 6 | - [ ] I have fully tested all of the added features 7 | - [ ] I have fully documented all of the relevant code 8 | 9 | #### Additional Notes 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: main 10 | pull_request: 11 | branches: "**" 12 | 13 | jobs: 14 | ci: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Install just from crates.io 19 | uses: baptiste0928/cargo-install@v2 20 | with: 21 | crate: just 22 | - name: Install Typst 23 | uses: yusancky/setup-typst@v3 24 | id: setup-typst 25 | - name: Run checks 26 | run: | 27 | just package local 28 | just package preview 29 | just gallery 30 | # TODO: run unit tests here 31 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: GH Pages Deploy 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: [stable] 10 | paths-ignore: 11 | - "docs/**" 12 | 13 | pull_request: 14 | branches: [main] 15 | paths-ignore: 16 | - "docs/**" 17 | 18 | jobs: 19 | build-deploy: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v2 25 | 26 | - name: Install Typst 27 | uses: yusancky/setup-typst@v3 28 | id: setup-typst 29 | 30 | - name: Install Mdbook 31 | uses: extractions/setup-crate@v1 32 | with: 33 | owner: rust-lang 34 | name: mdBook 35 | 36 | - name: Install Mdbook admonish 37 | uses: extractions/setup-crate@v1 38 | with: 39 | owner: tommilligan 40 | name: mdbook-admonish 41 | 42 | - name: Install Mdbook typst doc 43 | uses: baptiste0928/cargo-install@v3 44 | with: 45 | crate: mdbook-typst-doc 46 | git: https://github.com/fenjalien/mdbook-typst-doc 47 | tag: 0.1.2 48 | 49 | - name: Build 50 | run: | 51 | typst compile docs/src/reference.typ --root ./ 52 | cd docs 53 | mdbook build 54 | 55 | - name: Deploy to GitHub Pages 56 | if: ${{ github.event_name != 'pull_request' }} 57 | uses: peaceiris/actions-gh-pages@v3 58 | with: 59 | github_token: ${{ secrets.GITHUB_TOKEN }} 60 | publish_dir: docs/book 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | *.pdf 3 | !docs.pdf 4 | test.typ 5 | 6 | # This is kind of hacky, but I need to ignore the images in the gallery that I'm not using 7 | gallery/*.png 8 | !gallery/radial-4.png 9 | !gallery/radial-5.png 10 | !gallery/radial-6.png 11 | 12 | !gallery/linear-03.png 13 | !gallery/linear-04.png 14 | !gallery/linear-05.png 15 | 16 | docs/book 17 | docs/src/reference.pdf 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![alt text](./logo.png) 4 | 5 |
6 | 7 |
8 | 9 | ![GitHub Repo stars](https://img.shields.io/github/stars/battlech1cken/notebookinator?style=for-the-badge) 10 | ![GitHub contributors](https://img.shields.io/github/contributors/battlech1cken/notebookinator?style=for-the-badge) 11 | ![GitHub License](https://img.shields.io/github/license/battlech1cken/notebookinator?style=for-the-badge) 12 | ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/battlech1cken/notebookinator/ci.yml?style=for-the-badge&label=CI) 13 | ![Discord](https://img.shields.io/discord/1183511612322222183?style=for-the-badge&logo=discord&label=Discord) 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 | 26 | 27 | 32 | 37 | 38 | 43 | 44 |
28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 |
45 | 46 | ## Linear Theme 47 | 48 | 49 | 50 | 55 | 60 | 61 | 66 | 67 |
51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 |
68 | 69 | ## Special Thanks 70 | 71 | - Maqmoon (logo drawing) 72 | - CeTZ (inspiration for a lot of different things) 73 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["BattleCh1cken"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "The Notebookinator" 7 | 8 | [preprocessor.admonish] 9 | command = "mdbook-admonish" 10 | assets_version = "3.0.2" # do not edit: managed by `mdbook-admonish install` 11 | 12 | [preprocessor.typst-doc] 13 | command = "mdbook-typst-doc" 14 | 15 | [output.html] 16 | additional-css = [ 17 | "./mdbook-admonish.css", 18 | "./typst-doc.css" 19 | ] 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](./introduction.md) 4 | 5 | - [Installation](./installation.md) 6 | - [Basic Usage](./basic_usage.md) 7 | - [API Reference](./reference.md) 8 | - [Developer Documentation](./developer_documentation/developer_documentation.md) 9 | - [Project Architecture](./developer_documentation/project_architecture.md) 10 | - [Making Your Own Theme](./developer_documentation/custom_themes.md) 11 | - [Contributing a Theme](./developer_documentation/theme_contribution.md) 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/src/developer_documentation/developer_documentation.md: -------------------------------------------------------------------------------- 1 | # Developer Documentation 2 | 3 | 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. 4 | 5 | The section covers: 6 | 7 | - project architecture 8 | - creating your own theme 9 | - contributing themes to the Notebookinator 10 | -------------------------------------------------------------------------------- /docs/src/developer_documentation/project_architecture.md: -------------------------------------------------------------------------------- 1 | # Project Architecture 2 | 3 | The Notebookinator is split into two sections, the base template, and the 4 | themes. The base template functions as the backend of the project. It handles 5 | all of the information processing, keeps track of global state, makes sure page 6 | numbers are correct, and so on. It exposes the main API that the user interacts 7 | for creating entries and creating glossary entries. 8 | 9 | The themes act as the frontend to the project, and are what the user actually 10 | sees. The themes expose an API for components that need to be called directly 11 | inside of entries. This could include things like admonitions, charts, and 12 | decision matrices. 13 | 14 | ## File Structure 15 | 16 | - `lib.typ`: The entrypoint for the whole template. 17 | - `internals.typ`: All of the internal function calls that should not be used by 18 | theme authors or users. 19 | - `entries.typ`: Contains the user facing API for entries, as well as the internal 20 | template functions for printing out the entries and cover. 21 | - `glossary.typ`: Contains the user facing API for the glossary. 22 | - `globals.typ`: Contains all of the global variables for the entire project. 23 | - `utils.typ`: Utility functions intended to help implement themes. Imports all of the functions located in `utils`. 24 | - `utils/` 25 | - `misc.typ`: Contains miscellaneous utility functions that don't fit in other categories 26 | - `components.typ`: Contains the constructors used by themes to implement components 27 | - `theme.typ`: Contains the constructors used to create themes 28 | - `themes/`: The folder containing all of the themes. 29 | - `themes.typ`: An index of all the themes are contained in the template 30 | - `docs.typ`: The entry point for the project documentation. 31 | - `docs-template.typ`: The template for the project documentation. 32 | -------------------------------------------------------------------------------- /docs/src/developer_documentation/theme_contribution.md: -------------------------------------------------------------------------------- 1 | # Contributing a Theme 2 | 3 | Before you get started, we ask that you read our [contributing guide](https://github.com/The-Notebookinator/notebookinator/blob/main/.github/CONTRIBUTING.md). 4 | 5 | Upon cloning your fork of the Notebookinator, you can find all of the existing themes in the `themes/` folder. 6 | 7 | Each theme in the `themes/` folder follows the same file structure detailed in the [previous](./custom_themes.md) 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` 8 | 9 | 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. 10 | 11 | For example, if a file imports `utils` like this: 12 | 13 | ```typ 14 | #import "@local/notebookinator:1.0.1": utils 15 | ``` 16 | 17 | Replace it with this: 18 | 19 | ```typ 20 | #import "/utils.typ" 21 | ``` 22 | 23 | If you don't currently have a theme written, follow the [previous](./custom_themes.md) guide from the beginning, but place the theme folder in the `themes/` folder. 24 | -------------------------------------------------------------------------------- /docs/src/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ```admonish warning 4 | This installation process is temporary, as we wait for Typst to overhaul its 5 | process for packaging templates and packages. 6 | ``` 7 | 8 | ## Devcontainer / Codespaces 9 | 10 | 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](https://github.com/The-Notebookinator/quick-start-template) for the best experience. 11 | 12 | ```admonish warning 13 | While this approach is easier, you may encounter some performance issues as your notebook increases in size. 14 | ``` 15 | 16 | The only thing you'll need in preparation is a [GitHub](https://github.com/) account (only if using Codespaces) and [VSCode](https://code.visualstudio.com/) installed. Once you do that, just follow the instructions in the [README](https://github.com/The-Notebookinator/quick-start-template). 17 | 18 | ## Local Installation 19 | 20 | This installation process is a little harder, and requires more software to be manually installed on your computer. 21 | 22 | Make sure you have the following software installed: 23 | 24 | - [Typst](https://github.com/typst/typst?tab=readme-ov-file#installation) 25 | - [Git](https://git-scm.com/downloads) 26 | - [VSCode](https://code.visualstudio.com/) 27 | 28 | Once you've installed everything, run the following commands: 29 | 30 | ```admonish info 31 | If you're running this on Windows, you'll need to run these commands in a sh 32 | shell, like git-bash or the shell packaged with Cygwin or GitHub Desktop. 33 | ``` 34 | 35 | ```bash 36 | git clone --depth 1 --branch 1.0.1 https://github.com/The-Notebookinator/notebookinator 37 | ./notebookinator/scripts/package @local 38 | rm -rf notebookinator 39 | ``` 40 | 41 | Once you do that you should be good to go! 42 | -------------------------------------------------------------------------------- /docs/src/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | 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. 4 | 5 | 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. 6 | 7 | If you're new here, we recommend you read the [installation guide](./installation.md) and the [basic usage guide](./basic_usage.md). 8 | 9 | If you already know what you're doing, and just some quick information, check out the [API reference](./reference.typ). 10 | 11 | If you're an advanced user, and want to extend / contribute to the Notebookinator, check out our [developer documentation](./developer_documentation/developer_documentation.md). 12 | -------------------------------------------------------------------------------- /docs/src/reference.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | Check out the API reference [here](./reference.pdf). 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1716358718, 6 | "narHash": "sha256-NQbegJb2ZZnAqp2EJhWwTf6DrZXSpA6xZCEq+RGV1r0=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "3f316d2a50699a78afe5e77ca486ad553169061e", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixpkgs-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs", 22 | "utils": "utils" 23 | } 24 | }, 25 | "systems": { 26 | "locked": { 27 | "lastModified": 1681028828, 28 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 29 | "owner": "nix-systems", 30 | "repo": "default", 31 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "nix-systems", 36 | "repo": "default", 37 | "type": "github" 38 | } 39 | }, 40 | "utils": { 41 | "inputs": { 42 | "systems": "systems" 43 | }, 44 | "locked": { 45 | "lastModified": 1710146030, 46 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 47 | "owner": "numtide", 48 | "repo": "flake-utils", 49 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "numtide", 54 | "repo": "flake-utils", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | outputs = 8 | { self 9 | , nixpkgs 10 | , utils 11 | , 12 | }: 13 | utils.lib.eachDefaultSystem (system: 14 | let 15 | pkgs = import nixpkgs { inherit system; }; 16 | mdbook-typst-doc = pkgs.rustPlatform.buildRustPackage rec { 17 | pname = "mdbook-typst-doc"; 18 | version = "0.1.2"; 19 | 20 | src = pkgs.fetchFromGitHub { 21 | owner = "fenjalien"; 22 | repo = pname; 23 | rev = version; 24 | sha256 = "sha256-GulEn/MSiE8su0k+VL3uNslYCbjLJnmW6rMOdTQSMUw="; 25 | }; 26 | 27 | cargoHash = "sha256-KD6J8dTIPaII4ISh0PW6u1EMj5JVEDYcXuZ75ycbSys="; 28 | }; 29 | in 30 | { 31 | devShell = pkgs.mkShell { 32 | packages = with pkgs; [ 33 | typst 34 | 35 | nodePackages_latest.prettier 36 | just 37 | mdbook 38 | mdbook-admonish 39 | 40 | mdbook-typst-doc 41 | typstyle 42 | ]; 43 | }; 44 | 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /gallery/linear-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Notebookinator/notebookinator/2488dfa70d1473016070644fa60631b66a519679/gallery/linear-03.png -------------------------------------------------------------------------------- /gallery/linear-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Notebookinator/notebookinator/2488dfa70d1473016070644fa60631b66a519679/gallery/linear-04.png -------------------------------------------------------------------------------- /gallery/linear-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Notebookinator/notebookinator/2488dfa70d1473016070644fa60631b66a519679/gallery/linear-05.png -------------------------------------------------------------------------------- /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 | ] -------------------------------------------------------------------------------- /gallery/radial-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Notebookinator/notebookinator/2488dfa70d1473016070644fa60631b66a519679/gallery/radial-4.png -------------------------------------------------------------------------------- /gallery/radial-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Notebookinator/notebookinator/2488dfa70d1473016070644fa60631b66a519679/gallery/radial-5.png -------------------------------------------------------------------------------- /gallery/radial-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Notebookinator/notebookinator/2488dfa70d1473016070644fa60631b66a519679/gallery/radial-6.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /globals.typ: -------------------------------------------------------------------------------- 1 | #let frontmatter-entries = state("frontmatter-entries", ()) 2 | #let entries = state("entries", ()) 3 | #let appendix-entries = state("appendix-entries", ()) 4 | 5 | #let glossary-entries = state("glossary-entries", ()) 6 | -------------------------------------------------------------------------------- /glossary.typ: -------------------------------------------------------------------------------- 1 | #import "./globals.typ" 2 | 3 | /// Adds a term to the glossary. 4 | /// 5 | /// *Example Usage:* 6 | /// 7 | /// ```typ 8 | /// #glossary.add-term( 9 | /// "Word", 10 | /// "The definiton of the word." 11 | /// ) 12 | /// ``` 13 | /// 14 | /// - word (string): The word you're defining 15 | /// - definition (string): The definition of the word 16 | /// 17 | #let add-term(word, definition) = { 18 | globals.glossary-entries.update(entries => { 19 | entries.push((word: word, definition: definition)) 20 | entries 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /internals.typ: -------------------------------------------------------------------------------- 1 | #import "./globals.typ" 2 | #import "./themes/themes.typ" 3 | 4 | /// Internal function used by the template to print out all of the entries 5 | /// 6 | /// - theme (theme): 7 | /// -> content 8 | #let print-entries(theme: (:)) = { 9 | let print-helper(section, state) = context { 10 | for entry in state.final() { 11 | let entry-func = theme.at(section + "-entry") 12 | entry-func(entry.body, ctx: entry.ctx) 13 | } 14 | } 15 | 16 | print-helper("frontmatter", globals.frontmatter-entries) 17 | print-helper("body", globals.entries) 18 | print-helper("appendix", globals.appendix-entries) 19 | } 20 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | gallery_dir := "./gallery" 2 | 3 | package target: 4 | ./scripts/package "{{target}}" 5 | 6 | install: 7 | ./scripts/package "@local" 8 | 9 | gallery: 10 | #!/usr/bin/env bash 11 | set -euxo pipefail 12 | 13 | for f in "{{gallery_dir}}"/*.typ; do 14 | typst c "$f" --format png "${f/.typ}-{n}.png" --root ./ 15 | done 16 | -------------------------------------------------------------------------------- /lib.typ: -------------------------------------------------------------------------------- 1 | #import "./internals.typ": * 2 | #import "./entries.typ": * 3 | #import "./utils.typ" 4 | #import "./themes/themes.typ" 5 | #import "./glossary.typ" 6 | 7 | /// The base notebook template. This function is meant to be applied to your entire document as a show rule. 8 | /// 9 | /// *Example Usage:* 10 | /// 11 | /// ```typ 12 | /// #import themes.default: default-theme 13 | /// 14 | /// #show: notebook.with( 15 | /// theme: default-theme 16 | /// ) 17 | /// ``` 18 | /// - team-name (string): The name of your team. 19 | /// - season (string): The name of the current season. 20 | /// - year (string): The years in which the notebook is being written. 21 | /// - theme (theme): The theme that will be applied to all of the entries. If no theme is specified, it will fall back on the default theme. 22 | /// - cover (content): The title page of the notebook. 23 | /// - body (content): The content of the notebook. This will be ignored. Use the create-entry functions instead. 24 | /// -> content 25 | #let notebook( 26 | team-name: "", 27 | season: "", 28 | year: "", 29 | cover: none, 30 | theme: (:), 31 | body, 32 | ) = { 33 | let rules = theme.rules 34 | show: doc => rules(doc) 35 | 36 | let cover-content = if cover == none { 37 | let ctx = ( 38 | team-name: team-name, 39 | season: season, 40 | year: year, 41 | ) 42 | 43 | (theme.cover)(ctx: ctx) 44 | } else { 45 | cover 46 | } 47 | page( 48 | margin: 0pt, 49 | cover-content, 50 | ) 51 | 52 | // Filler page 53 | page[] 54 | 55 | print-entries(theme: theme) 56 | body 57 | // FIXME: this should be ignored, but the document doesn't properly render without it. 58 | } 59 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Notebookinator/notebookinator/2488dfa70d1473016070644fa60631b66a519679/logo.png -------------------------------------------------------------------------------- /packages.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/tablex:0.0.8" 2 | #import "@preview/showybox:2.0.1" 3 | #import "@preview/timeliney:0.0.1" 4 | #import "@preview/cetz:0.2.0" 5 | -------------------------------------------------------------------------------- /scripts/format: -------------------------------------------------------------------------------- 1 | find ./ -iname "*.typ" | xargs typstfmt 2 | -------------------------------------------------------------------------------- /scripts/package: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | # Credit to the Cetz package for this script 5 | # https://github.com/johannes-wolf/cetz/blob/master/scripts/package 6 | 7 | PKG_PREFIX="notebookinator" 8 | 9 | # List of all files that get packaged 10 | files=( 11 | lib.typ 12 | entries.typ 13 | glossary.typ 14 | utils.typ 15 | globals.typ 16 | packages.typ 17 | internals.typ 18 | themes/ 19 | utils/ 20 | typst.toml 21 | LICENSE 22 | README.md 23 | ) 24 | 25 | # Local package directories per platform 26 | if [[ "$OSTYPE" == "linux"* ]]; then 27 | DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}" 28 | elif [[ "$OSTYPE" == "darwin"* ]]; then 29 | DATA_DIR="$HOME/Library/Application Support" 30 | else 31 | DATA_DIR="${APPDATA}" 32 | fi 33 | 34 | if (( $# < 1 )) || [[ "${1:-}" == "help" ]]; then 35 | echo "package TARGET" 36 | echo "" 37 | echo "Packages all relevant files into a directory named '${PKG_PREFIX}/'" 38 | echo "at TARGET. If TARGET is set to @local, the local Typst package directory" 39 | echo "will be used so that the package gets installed for local use." 40 | echo "The version is read from 'typst.toml' in the project root." 41 | echo "" 42 | echo "Local package prefix: $DATA_DIR/typst/package/local" 43 | exit 1 44 | fi 45 | 46 | function read-toml() { 47 | local file="$1" 48 | local key="$2" 49 | # Read a key value pair in the format: = "" 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/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 | -------------------------------------------------------------------------------- /themes/default/default.typ: -------------------------------------------------------------------------------- 1 | #import "./components.typ" 2 | #import "/utils.typ" 3 | 4 | #let rules = utils.make-rules(doc => { 5 | doc 6 | }) 7 | 8 | #let cover = utils.make-cover(ctx => [ 9 | #set align(center + horizon) 10 | *Default Cover* 11 | ]) 12 | 13 | #let frontmatter-entry = utils.make-frontmatter-entry((ctx, body) => { 14 | show: page.with( 15 | header: [ 16 | = #ctx.title 17 | #box( 18 | width: 1fr, 19 | line(length: 100%), 20 | ) 21 | ], 22 | footer: align( 23 | center, 24 | context counter(page).display("i"), 25 | ), 26 | ) 27 | 28 | body 29 | }) 30 | 31 | #let body-entry = utils.make-body-entry((ctx, body) => { 32 | show: page.with( 33 | header: [ 34 | = #ctx.title 35 | #box( 36 | width: 1fr, 37 | line(length: 100%), 38 | ) 39 | ], 40 | footer: align( 41 | center, 42 | context counter(page).display(), 43 | ), 44 | ) 45 | 46 | body 47 | }) 48 | 49 | #let appendix-entry = utils.make-appendix-entry((ctx, body) => { 50 | show: page.with( 51 | header: [ 52 | = #ctx.title 53 | #box( 54 | width: 1fr, 55 | line(length: 100%), 56 | ) ], 57 | footer: align( 58 | center, 59 | context counter(page).display("i"), 60 | ), 61 | ) 62 | body 63 | }) 64 | } 65 | 66 | #let default-theme = utils.make-theme( 67 | // Global show rules 68 | rules: rules, 69 | cover: cover, 70 | // Entry pages 71 | frontmatter-entry: frontmatter-entry, 72 | body-entry: body-entry, 73 | appendix-entry: appendix-entry, 74 | ) 75 | -------------------------------------------------------------------------------- /themes/linear/colors.typ: -------------------------------------------------------------------------------- 1 | // Dark 2 | #let dark-red = rgb("#DE6C8E") 3 | #let dark-yellow = rgb("#F6AA28") 4 | #let dark-green = rgb("#04DBAD") 5 | #let dark-blue = rgb("#3195D8") 6 | #let dark-purple = rgb("#584CBD") 7 | #let dark-pink = rgb("#CD89E2") 8 | 9 | // Light 10 | #let light-red = rgb("#EBA8BC") 11 | #let light-yellow = rgb("#FACC7E") 12 | #let light-green = rgb("#68E8CD") 13 | #let light-blue = rgb("#8EC5E9") 14 | #let light-purple = rgb("#B4AFE1") 15 | #let light-pink = rgb("#DDB0EB") 16 | 17 | // Component 18 | #let pro-green = rgb("#04db69") 19 | #let con-red = rgb("#e95f5f") 20 | #let decision-green = rgb("#00c05a") 21 | 22 | // Surface 23 | #let surface-0 = rgb("#f1f3f5") 24 | #let surface-1 = rgb("#e9ecef") 25 | #let surface-2 = rgb("#dee2e6") 26 | #let surface-3 = rgb("#ced4da") 27 | #let surface-4 = rgb("#adb5bd") -------------------------------------------------------------------------------- /themes/linear/components/components.typ: -------------------------------------------------------------------------------- 1 | #import "./glossary.typ": * 2 | #import "./pro-con.typ": * 3 | #import "./toc.typ": * 4 | #import "./decision-matrix.typ": * 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /themes/linear/components/glossary.typ: -------------------------------------------------------------------------------- 1 | #import "/utils.typ" 2 | 3 | #let glossary = utils.make-glossary(glossary => { 4 | stack( 5 | dir: ttb, 6 | spacing: 15pt, 7 | ..for entry in glossary { 8 | ( 9 | [ 10 | #box( 11 | inset: 0.5em, 12 | fill: gray, 13 | )[== #entry.word] 14 | #h(5pt) 15 | #box( 16 | baseline: -10pt, 17 | width: 1fr, 18 | line(length: 100%), 19 | ) 20 | 21 | #entry.definition 22 | ], 23 | ) 24 | }, 25 | ) 26 | }) 27 | -------------------------------------------------------------------------------- /themes/linear/components/pro-con.typ: -------------------------------------------------------------------------------- 1 | #import "../colors.typ": * 2 | #import "/utils.typ" 3 | 4 | #let pro-con = utils.make-pro-con((pros, cons) => { 5 | table( 6 | columns: (50%, 50%), 7 | inset: 0.75em, 8 | fill: (col, row) => if row == 0 { 9 | if col == 0 { 10 | pro-green 11 | } 12 | if col == 1 { 13 | con-red 14 | } 15 | }, 16 | align( 17 | center, 18 | text(size: 14pt, weight: "semibold", [Pros]), 19 | ), 20 | align( 21 | center, 22 | text(size: 14pt, weight: "semibold", [Cons]), 23 | ), 24 | pros, 25 | cons, 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /themes/linear/components/toc.typ: -------------------------------------------------------------------------------- 1 | #import "../../../utils.typ" 2 | #import "../entry-types.typ": * 3 | /// Prints the table of contents. 4 | /// 5 | /// *Example Usage* 6 | /// ```typ 7 | /// #create-frontmatter-entry(title: "Table of Contents")[ 8 | /// #components.toc() 9 | /// ] 10 | /// ``` 11 | #let toc = utils.make-toc(( 12 | _, 13 | body, 14 | appendix, 15 | ) => { 16 | let previous-date 17 | 18 | let toc = stack( 19 | dir: ttb, 20 | spacing: 0.3em, 21 | ..for entry in body { 22 | let date-content = if entry.date == previous-date { } else { 23 | box( 24 | inset: 5pt, 25 | fill: white, 26 | entry.date.display("[day]/[month]/[year]"), 27 | ) 28 | } 29 | 30 | previous-date = entry.date 31 | ( 32 | [ 33 | // Single line content 34 | #box( 35 | baseline: 0.35em, 36 | width: 5em, 37 | )[ 38 | #set align(center) 39 | #date-content 40 | ] 41 | #h(1em) 42 | #box( 43 | fill: entry-type-metadata.at(entry.type), 44 | inset: ( 45 | x: .4em, 46 | y: .6em, 47 | ), 48 | baseline: 25%, 49 | )[#entry.title #h(1.4em)] 50 | #h(1em) 51 | #box( 52 | width: 1fr, 53 | line( 54 | length: 100%, 55 | start: ( 56 | 0pt, 57 | -.35em, 58 | ), 59 | ), 60 | ) 61 | #h(1em) 62 | #entry.page-number 63 | ], 64 | ) 65 | }, 66 | ) 67 | 68 | let height = measure(toc).height 69 | 70 | box[ 71 | #place( 72 | top + left, 73 | dx: 2.5em, 74 | dy: 1em, 75 | line( 76 | angle: 90deg, 77 | length: height - 12pt, 78 | ), 79 | ) 80 | #toc 81 | ] 82 | }) 83 | -------------------------------------------------------------------------------- /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/linear/entry-types.typ: -------------------------------------------------------------------------------- 1 | #import "colors.typ": * 2 | 3 | #let entry-type-metadata = ( 4 | "identify": light-red, 5 | "brainstorm": light-yellow, 6 | "decide": light-green, 7 | "build": light-blue, 8 | "program": light-purple, 9 | "test": light-pink, 10 | "management": gray, 11 | "notebook": gray, 12 | ) 13 | -------------------------------------------------------------------------------- /themes/linear/format.typ: -------------------------------------------------------------------------------- 1 | #import "entry-types.typ": * 2 | /// Formats the border lines 3 | /// 4 | /// Example Usage: 5 | /// 6 | /// ```typ 7 | /// #set-border() 8 | /// ``` 9 | #let set-border(type) = { 10 | let top-axis = -8.4% // The title line height 11 | let bottom-axis = 100% // The automatic bottom border 12 | 13 | // Adjusts top and bottom borders for frontmatter and appendix entries 14 | if (type == none) { 15 | top-axis = -6.6% 16 | bottom-axis = 106% 17 | } 18 | 19 | let left-axis = -8% // 8% less than the automatic left border (0%) 20 | let right-axis = 108% // 8% more than the automatic right border (100%) 21 | 22 | set line(stroke: 1.5pt) 23 | // Top left border 24 | place(line(start: (left-axis, top-axis), end: (0%, top-axis))) 25 | // Top right border 26 | place(line(start: (right-axis, top-axis), end: (100%, top-axis))) 27 | // Left border 28 | place(line(start: (left-axis, top-axis), end: (left-axis, bottom-axis))) 29 | // Right border 30 | place(line(start: (right-axis, top-axis), end: (right-axis, bottom-axis))) 31 | // Bottom border 32 | place(line(start: (left-axis, bottom-axis), end: (right-axis, bottom-axis))) 33 | } 34 | 35 | #let set-heading(it, type) = { 36 | show: block 37 | let color = entry-type-metadata.at(type) 38 | 39 | if it.level == 1 { 40 | set text(18pt) 41 | } 42 | if it.level == 2 { 43 | set text(15pt) 44 | } 45 | if it.level == 3 { 46 | set text(13pt) 47 | } else { 48 | set text(11pt) 49 | } 50 | 51 | set text(weight: "semibold") 52 | 53 | box(inset: 0.5em, fill: color)[#it.body] 54 | h(5pt) 55 | box(baseline: -10pt, width: 1fr, line(length: 100%)) 56 | } 57 | -------------------------------------------------------------------------------- /themes/linear/linear.typ: -------------------------------------------------------------------------------- 1 | #import "rules.typ": rules 2 | #import "entries.typ": cover, frontmatter-entry, body-entry, appendix-entry 3 | #import "format.typ": set-border, set-heading 4 | #import "components/components.typ" 5 | #import "colors.typ": * 6 | #import "/utils.typ" 7 | 8 | #let linear-theme = utils.make-theme( 9 | // Global show rules 10 | rules: rules, 11 | cover: cover, 12 | // Entry pages 13 | frontmatter-entry: frontmatter-entry, 14 | body-entry: body-entry, 15 | appendix-entry: appendix-entry, 16 | ) 17 | -------------------------------------------------------------------------------- /themes/linear/rules.typ: -------------------------------------------------------------------------------- 1 | #import "format.typ": * 2 | #import "/utils.typ" 3 | 4 | #let rules = utils.make-rules(doc => { 5 | set text( 6 | font: "Blinker", 7 | size: 11pt, 8 | ) 9 | doc 10 | }) 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /themes/radial/colors.typ: -------------------------------------------------------------------------------- 1 | #let red = rgb("#e03131") 2 | #let pink = rgb("#c2255c") 3 | #let purple = rgb("#9c36b5") 4 | #let violet = rgb("#6741d9") 5 | #let indigo = rgb("#3b5bdb") 6 | #let blue = rgb("#1971c2") 7 | #let cyan = rgb("#0c8599") 8 | #let teal = rgb("#099268") 9 | #let green = rgb("#2f9e44") 10 | #let lime = rgb("#66a80f") 11 | #let yellow = rgb("#f08c00") 12 | #let orange = rgb("#e8590c") 13 | #let gray = rgb("#343a40") 14 | #let white = rgb("#ffffff") 15 | #let black = rgb("#1e1e1e") 16 | 17 | #let surface-0 = rgb("#f1f3f5") 18 | #let surface-1 = rgb("#e9ecef") 19 | #let surface-2 = rgb("#dee2e6") 20 | #let surface-3 = rgb("#ced4da") 21 | #let surface-4 = rgb("#adb5bd") 22 | -------------------------------------------------------------------------------- /themes/radial/components/admonitions.typ: -------------------------------------------------------------------------------- 1 | #import "../colors.typ": * 2 | #import "../icons/icons.typ" 3 | #import "/utils.typ" 4 | #import "../metadata.typ": * 5 | #import "/packages.typ": showybox 6 | #import showybox: * 7 | 8 | #let admonition = utils.make-admonition((type, body) => { 9 | let info = admonition-type-metadata.at(type) 10 | let colored-icon = utils.change-icon-color( 11 | raw-icon: info.icon, 12 | fill: info.color, 13 | ) 14 | 15 | showybox( 16 | frame: ( 17 | border-color: info.color, 18 | body-color: info.color.lighten(80%), 19 | thickness: ( 20 | left: 4pt, 21 | ), 22 | radius: 1.5pt, 23 | ), 24 | [ 25 | #text( 26 | size: 15pt, 27 | fill: info.color, 28 | [ 29 | #box(baseline: 30%, image.decode(colored-icon, width: 1.5em)) *#info.title* 30 | ], 31 | ) 32 | \ 33 | #body 34 | ], 35 | ) 36 | }) 37 | -------------------------------------------------------------------------------- /themes/radial/components/components.typ: -------------------------------------------------------------------------------- 1 | #import "./toc.typ": * 2 | #import "./glossary.typ": * 3 | #import "./gantt-chart.typ": * 4 | #import "./admonitions.typ": * 5 | #import "./label.typ": * 6 | #import "./team.typ": * 7 | #import "./pro-con.typ": * 8 | #import "./decision-matrix.typ": * 9 | #import "./tournament.typ": * 10 | #import "./graphs.typ": * 11 | -------------------------------------------------------------------------------- /themes/radial/components/decision-matrix.typ: -------------------------------------------------------------------------------- 1 | #import "../colors.typ": * 2 | #import "/utils.typ" 3 | 4 | #let decision-matrix = utils.make-decision-matrix((properties, data) => { 5 | set align(center) 6 | 7 | let winning-row 8 | for (index, choice) in data.values().enumerate() { 9 | if choice.total.highest { 10 | winning-row = index + 2 11 | } 12 | } 13 | 14 | table( 15 | stroke: none, 16 | columns: properties.len() + 2, 17 | fill: (_, row) => { 18 | if row == winning-row { green } 19 | else if calc.odd(row) { surface-3 } 20 | else if calc.even(row) { surface-1 } 21 | }, 22 | 23 | // Top line 24 | table.hline(stroke: (cap: "round", thickness: 2pt)), 25 | // Blank column to account for names of choices 26 | [], 27 | // Print out all the properties 28 | ..for property in properties { 29 | ([ *#property.name* ],) 30 | }, 31 | // Last box in the row 32 | [*Total*], 33 | // Print out the data for each choice 34 | 35 | ..for (index, choice) in data { 36 | ( 37 | [#index], 38 | ..for property in properties { 39 | let value = choice.at(property.name) 40 | ([#value.weighted],) 41 | }, 42 | [#choice.total.weighted] 43 | ) 44 | }, 45 | 46 | //..for result in data { 47 | // Override the fill if the choice has the highest score 48 | //let cell = if choice.values.total.highest { cellx.with(fill: green) } else { cellx } 49 | //(cell[*#choice.name*], ..for value in choice.values { 50 | //(cell[#value.at(1).value],) 51 | //}) 52 | //}, 53 | // Bottom line 54 | table.hline(stroke: (cap: "round", thickness: 2pt)), 55 | ) 56 | }) 57 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /themes/radial/components/glossary.typ: -------------------------------------------------------------------------------- 1 | #import "/utils.typ" 2 | 3 | #let glossary = utils.make-glossary(glossary => { 4 | columns(2)[ 5 | #for entry in glossary { 6 | box[ 7 | == #entry.word 8 | #entry.definition 9 | \ 10 | ] 11 | } 12 | ] 13 | }) 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /themes/radial/components/label.typ: -------------------------------------------------------------------------------- 1 | #import "../colors.typ": * 2 | #import "../metadata.typ": * 3 | #import "/utils.typ" 4 | 5 | /// A label that corresponds with one of the entry types. 6 | /// 7 | /// - type (string): Any of the radial entry types 8 | /// - size (size): The size of the label 9 | /// -> content 10 | #let label(type, size: 0.7em, radius: 1.5pt) = { 11 | let data = entry-type-metadata.at(type) 12 | let colored-image = utils.change-icon-color(raw-icon: data.icon, fill: white) 13 | 14 | box(fill: data.color, outset: 3pt, radius: radius)[ 15 | #set align(center + horizon) 16 | #image.decode(colored-image, height: size) 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /themes/radial/components/pro-con.typ: -------------------------------------------------------------------------------- 1 | #import "../colors.typ": * 2 | #import "/utils.typ" 3 | 4 | #let pro-con = utils.make-pro-con((pros, cons) => { 5 | let cell = rect.with( 6 | width: 100%, 7 | inset: 5pt, 8 | ) 9 | 10 | grid( 11 | columns: (1fr, 1fr), 12 | column-gutter: 4pt, 13 | cell( 14 | fill: green, 15 | radius: (top: 1.5pt), 16 | )[*Pros*], 17 | cell( 18 | fill: red, 19 | radius: (top: 1.5pt), 20 | )[*Cons*], 21 | cell( 22 | fill: green.lighten(80%), 23 | radius: (bottom: 1.5pt), 24 | pros, 25 | ), 26 | cell( 27 | fill: red.lighten(80%), 28 | radius: (bottom: 1.5pt), 29 | cons, 30 | ), 31 | ) 32 | }) 33 | -------------------------------------------------------------------------------- /themes/radial/components/team.typ: -------------------------------------------------------------------------------- 1 | #import "../colors.typ": * 2 | 3 | /// Display information about your team. 4 | /// 5 | /// Example Usage: 6 | /// ```typ 7 | /// #team( 8 | /// ( 9 | /// name: "Random Person", 10 | /// picture: image("./path-to-image.png", width: 90pt, height: 90pt), 11 | /// about: [ 12 | /// Likes Coding 13 | /// ], 14 | /// ), 15 | /// ) 16 | /// ``` 17 | /// - ..members (dictionary): A list of members in your team. Each dictionary must contain the following fields: 18 | /// - name ``: The name of the team member 19 | /// - picture ``: An image of the team member 20 | /// - about ``: About the team member 21 | /// -> content 22 | #let team(..members) = { 23 | set align(center) 24 | grid( 25 | columns: (1fr, 1fr), 26 | gutter: 20pt, 27 | ..for member in members.pos() { 28 | ( 29 | rect( 30 | fill: surface-1, 31 | inset: 20pt, 32 | radius: 1.5pt, 33 | )[ 34 | * #member.name * 35 | #line( 36 | length: 100%, 37 | stroke: (cap: "round", dash: "solid", thickness: 1.5pt), 38 | ) 39 | #v(8pt) 40 | #grid( 41 | columns: (1fr, 1fr), 42 | gutter: 20pt, 43 | align(center, member.picture), 44 | align(left, member.about), 45 | ) 46 | ], 47 | ) 48 | }, 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /themes/radial/components/title.typ: -------------------------------------------------------------------------------- 1 | #import "../colors.typ": * 2 | 3 | /// The title for an entry. This function is used internally by the theme, and not meant to be called by the user. 4 | /// 5 | /// - color (color): 6 | /// - beginning (content): 7 | /// - end (content): 8 | /// - body (content): 9 | /// -> content 10 | #let title(color: gray, beginning: none, end: none, body) = { 11 | let highlight(color: none, body, width: auto) = { 12 | box( 13 | fill: color, 14 | outset: 5pt, 15 | radius: 1.5pt, 16 | body, 17 | height: 1em, 18 | width: width, 19 | ) 20 | } 21 | 22 | set text(size: 18pt, weight: "bold") 23 | set align(horizon) 24 | 25 | if not beginning == none { 26 | highlight(color: color, beginning) 27 | h(15pt) 28 | } 29 | highlight(color: color.lighten(80%), width: 1fr)[ 30 | #body 31 | ] 32 | if not end == none { 33 | h(15pt) 34 | highlight(color: color.lighten(80%), end) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /themes/radial/components/toc.typ: -------------------------------------------------------------------------------- 1 | #import "/utils.typ" 2 | #import "../metadata.typ": * 3 | #import "./label.typ": * 4 | 5 | #let toc = utils.make-toc(( 6 | _, 7 | body, 8 | appendix, 9 | ) => { 10 | heading(level: 1)[Entries] 11 | 12 | stack( 13 | spacing: 1em, 14 | ..for entry in body { 15 | ( 16 | [ 17 | #entry.date.display("[year]/[month]/[day]") 18 | #h(5pt) 19 | #label(entry.type) 20 | #h(5pt) 21 | #entry.title 22 | #box( 23 | width: 1fr, 24 | line( 25 | length: 100%, 26 | stroke: ( 27 | dash: "dotted", 28 | ), 29 | ), 30 | ) 31 | #entry.page-number 32 | ], 33 | ) 34 | }, 35 | ) 36 | 37 | if (appendix.len() <= 0) { 38 | return 39 | } 40 | 41 | linebreak() 42 | 43 | heading(level: 1)[Appendix] 44 | 45 | for entry in appendix [ 46 | #entry.title 47 | #box( 48 | width: 1fr, 49 | line( 50 | length: 100%, 51 | stroke: ( 52 | dash: "dotted", 53 | ), 54 | ), 55 | ) 56 | #entry.page-number 57 | ] 58 | }) 59 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/radial/format.typ: -------------------------------------------------------------------------------- 1 | #import "/packages.typ": tablex 2 | #import tablex: * 3 | #import "./colors.typ": * 4 | 5 | #let table(it) = { 6 | tablex( 7 | columns: it.columns, 8 | auto-lines: false, 9 | inset: 10pt, 10 | fill: (_, row) => { 11 | if calc.odd(row) { 12 | surface-3 13 | } 14 | if calc.even(row) { 15 | surface-1 16 | } 17 | }, 18 | hlinex(stroke: (cap: "round", thickness: 2pt)), 19 | ..for child in it.children { 20 | ([#child],) 21 | }, 22 | hlinex(stroke: (cap: "round", thickness: 2pt)), 23 | ) 24 | } 25 | 26 | #let heading(it) = { 27 | set block(below: 1em) 28 | 29 | let content = if it.level == 1 { 30 | set text(size: 15pt) 31 | box(fill: surface-3, outset: 0.5em, radius: 1.5pt, it.body) 32 | } else if it.level == 2 { 33 | set text(size: 14pt) 34 | it.body 35 | } else { 36 | set text(size: 11pt) 37 | it.body 38 | } 39 | 40 | block(content) 41 | } 42 | 43 | #let raw-not-block = box.with( 44 | fill: surface-2, 45 | inset: (x: 3pt, y: 0pt), 46 | outset: (y: 3pt), 47 | radius: 2pt, 48 | ) 49 | 50 | #let raw-block(it) = { 51 | set par(justify: false) 52 | // the line counter 53 | let i = 0 54 | let box-radius = 1.5pt 55 | 56 | let detail-radius = 1.5pt 57 | if (it.lang != none) { 58 | grid( 59 | columns: (100%, 100%), 60 | column-gutter: (-100%), 61 | block( 62 | width: 100%, 63 | inset: 1em, 64 | { 65 | for line in it.text.split("\n") { 66 | box(width: 0pt, align(right, str(i + 1) + h(2em))) 67 | hide(line) 68 | linebreak() 69 | i = i + 1 70 | } 71 | }, 72 | ), 73 | block( 74 | radius: box-radius, 75 | fill: surface-1, 76 | width: 100%, 77 | inset: 1em, 78 | { 79 | place( 80 | top + right, 81 | box(fill: surface-3, radius: detail-radius, outset: 3pt, it.lang), 82 | ) 83 | it 84 | }, 85 | ), 86 | ) 87 | } else { 88 | block(radius: box-radius, fill: surface-2, width: 100%, inset: 1em, it) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /themes/radial/icons/bar-chart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/build.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/flask.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/function.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/hammer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/icons.typ: -------------------------------------------------------------------------------- 1 | // Contains the raw text data of each icon. The icons are not stored as content so their colors can be changed later. 2 | 3 | #let question-mark = read("./question-mark.svg") 4 | #let light-bulb = read("./light-bulb.svg") 5 | #let target = read("./target.svg") 6 | #let hammer = read("./hammer.svg") 7 | #let terminal = read("./terminal.svg") 8 | #let flask = read("./flask.svg") 9 | #let bar-chart = read("./bar-chart.svg") 10 | #let page = read("./page.svg") 11 | 12 | #let pencil = read("./pencil.svg") 13 | #let warning = read("./warning.svg") 14 | #let web = read("./web.svg") 15 | #let quotes = read("./quotes.svg") 16 | #let function = read("./function.svg") 17 | -------------------------------------------------------------------------------- /themes/radial/icons/info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/light-bulb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/page.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/pencil.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/question-mark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/quotes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/target.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/terminal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/web.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/icons/x.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/radial/metadata.typ: -------------------------------------------------------------------------------- 1 | #import "./icons/icons.typ" 2 | #import "./colors.typ": * 3 | 4 | // These should really be with their respective files, but that causes a cyclic import error, so I put them here. 5 | 6 | // @typstyle off 7 | #let entry-type-metadata = ( 8 | "identify": (icon: icons.question-mark, color: yellow), 9 | "brainstorm": (icon: icons.light-bulb, color: orange), 10 | "decide": (icon: icons.target, color: blue), 11 | "build": (icon: icons.hammer, color: red), 12 | "program": (icon: icons.terminal, color: purple), 13 | "test": (icon: icons.flask, color: green), 14 | "management": (icon: icons.bar-chart, color: surface-4), 15 | "notebook": (icon: icons.page, color: pink), 16 | ) 17 | 18 | // @typstyle off 19 | #let admonition-type-metadata = ( 20 | "note": (icon: icons.pencil, color: green, title: "Note"), 21 | "warning": (icon: icons.warning, color: red, title: "Warning"), 22 | "example": (icon: icons.web, color: purple, title: "Example"), 23 | "quote": (icon: icons.quotes, color: gray, title: "Quote"), 24 | "equation": (icon: icons.function, color: orange, title: "Equation"), 25 | "decision": (icon: icons.target, color: blue, title: "Final Decision"), 26 | "build": (icon: icons.hammer, color: red, title: "Build Complete"), 27 | ) 28 | -------------------------------------------------------------------------------- /themes/radial/radial.typ: -------------------------------------------------------------------------------- 1 | #import "./rules.typ": rules 2 | #import "./entries.typ": cover, frontmatter-entry, body-entry, appendix-entry 3 | #import "./components/components.typ" 4 | #import "./colors.typ" 5 | 6 | #let radial-theme = ( 7 | // Global show and set rules 8 | rules: rules, 9 | cover: cover, 10 | // Entry pages 11 | frontmatter-entry: frontmatter-entry, 12 | body-entry: body-entry, 13 | appendix-entry: appendix-entry, 14 | ) 15 | -------------------------------------------------------------------------------- /themes/radial/rules.typ: -------------------------------------------------------------------------------- 1 | #import "./format.typ" 2 | #import "./colors.typ": * 3 | #import "/utils.typ" 4 | 5 | #let rules = utils.make-rules(doc => { 6 | set text(font: ("Calibri", "Carlito"), size: 11pt) 7 | set page("us-letter") 8 | 9 | set footnote.entry(separator: none) 10 | 11 | // Enforce the correct font on Excalidraw drawings 12 | show image: it => [ 13 | #align(center)[ 14 | #set text(font: "Virgil 3 YOFF") 15 | #it 16 | ] 17 | ] 18 | 19 | show link: it => [ 20 | #text( 21 | fill: blue, 22 | [ _ #it _ ], 23 | ) 24 | ] 25 | 26 | show figure: it => align(center)[ 27 | #it.body 28 | 29 | #if it.caption != none [ 30 | _ #it.caption.body _ 31 | ] 32 | ] 33 | 34 | set raw(theme: "./radial.tmTheme") 35 | show raw.where(block: false): format.raw-not-block 36 | show raw.where(block: true): it => format.raw-block(it) 37 | 38 | show heading: format.heading 39 | //show table: format.table 40 | 41 | // Display the whole document 42 | doc 43 | }) 44 | -------------------------------------------------------------------------------- /themes/themes.typ: -------------------------------------------------------------------------------- 1 | #import "./default/default.typ" 2 | 3 | /* 4 | Radial Theme 5 | Designed by @BattleCh1cken 6 | Implemented by @BattleCh1cken 7 | */ 8 | 9 | #import "./radial/radial.typ" 10 | 11 | /* 12 | Linear Theme 13 | Designed by @BestUsernamEver 14 | Implemented by @BestUsernamEver 15 | */ 16 | #import "./linear/linear.typ" 17 | -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notebookinator" 3 | version = "1.0.1" 4 | entrypoint = "lib.typ" 5 | authors = ["BattleCh1cken"] 6 | license = "MIT" 7 | description = "Notebookin." 8 | compiler = "0.11.0" 9 | -------------------------------------------------------------------------------- /utils.typ: -------------------------------------------------------------------------------- 1 | #import "./utils/misc.typ": * 2 | #import "./utils/theme.typ": * 3 | #import "./utils/components.typ": * 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /utils/misc.typ: -------------------------------------------------------------------------------- 1 | /// Returns the raw image data, not image content 2 | /// You'll still need to run image.decode on the result 3 | /// 4 | /// - raw-icon (string): The raw data for the image. Must be svg data. 5 | /// - fill (color): The new icon color 6 | /// -> string 7 | #let change-icon-color( 8 | raw-icon: "", 9 | fill: red, 10 | ) = { 11 | return raw-icon.replace( 12 | " content 25 | #let colored-icon( 26 | path, 27 | fill: red, 28 | width: 100%, 29 | height: 100%, 30 | fit: "contain", 31 | ) = { 32 | let raw-icon = read(path) 33 | let raw-colored-icon = raw-icon.replace( 34 | " 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 | --------------------------------------------------------------------------------