@uniweb/unipress 0.4.22 → 0.4.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -27
- package/docs/templates/article.md +69 -0
- package/docs/troubleshooting.md +40 -3
- package/package.json +4 -4
- package/src/cli.js +4 -0
- package/src/commands/compile.js +101 -2
- package/src/compile.js +17 -0
- package/src/foundations-data.js +15 -2
- package/src/materialize.js +177 -0
- package/src/templates-data.js +10 -4
package/README.md
CHANGED
|
@@ -2,12 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
**Compile a directory of markdown into a document — typeset PDF, EPUB, Word, Excel, Paged.js HTML, Typst source — using a foundation that knows the conventions of the kind of document you're writing.**
|
|
4
4
|
|
|
5
|
+
You already write in markdown, and you keep it organized — a folder of files, numbered for order, maybe a `README` and some assets. That's good structure. But turning it into one polished, shareable document usually means pandoc incantations, a LaTeX toolchain, or a pile of glue scripts.
|
|
6
|
+
|
|
7
|
+
unipress does it in one command.
|
|
8
|
+
|
|
9
|
+
## Quick start: markdown you already have → a PDF
|
|
10
|
+
|
|
11
|
+
Point it at your folder:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd my-docs # a folder of .md files
|
|
15
|
+
unipress compile .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
There's no project to set up. The first time, unipress sees there's no config, offers to create a small `document.yml` for you, and produces the PDF — a clean, single-column **article**: A4, typeset prose, an inline title block, no chapters. Numbered filenames (`01-intro.md`, `02-overview.md`) set the section order; `README.md` and draft files (`_notes.md`) are left out.
|
|
19
|
+
|
|
20
|
+
Prefer no prompts? `--yes` takes the defaults — the A4 article, PDF output:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
unipress compile . --yes
|
|
24
|
+
unipress compile . --yes --format epub # or any format the foundation emits
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The generated `document.yml` is yours to keep and edit — it holds the title, reading order, and output format. Re-run compile whenever the content changes.
|
|
28
|
+
|
|
29
|
+
### A4 or US Letter?
|
|
30
|
+
|
|
31
|
+
The default page size is **A4**. In the US and Canada you'll want **Letter** — open the generated `document.yml` and change `book.trim` from `a4` to `letter`:
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
book:
|
|
35
|
+
kind: article
|
|
36
|
+
trim: letter # 8.5 × 11 in (was: a4)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then re-run `unipress compile .`. A couple of other one-line tweaks live in the same `book:` block — numbered sections (`structure: { numberHeadings: true }`) and a table of contents (`structure: { toc: true }`). See the [`article` guide](./docs/templates/article.md) for the full list.
|
|
40
|
+
|
|
41
|
+
## Starting from scratch
|
|
42
|
+
|
|
43
|
+
No content yet? Scaffold a starter from a template:
|
|
44
|
+
|
|
5
45
|
```bash
|
|
6
|
-
unipress create my-book
|
|
7
|
-
unipress compile
|
|
46
|
+
unipress create my-book --template book --title "My Book" --author "Your Name"
|
|
47
|
+
cd my-book && unipress compile .
|
|
8
48
|
```
|
|
9
49
|
|
|
10
|
-
|
|
50
|
+
`create` writes a content-only directory — markdown, a `document.yml`, optional `theme.yml` and `assets/`; no `package.json`, no `node_modules`. Pick the template that matches your document (see [Pick a template](#pick-a-template)) — it carries the typography and structure so a book reads like a book and a directory like a directory.
|
|
11
51
|
|
|
12
52
|
## What it makes
|
|
13
53
|
|
|
@@ -52,38 +92,22 @@ npm i -g @uniweb/unipress
|
|
|
52
92
|
|
|
53
93
|
## Pick a template
|
|
54
94
|
|
|
55
|
-
|
|
95
|
+
Built-in templates ship with the binary. Run `unipress list-templates` for the picker.
|
|
56
96
|
|
|
57
97
|
| Template | Foundation | Outputs | Use case |
|
|
58
98
|
|---|---|---|---|
|
|
59
|
-
| `
|
|
99
|
+
| `article` | `@uniweb/book` | pdf, typst, pagedjs, epub | Single-column paper (A4/Letter, no chapters) — the default for a loose folder of markdown |
|
|
100
|
+
| `book` | `@uniweb/book` | pdf, typst, pagedjs, epub | Trade book, long-form prose with chapters |
|
|
60
101
|
| `monograph` | `@uniweb/book` | pdf, typst, pagedjs, epub | Scholarly monograph (royal-octavo, classical typography) |
|
|
61
102
|
| `report` | `@uniweb/book` | pdf, typst, pagedjs, epub | Technical report (trade-7x10, block paragraphs) |
|
|
62
103
|
| `data-report` | `@uniweb/data` | xlsx, docx | Aggregate metrics across structured records |
|
|
63
104
|
| `directory` | `@uniweb/data` | xlsx, docx | Flat records listing with a filterable surface |
|
|
64
105
|
|
|
65
|
-
|
|
106
|
+
`article` and `book`/`monograph`/`report` are one foundation, two genres: `article` is single-column and chapter-less (LaTeX's `article`), the others are chaptered with a title page and TOC. Switch by setting `book.kind`.
|
|
66
107
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
## Write your first document
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
unipress create my-book --template book --title "My Book" --author "Your Name"
|
|
73
|
-
cd my-book
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
The result is a content-only directory — markdown pages, a `document.yml`, optional `theme.yml` and `assets/`. **No `package.json`, no `node_modules`.** Edit the markdown — that's your content. Numbered filenames (`01-intro.md`, `02-chapter-one.md`) keep chapter order predictable.
|
|
77
|
-
|
|
78
|
-
When you're ready to produce the document:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
unipress compile . --format pdf
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Or any of the formats the foundation declares (`--format epub`, `--format pagedjs`). Write, compile, look at the result, revise, compile again — that's the loop.
|
|
108
|
+
Per-template guides: [docs/templates/](./docs/templates/).
|
|
85
109
|
|
|
86
|
-
|
|
110
|
+
More templates land as more foundations ship — `cv`, `resume`, `thesis` are on the roadmap for upcoming releases.
|
|
87
111
|
|
|
88
112
|
## Custom foundations
|
|
89
113
|
|
|
@@ -119,6 +143,9 @@ unipress compile <dir> [options]
|
|
|
119
143
|
--config <path> Explicit config file (default: <dir>/unipress.config.js).
|
|
120
144
|
--typst-binary <p> Path to a typst binary (skips the managed download).
|
|
121
145
|
--keep-temp On typst-compile failure, keep the temp dir for inspection.
|
|
146
|
+
--yes If no document.yml exists, generate one from the folder's
|
|
147
|
+
markdown without prompting (book foundation by default;
|
|
148
|
+
honors --foundation/--format).
|
|
122
149
|
--verbose Per-step progress to stderr + stack traces on errors.
|
|
123
150
|
|
|
124
151
|
unipress create <dir> [options]
|
|
@@ -159,7 +186,8 @@ The content-directory-level config. Fields unipress reads:
|
|
|
159
186
|
| `name` | Document name (used as a title fallback). |
|
|
160
187
|
| `foundation` | Registry ref (`@ns/name@ver`), URL, or local path to the foundation. |
|
|
161
188
|
| `format` | Default output format. Overridable by CLI `--format` or `unipress.config.js`. |
|
|
162
|
-
| `
|
|
189
|
+
| `content:` | Reading order — chapter names with any numeric prefix stripped (`00-intro.md` → `intro`). `pages:` is accepted as an alias. |
|
|
190
|
+
| `paths:` | Content-directory overrides. `paths: { pages: . }` reads markdown loose at the project root instead of a `content/` subfolder. |
|
|
163
191
|
| `book:`, `report:`, `collections:` … | Foundation-specific config blocks. The foundation's `getOptions` reads these. |
|
|
164
192
|
|
|
165
193
|
`site.yml` is also accepted as a fallback for compatibility with existing Uniweb site directories.
|
|
@@ -206,7 +234,7 @@ Applied per-field.
|
|
|
206
234
|
|
|
207
235
|
unipress is **pre-1.0**. The CLI is stable enough to write real documents with — the `book` template, in particular, is well-tested. Future versions may change small things, but `document.yml` files and project folders from today should keep working: scaffolded projects pin a specific foundation version, and registry artifacts are immutable.
|
|
208
236
|
|
|
209
|
-
**
|
|
237
|
+
**Foundations are fetched on first use.** The bundled templates pin foundations served from `https://uniweb.github.io/unipress/foundations/…`; the first compile downloads and caches the one your `document.yml` names (alongside the Typst binary). To iterate on a foundation locally, pass `--foundation <path>` pointing at a built foundation directory; to point at a different registry, set `UNIWEB_REGISTRY_URL`.
|
|
210
238
|
|
|
211
239
|
## Troubleshooting
|
|
212
240
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# `article`
|
|
2
|
+
|
|
3
|
+
A single-column paper — A4 (default) or US Letter, continuous prose, no chapters. Clean typography, symmetric margins tuned for a comfortable ~75-character measure, an inline title block (no separate title page), and unnumbered section headings. It's the natural shape for turning a folder of notes, a report, a proposal, or an essay into one shareable PDF — and the default when you run `unipress compile .` on a loose folder of markdown.
|
|
4
|
+
|
|
5
|
+
The same `@uniweb/book` foundation drives `article`, `book`, `monograph`, and `report`. The difference is the genre: `article` sets `book.kind: article`, which renders continuous prose instead of chapters.
|
|
6
|
+
|
|
7
|
+
## Scaffold
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
unipress create my-paper --template article --title "On Systems" --author "Your Name"
|
|
11
|
+
cd my-paper
|
|
12
|
+
unipress compile . --format pdf --out my-paper.pdf
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or skip the scaffold entirely — point unipress at any folder of markdown and let it generate the config:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd my-notes # a folder of .md files
|
|
19
|
+
unipress compile . --yes
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## What you get
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
my-paper/
|
|
26
|
+
├── document.yml pinned to @uniweb/book@<version>; book.kind: article, trim: a4
|
|
27
|
+
├── content/
|
|
28
|
+
│ ├── 01-introduction.md
|
|
29
|
+
│ └── 02-main.md
|
|
30
|
+
└── README.md
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Each file in `content/` is a section, ordered by its number prefix. The file's `# H1` is the section heading; `##` / `###` are subsections. Sections flow continuously — no page break before each one.
|
|
34
|
+
|
|
35
|
+
## When to pick `article` over `book`
|
|
36
|
+
|
|
37
|
+
- The writing is continuous prose, not divided into chapters.
|
|
38
|
+
- You want a full page of paper (A4 / Letter), not a small book trim.
|
|
39
|
+
- A title at the top is enough — no separate title page, copyright page, or cover.
|
|
40
|
+
- It's a report, proposal, essay, set of notes, or design doc rather than a book.
|
|
41
|
+
|
|
42
|
+
For chaptered, bindable, prose-driven work, use `book` (trade-6×9) or `monograph` (royal-octavo). For data-driven Excel/Word output, use `data-report` or `directory`.
|
|
43
|
+
|
|
44
|
+
## `document.yml` fields
|
|
45
|
+
|
|
46
|
+
Same `book:` shape as the other genres (see [book.md](./book.md)), with these article-specific defaults and knobs:
|
|
47
|
+
|
|
48
|
+
| Field | `article` default / note |
|
|
49
|
+
|-----------------------------|-----------------------------------------------------------------|
|
|
50
|
+
| `book.kind` | `article` — selects the single-column genre |
|
|
51
|
+
| `book.trim` | `a4` (or `letter` for US/Canada) |
|
|
52
|
+
| `book.structure.toc` | `false` — a paper has no TOC by default; set `true` for a longer one |
|
|
53
|
+
| `book.structure.numberHeadings` | `false` — set `true` for numbered sections (`1`, `1.1`, `1.1.1`) |
|
|
54
|
+
| `book.title` / `subtitle` / `author` | render as a centered title block atop page 1; clear the title to omit the block entirely |
|
|
55
|
+
|
|
56
|
+
Cover artwork, title pages, copyright pages, and chapter numbering don't apply to the article genre.
|
|
57
|
+
|
|
58
|
+
## Common customizations
|
|
59
|
+
|
|
60
|
+
- **US Letter instead of A4**: `book.trim: letter`.
|
|
61
|
+
- **Numbered sections**: `book.structure: { numberHeadings: true }` → `1`, `1.1`, `1.1.1`.
|
|
62
|
+
- **Add a table of contents**: `book.structure: { toc: true, tocDepth: 2 }`.
|
|
63
|
+
- **Different body font**: `book.typography.bodyFont: ["EB Garamond", "Georgia"]`.
|
|
64
|
+
- **Tighter or looser measure**: override `book.trim` with an object — `{ preset: a4, margins: { inside: 40mm, outside: 40mm } }` — for narrower text.
|
|
65
|
+
- **Grow into a book**: remove `book.kind` (and set a book trim like `trade-6x9`), or start fresh with `unipress create --template book`.
|
|
66
|
+
|
|
67
|
+
## Foundation reference
|
|
68
|
+
|
|
69
|
+
`@uniweb/book` — see the foundation's README: `foundations/book/README.md`.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -25,16 +25,53 @@ The positional argument to `unipress compile` is wrong. Check your `cd` and the
|
|
|
25
25
|
|
|
26
26
|
## `DocumentYmlError: no document.yml (or site.yml) found in <path>`
|
|
27
27
|
|
|
28
|
-
unipress expects a top-level config file inside the content directory.
|
|
28
|
+
unipress expects a top-level config file inside the content directory.
|
|
29
|
+
|
|
30
|
+
If the folder already holds markdown, you don't have to write one by hand —
|
|
31
|
+
`compile` offers to generate it. Running interactively, it prompts:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
$ unipress compile .
|
|
35
|
+
No document.yml here. Generate one (A4 article, pdf) from the 12 markdown files at the project root and compile? (Y/n)
|
|
36
|
+
Document title › My Document
|
|
37
|
+
created /path/to/document.yml
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
By default this produces a clean single-column **article** (A4, no chapters).
|
|
41
|
+
Pass `--yes` to skip the prompt (handy in scripts or non-interactive shells):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
unipress compile . --yes # generate + compile → A4 article PDF
|
|
45
|
+
unipress compile . --yes --foundation @uniweb/data@0.1.0 --format docx
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The generated `document.yml` pins a foundation, sets the format, lists the
|
|
49
|
+
sections in reading order, and — when the markdown sits loose at the project
|
|
50
|
+
root rather than under `content/` — points the content directory at the root
|
|
51
|
+
with `paths: { pages: . }`. It's an ordinary config file; edit it freely.
|
|
52
|
+
|
|
53
|
+
To write one by hand instead:
|
|
29
54
|
|
|
30
55
|
```yaml
|
|
31
|
-
foundation: "@uniweb/book@0.1
|
|
56
|
+
foundation: "@uniweb/book@0.4.1"
|
|
32
57
|
format: pdf
|
|
33
|
-
|
|
58
|
+
paths:
|
|
59
|
+
pages: . # only needed when sections live at the root (no content/ folder)
|
|
60
|
+
book:
|
|
61
|
+
kind: article # single-column paper; omit for a chaptered book
|
|
62
|
+
trim: a4 # or: letter
|
|
63
|
+
content: [introduction, main]
|
|
34
64
|
```
|
|
35
65
|
|
|
36
66
|
`site.yml` is accepted as a fallback if you're dogfooding an existing Uniweb site.
|
|
37
67
|
|
|
68
|
+
## `DocumentYmlError: <config> has no pages — nothing to compile`
|
|
69
|
+
|
|
70
|
+
A config file exists, but the collector found no chapters. The usual cause is
|
|
71
|
+
markdown living somewhere the document profile didn't scan — loose at the
|
|
72
|
+
project root while the profile looks under `content/`. The error names the fix;
|
|
73
|
+
for root-level files, add `paths: { pages: . }` to your `document.yml`.
|
|
74
|
+
|
|
38
75
|
## `DocumentYmlError: malformed YAML in document.yml at <file>:<line>:<col>`
|
|
39
76
|
|
|
40
77
|
Fix the YAML. Common causes: unquoted strings containing `:`, unclosed brackets, inconsistent indentation, tabs mixed with spaces.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/unipress",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.24",
|
|
4
4
|
"description": "Compile a content directory into a document (PDF, EPUB, Paged.js HTML, Typst source bundle, DOCX, XLSX) using a Uniweb foundation. Five built-in templates: book, monograph, report, data-report, directory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"prompts": "^2.4.2",
|
|
50
50
|
"react": "^19.0.0",
|
|
51
51
|
"react-dom": "^19.0.0",
|
|
52
|
+
"@uniweb/core": "0.7.14",
|
|
53
|
+
"@uniweb/build": "0.14.17",
|
|
52
54
|
"@uniweb/content-reader": "1.1.12",
|
|
53
55
|
"@uniweb/semantic-parser": "1.1.17",
|
|
54
|
-
"@uniweb/
|
|
55
|
-
"@uniweb/core": "0.7.14",
|
|
56
|
-
"@uniweb/runtime": "0.8.19"
|
|
56
|
+
"@uniweb/runtime": "0.8.20"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"vitest": "^4.1.7"
|
package/src/cli.js
CHANGED
|
@@ -28,6 +28,9 @@ Commands:
|
|
|
28
28
|
--config <path> explicit config file (default: <dir>/unipress.config.js)
|
|
29
29
|
--typst-binary <p> path to a typst binary (skips managed download)
|
|
30
30
|
--keep-temp keep the typst temp dir on failure (for debugging)
|
|
31
|
+
--yes if no document.yml exists, generate one from the
|
|
32
|
+
folder's markdown without prompting (defaults to
|
|
33
|
+
the book foundation; honors --foundation/--format)
|
|
31
34
|
create <dir> Scaffold a new unipress project from a template
|
|
32
35
|
--template <id> template to use (interactive picker if omitted)
|
|
33
36
|
--title <str> document title (prompts if omitted)
|
|
@@ -130,6 +133,7 @@ async function main(argv) {
|
|
|
130
133
|
config: values.config ?? null,
|
|
131
134
|
typstBinary: values['typst-binary'] ?? null,
|
|
132
135
|
keepTemp: !!values['keep-temp'],
|
|
136
|
+
yes: !!values.yes,
|
|
133
137
|
verbose: !!values.verbose
|
|
134
138
|
})
|
|
135
139
|
break
|
package/src/commands/compile.js
CHANGED
|
@@ -2,16 +2,115 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Writes verbose progress to stderr so stdout stays clean for the
|
|
4
4
|
// single success summary line.
|
|
5
|
+
//
|
|
6
|
+
// Before compiling, if the directory has no document.yml/site.yml but
|
|
7
|
+
// does hold markdown, offer to generate a document.yml (the
|
|
8
|
+
// offer-to-materialize flow). The generation itself lives in
|
|
9
|
+
// src/materialize.js; the prompt is here because it's a CLI concern —
|
|
10
|
+
// the library entry (src/compile.js) stays non-interactive.
|
|
5
11
|
|
|
12
|
+
import { existsSync } from 'node:fs'
|
|
13
|
+
import { basename, resolve } from 'node:path'
|
|
14
|
+
import prompts from 'prompts'
|
|
6
15
|
import { compile } from '../compile.js'
|
|
16
|
+
import { detectConfigFile, CONFIG_FILE_NAMES } from '../document-yml.js'
|
|
17
|
+
import { detectBareContent, materializeDocumentYml } from '../materialize.js'
|
|
18
|
+
import { findCatalogEntry } from '../catalog.js'
|
|
19
|
+
|
|
20
|
+
// Bare-folder default: the book foundation's `article` genre — a clean
|
|
21
|
+
// single-column A4 paper, the natural shape for a loose folder of markdown.
|
|
22
|
+
const DEFAULT_FOUNDATION =
|
|
23
|
+
findCatalogEntry('article')?.foundation?.ref ?? '@uniweb/book@0.4.1'
|
|
24
|
+
|
|
25
|
+
// "durable-structured-systems" → "Durable Structured Systems"
|
|
26
|
+
function titleize(slug) {
|
|
27
|
+
return slug
|
|
28
|
+
.replace(/[-_]+/g, ' ')
|
|
29
|
+
.replace(/\s+/g, ' ')
|
|
30
|
+
.trim()
|
|
31
|
+
.split(' ')
|
|
32
|
+
.map((w) => (w ? w[0].toUpperCase() + w.slice(1) : w))
|
|
33
|
+
.join(' ')
|
|
34
|
+
}
|
|
7
35
|
|
|
8
|
-
|
|
36
|
+
// When there's no config but markdown is present, offer to write a
|
|
37
|
+
// document.yml and continue. Returns true if a config now exists (so
|
|
38
|
+
// compile should proceed), false if the user declined.
|
|
39
|
+
async function offerToMaterialize({ sitePath, format, foundation, yes }) {
|
|
40
|
+
const bare = detectBareContent(sitePath)
|
|
41
|
+
// No markdown anywhere — let compile() throw its normal, helpful
|
|
42
|
+
// "no document.yml" error rather than inventing one.
|
|
43
|
+
if (!bare) return true
|
|
44
|
+
|
|
45
|
+
const foundationRef = foundation ?? DEFAULT_FOUNDATION
|
|
46
|
+
const fmt = format ?? 'pdf'
|
|
47
|
+
const defaultTitle = titleize(basename(sitePath)) || 'Untitled document'
|
|
48
|
+
const n = bare.chapters.length
|
|
49
|
+
const filesLabel = `${n} markdown file${n === 1 ? '' : 's'}`
|
|
50
|
+
const where = bare.contentDir === '.' ? 'at the project root' : `in ${bare.contentDir}/`
|
|
51
|
+
// The default book foundation materializes the single-column article
|
|
52
|
+
// genre; a custom --foundation is named as-is.
|
|
53
|
+
const genreLabel = foundationRef === DEFAULT_FOUNDATION ? 'A4 article' : foundationRef
|
|
54
|
+
let title = defaultTitle
|
|
55
|
+
|
|
56
|
+
if (!yes) {
|
|
57
|
+
// Don't hang on a prompt (or silently create files) without a TTY.
|
|
58
|
+
// Nudge toward --yes and let compile() throw the normal error.
|
|
59
|
+
if (!process.stdin.isTTY) {
|
|
60
|
+
process.stderr.write(
|
|
61
|
+
`note: no ${CONFIG_FILE_NAMES.PRIMARY} found, but ${n} markdown file(s) are ${where}.\n` +
|
|
62
|
+
` re-run with --yes to generate one, or add ${CONFIG_FILE_NAMES.PRIMARY} by hand.\n`
|
|
63
|
+
)
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
const answers = await prompts([
|
|
67
|
+
{
|
|
68
|
+
type: 'confirm',
|
|
69
|
+
name: 'create',
|
|
70
|
+
message:
|
|
71
|
+
`No ${CONFIG_FILE_NAMES.PRIMARY} here. Generate one ` +
|
|
72
|
+
`(${genreLabel}, ${fmt}) from the ${filesLabel} ${where} and compile?`,
|
|
73
|
+
initial: true
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: (prev) => (prev ? 'text' : null),
|
|
77
|
+
name: 'title',
|
|
78
|
+
message: 'Document title',
|
|
79
|
+
initial: defaultTitle
|
|
80
|
+
}
|
|
81
|
+
])
|
|
82
|
+
if (!answers.create) {
|
|
83
|
+
process.stderr.write(`aborted — no ${CONFIG_FILE_NAMES.PRIMARY} created.\n`)
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
title = answers.title || defaultTitle
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const { path } = materializeDocumentYml({
|
|
90
|
+
sitePath,
|
|
91
|
+
name: title,
|
|
92
|
+
foundationRef,
|
|
93
|
+
format: fmt,
|
|
94
|
+
contentDir: bare.contentDir,
|
|
95
|
+
chapters: bare.chapters
|
|
96
|
+
})
|
|
97
|
+
process.stderr.write(`created ${path}\n`)
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function compileCommand({ dir, format = null, foundation = null, out = null, config = null, typstBinary = null, keepTemp = false, yes = false, verbose = false } = {}) {
|
|
9
102
|
if (!dir) {
|
|
10
103
|
process.stderr.write('error: `compile` requires a directory argument\n')
|
|
11
|
-
process.stderr.write('usage: unipress compile <dir> [--format <fmt>] [--foundation <ref>] [--out <path>] [--config <path>] [--typst-binary <path>] [--keep-temp] [--verbose]\n')
|
|
104
|
+
process.stderr.write('usage: unipress compile <dir> [--format <fmt>] [--foundation <ref>] [--out <path>] [--config <path>] [--typst-binary <path>] [--keep-temp] [--yes] [--verbose]\n')
|
|
12
105
|
process.exit(1)
|
|
13
106
|
}
|
|
14
107
|
|
|
108
|
+
const sitePath = resolve(dir)
|
|
109
|
+
if (existsSync(sitePath) && !detectConfigFile(sitePath)) {
|
|
110
|
+
const proceed = await offerToMaterialize({ sitePath, format, foundation, yes })
|
|
111
|
+
if (!proceed) process.exit(1)
|
|
112
|
+
}
|
|
113
|
+
|
|
15
114
|
const onProgress = verbose
|
|
16
115
|
? (msg) => process.stderr.write(`[compile] ${msg}\n`)
|
|
17
116
|
: () => {}
|
package/src/compile.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
import { basename, resolve } from 'node:path'
|
|
29
29
|
import { loadContent } from './content-loader.js'
|
|
30
|
+
import { detectBareContent } from './materialize.js'
|
|
30
31
|
import { resolveFoundation } from './foundation-loader.js'
|
|
31
32
|
import { loadAndInit, compileDocumentWithFoundation, getFoundationOutputs } from './orchestrator.js'
|
|
32
33
|
import { loadUnipressConfig } from './config.js'
|
|
@@ -57,6 +58,22 @@ export async function compile({
|
|
|
57
58
|
const { content, sitePath, configFile } = await loadContent(dir)
|
|
58
59
|
onProgress(` ${content.pages.length} page(s) from ${sitePath} (${configFile})`)
|
|
59
60
|
|
|
61
|
+
// A config exists but nothing was collected — almost always the markdown
|
|
62
|
+
// sits in a directory the profile didn't look in (e.g. loose at the root
|
|
63
|
+
// while the document profile scans content/). Fail loudly with the fix
|
|
64
|
+
// rather than silently emitting an empty document.
|
|
65
|
+
if (content.pages.length === 0) {
|
|
66
|
+
const bare = detectBareContent(sitePath)
|
|
67
|
+
const hint = bare
|
|
68
|
+
? bare.contentDir === '.'
|
|
69
|
+
? `found ${bare.chapters.length} markdown file(s) at the project root — add 'paths: { pages: . }' to ${configFile}`
|
|
70
|
+
: `found markdown in ${bare.contentDir}/ — add 'paths: { pages: ${bare.contentDir} }' to ${configFile}`
|
|
71
|
+
: `no markdown files found — check the content directory and ${configFile}`
|
|
72
|
+
throw new DocumentYmlError(
|
|
73
|
+
`${configFile} has no pages — nothing to compile.\nhint: ${hint}`
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
60
77
|
// Load unipress.config.js — from --config, then <dir>/unipress.config.js,
|
|
61
78
|
// else empty. Relative paths inside the config are resolved against the
|
|
62
79
|
// config file's own directory (see src/config.js).
|
package/src/foundations-data.js
CHANGED
|
@@ -72,8 +72,8 @@ function publicUrl(name, version) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const BOOK_FOUNDATION = {
|
|
75
|
-
ref: '@uniweb/book@0.
|
|
76
|
-
source: { url: publicUrl('book', '0.
|
|
75
|
+
ref: '@uniweb/book@0.4.1',
|
|
76
|
+
source: { url: publicUrl('book', '0.4.1') },
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
const DATA_FOUNDATION = {
|
|
@@ -87,6 +87,19 @@ const BUSINESS_DOCS_FOUNDATION = {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export const FOUNDATIONS = [
|
|
90
|
+
{
|
|
91
|
+
id: 'article',
|
|
92
|
+
name: 'Article',
|
|
93
|
+
description:
|
|
94
|
+
'Single-column paper — A4 or US Letter, continuous prose, no ' +
|
|
95
|
+
'chapters. Clean typography, an inline title block, comfortable ' +
|
|
96
|
+
'margins. The natural shape for turning a folder of notes or docs ' +
|
|
97
|
+
'into one shareable PDF. Compiles to PDF (Typst), Typst source ' +
|
|
98
|
+
'bundle, Paged.js HTML, or EPUB.',
|
|
99
|
+
outputs: ['pdf', 'typst', 'pagedjs', 'epub'],
|
|
100
|
+
foundation: BOOK_FOUNDATION,
|
|
101
|
+
scaffold: 'article',
|
|
102
|
+
},
|
|
90
103
|
{
|
|
91
104
|
id: 'book',
|
|
92
105
|
name: 'Book',
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Materialize a `document.yml` for a bare folder of markdown.
|
|
2
|
+
//
|
|
3
|
+
// `unipress compile <dir>` requires a top-level config file (document.yml
|
|
4
|
+
// or site.yml). When a user points it at a plain folder of markdown — no
|
|
5
|
+
// config, content possibly loose at the root rather than under content/ —
|
|
6
|
+
// the CLI offers to generate a document.yml here, then compiles. This
|
|
7
|
+
// module owns the detection + file generation; the interactive offer
|
|
8
|
+
// itself lives in commands/compile.js (a CLI concern).
|
|
9
|
+
//
|
|
10
|
+
// Chapter discovery mirrors @uniweb/build's content-collector so the
|
|
11
|
+
// generated `content:` reading order matches what the collector will
|
|
12
|
+
// actually discover at compile time. The collector stays authoritative —
|
|
13
|
+
// the list we emit is a convenience manifest (and the trailing `...`
|
|
14
|
+
// keeps it forgiving), so faithful-but-not-identical is fine. The rules
|
|
15
|
+
// copied here: skip non-.md, `_`-prefixed (drafts), `@`-prefixed (nested
|
|
16
|
+
// child sections), and README.md; sort by numeric prefix; strip the
|
|
17
|
+
// numeric prefix to form the stable name used for ordering. Source of
|
|
18
|
+
// truth: framework/build/src/site/content-collector.js
|
|
19
|
+
// (isMarkdownFile / isChildSection / parseNumericPrefix / compareFilenames).
|
|
20
|
+
|
|
21
|
+
import { existsSync, readdirSync, statSync, writeFileSync } from 'node:fs'
|
|
22
|
+
import { join, parse } from 'node:path'
|
|
23
|
+
import { CONFIG_FILE_NAMES } from './document-yml.js'
|
|
24
|
+
|
|
25
|
+
// A markdown file that becomes a chapter/page (not a draft, nested child,
|
|
26
|
+
// or repo README).
|
|
27
|
+
function isChapterFile(filename) {
|
|
28
|
+
if (!filename.endsWith('.md')) return false
|
|
29
|
+
if (filename.startsWith('_')) return false
|
|
30
|
+
if (filename.startsWith('@')) return false
|
|
31
|
+
if (filename.toLowerCase() === 'readme.md') return false
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseNumericPrefix(name) {
|
|
36
|
+
const match = name.match(/^(\d+(?:\.\d+)*)-?(.*)$/)
|
|
37
|
+
if (match) return { prefix: match[1], name: match[2] || match[1] }
|
|
38
|
+
return { prefix: null, name }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Sort by numeric prefix (dots are sub-levels: 1, 1.5, 2), unprefixed
|
|
42
|
+
// names last in locale order.
|
|
43
|
+
function compareFilenames(a, b) {
|
|
44
|
+
const { prefix: pa } = parseNumericPrefix(parse(a).name)
|
|
45
|
+
const { prefix: pb } = parseNumericPrefix(parse(b).name)
|
|
46
|
+
if (!pa && !pb) return a.localeCompare(b)
|
|
47
|
+
if (!pa) return 1
|
|
48
|
+
if (!pb) return -1
|
|
49
|
+
const partsA = pa.split('.').map(Number)
|
|
50
|
+
const partsB = pb.split('.').map(Number)
|
|
51
|
+
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
52
|
+
const numA = partsA[i] ?? 0
|
|
53
|
+
const numB = partsB[i] ?? 0
|
|
54
|
+
if (numA !== numB) return numA - numB
|
|
55
|
+
}
|
|
56
|
+
return 0
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function stableName(fileBase) {
|
|
60
|
+
return parseNumericPrefix(fileBase).name
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Ordered stable chapter names for a directory (empty if none / unreadable).
|
|
64
|
+
function listChapters(dirPath) {
|
|
65
|
+
let entries
|
|
66
|
+
try {
|
|
67
|
+
entries = readdirSync(dirPath)
|
|
68
|
+
} catch {
|
|
69
|
+
return []
|
|
70
|
+
}
|
|
71
|
+
return entries
|
|
72
|
+
.filter(isChapterFile)
|
|
73
|
+
.sort(compareFilenames)
|
|
74
|
+
.map((f) => stableName(parse(f).name))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Find where a bare folder keeps its markdown.
|
|
79
|
+
*
|
|
80
|
+
* Prefers an existing content/ then pages/ subfolder (the document.yml
|
|
81
|
+
* profile default + its legacy fallback), else loose markdown at the
|
|
82
|
+
* root. Returns the directory to point the collector at and the ordered
|
|
83
|
+
* chapter names, or null when no markdown is found anywhere.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} sitePath - Absolute path to the content directory.
|
|
86
|
+
* @returns {{ contentDir: '.' | 'content' | 'pages', chapters: string[] } | null}
|
|
87
|
+
*/
|
|
88
|
+
export function detectBareContent(sitePath) {
|
|
89
|
+
for (const dir of ['content', 'pages']) {
|
|
90
|
+
const p = join(sitePath, dir)
|
|
91
|
+
if (existsSync(p) && statSync(p).isDirectory()) {
|
|
92
|
+
const chapters = listChapters(p)
|
|
93
|
+
if (chapters.length) return { contentDir: dir, chapters }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const rootChapters = listChapters(sitePath)
|
|
97
|
+
if (rootChapters.length) return { contentDir: '.', chapters: rootChapters }
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Build a document.yml as a commented YAML string.
|
|
103
|
+
*
|
|
104
|
+
* Emits `paths: { pages: . }` only for root-level content; the book block
|
|
105
|
+
* only for the book foundation. Comments explain the reading order and how
|
|
106
|
+
* to edit, matching the style of the bundled templates' document.yml.
|
|
107
|
+
*
|
|
108
|
+
* @returns {string}
|
|
109
|
+
*/
|
|
110
|
+
export function buildDocumentYml({
|
|
111
|
+
name,
|
|
112
|
+
foundationRef,
|
|
113
|
+
format,
|
|
114
|
+
contentDir,
|
|
115
|
+
chapters = [],
|
|
116
|
+
author = ''
|
|
117
|
+
}) {
|
|
118
|
+
const q = (s) => JSON.stringify(String(s ?? ''))
|
|
119
|
+
const isBook = String(foundationRef).startsWith('@uniweb/book')
|
|
120
|
+
const lines = [
|
|
121
|
+
'# Generated by `unipress compile`. Edit freely — this is an ordinary',
|
|
122
|
+
'# unipress document config. Docs: https://github.com/uniweb/unipress',
|
|
123
|
+
'',
|
|
124
|
+
`name: ${q(name)}`,
|
|
125
|
+
`foundation: ${q(foundationRef)}`,
|
|
126
|
+
`format: ${format}`
|
|
127
|
+
]
|
|
128
|
+
if (contentDir === '.') {
|
|
129
|
+
lines.push(
|
|
130
|
+
'',
|
|
131
|
+
'# Your markdown lives at the project root (no content/ or pages/',
|
|
132
|
+
'# subfolder), so point the content directory at the root.',
|
|
133
|
+
'paths:',
|
|
134
|
+
' pages: .'
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
if (isBook) {
|
|
138
|
+
lines.push(
|
|
139
|
+
'',
|
|
140
|
+
'book:',
|
|
141
|
+
' # Single-column A4 paper (the article genre). Set trim: letter for',
|
|
142
|
+
' # US/Canada, or remove `kind` (and pick a book trim) for a chaptered',
|
|
143
|
+
' # book — or run `unipress create --template book`.',
|
|
144
|
+
' kind: article',
|
|
145
|
+
' trim: a4',
|
|
146
|
+
` title: ${q(name)}`,
|
|
147
|
+
` author: ${q(author)}`,
|
|
148
|
+
' language: en'
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
if (chapters.length) {
|
|
152
|
+
lines.push(
|
|
153
|
+
'',
|
|
154
|
+
'# Reading order. Each name is a section filename with any leading',
|
|
155
|
+
'# "NN-" number prefix removed (00-intro.md → intro). Reorder these',
|
|
156
|
+
'# lines to reorder the document; `...` includes anything you add',
|
|
157
|
+
'# later. README.md is skipped automatically.',
|
|
158
|
+
'content:'
|
|
159
|
+
)
|
|
160
|
+
for (const c of chapters) lines.push(` - ${c}`)
|
|
161
|
+
lines.push(' - ...')
|
|
162
|
+
}
|
|
163
|
+
lines.push('')
|
|
164
|
+
return lines.join('\n')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Write a generated document.yml into sitePath.
|
|
169
|
+
*
|
|
170
|
+
* @returns {{ path: string, content: string }}
|
|
171
|
+
*/
|
|
172
|
+
export function materializeDocumentYml({ sitePath, ...opts }) {
|
|
173
|
+
const content = buildDocumentYml(opts)
|
|
174
|
+
const path = join(sitePath, CONFIG_FILE_NAMES.PRIMARY)
|
|
175
|
+
writeFileSync(path, content, 'utf8')
|
|
176
|
+
return { path, content }
|
|
177
|
+
}
|
package/src/templates-data.js
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
// Do not edit by hand — edit files under documents/ and regenerate.
|
|
3
3
|
|
|
4
4
|
export const TEMPLATES = {
|
|
5
|
+
"article": {
|
|
6
|
+
"README.md": "# `article` template\n\nA single-column paper — A4 or US Letter, continuous prose, no chapters.\nClean typography, an inline title block, comfortable margins. The natural\nshape for turning a folder of notes or docs into one shareable PDF. Pinned\nto `@uniweb/book` (the `article` genre of the book foundation).\n\n```bash\nunipress compile . --format pdf\nunipress compile . --format epub # or pagedjs, or the typst source bundle\n```\n\n## What's here\n\n```\narticle/\n├── document.yml pinned to @uniweb/book; book.kind: article, trim: a4\n└── content/\n ├── 01-introduction.md\n └── 02-main.md\n```\n\nEach file in `content/` is a section, ordered by the number prefix. Its\n`# H1` is the section heading; `##`/`###` are subsections. Add a section by\ndropping in `content/03-<name>.md` and listing its base name under\n`content:` in `document.yml`.\n\n## Customize\n\n### Page size\n\n`book.trim` is `a4` by default; set it to `letter` for US/Canada. Both ship\nas presets — it's a one-word change.\n\n### Title block\n\n`book.title`, `book.subtitle`, and `book.author` render as a centered block\nat the top of the first page. Leave a field blank to omit that line; clear\nthe title entirely for a paper with no title block at all.\n\n### Table of contents\n\nOff by default for an article. For a longer paper, turn it on:\n\n```yaml\nbook:\n structure:\n toc: true\n tocDepth: 2\n```\n\n### When to pick `book` instead\n\nIf your writing has chapters — and wants a title page, copyright page, and\na smaller bindable trim — use the `book` template (or `monograph` /\n`report`). Same foundation; the genre is the difference.\n",
|
|
7
|
+
"content/01-introduction.md": "# Introduction\n\nYou're looking at an article — a single-column document on a full page of\npaper (A4 by default, or US Letter), typeset for comfortable reading. No\nchapters, no cover, no separate title page: just your title at the top and\nyour writing flowing underneath.\n\nThis is the natural shape for most things people write in markdown — notes,\na report, a proposal, a design doc, an essay. You keep your content as\nordinary markdown files in `content/`; the number at the front of each\nfilename sets the order (`01-introduction.md` comes before `02-main.md`).\nEach file becomes a section, and its `# H1` becomes that section's heading.\n\nEverything you'd expect from markdown just works: **bold** and *italic*,\n[links](https://uniweb.io), lists, tables, blockquotes, footnotes, fenced\ncode blocks, and math. You don't choose the typography — the margins, the\nmeasure, the way headings sit on the page are the template's job. You write;\nit typesets.\n\nWhen you're ready, produce the PDF:\n\n```bash\nunipress compile . --format pdf\n```\n",
|
|
8
|
+
"content/02-main.md": "# Writing your article\n\nReplace this file with your own writing, or split it into several files —\n`03-results.md`, `04-discussion.md`, and so on. Each one becomes a section\nin reading order.\n\n## Headings and structure\n\nUse `##` for section headings within a file and `###` for subsections. They\nrender as clean, unnumbered headings — no \"Chapter 1\", no page break before\neach one. The document reads as one continuous piece.\n\n### A worked example\n\nLists, quotes, and code all behave the way you'd expect:\n\n- A first point, stated plainly.\n- A second point, with a bit more detail.\n- A third, to round things out.\n\n> A blockquote sets a passage apart — useful for an epigraph, a pulled\n> quote, or a definition you want to dwell on.\n\n```python\ndef measure(text):\n \"\"\"Roughly 75 characters per line is a comfortable measure.\"\"\"\n return sum(len(line) for line in text.splitlines())\n```\n\n## Math\n\nInline math like $E = mc^2$ sits in the run of text, and displayed\nequations stand on their own line:\n\n$$\n\\int_{0}^{\\infty} e^{-x^2}\\,dx = \\frac{\\sqrt{\\pi}}{2}\n$$\n\n## Switching to a book\n\nIf your writing grows chapters — and wants a title page, a table of\ncontents, and a smaller trim — it's one foundation, two genres. Remove\n`kind: article` from `document.yml` (and set a book trim), or start fresh\nwith `unipress create my-book --template book`.\n",
|
|
9
|
+
"document.yml.hbs": "name: \"{{title}}\"\nfoundation: '@uniweb/book@0.4.1'\nformat: pdf\n\nbook:\n # The `article` genre: single-column continuous prose on a paper size —\n # no chapters, no cover, no separate title page. Remove `kind` (and pick\n # a book trim like trade-6x9) to render a chaptered book instead.\n kind: article\n\n # Paper size. `a4` (default) or `letter` (US / Canada).\n trim: a4\n\n title: \"{{title}}\"\n subtitle: \"\"\n author: \"{{author}}\"\n language: en\n\n # A4/Letter at the 11pt default gives a comfortable ~75-character\n # measure. Uncomment to tune the body copy.\n # typography:\n # bodySize: 11pt\n # leading: 0.72em\n # firstLineIndent: 1.4em\n # bodyFont: [\"EB Garamond\", \"Garamond\", \"Georgia\"]\n\n # A table of contents is off by default for an article, and sections are\n # unnumbered. Turn either on for a longer or more formal paper:\n # structure:\n # toc: true\n # tocDepth: 2\n # numberHeadings: true # 1, 1.1, 1.1.1\n\n# Reading order. Files in content/ are sorted by name; the explicit list\n# below makes the order predictable. Names are the filename with any\n# leading number prefix removed (01-introduction.md → introduction). Add a\n# section by creating content/<name>.md and adding its base name here.\ncontent:\n - introduction\n - main\n",
|
|
10
|
+
},
|
|
5
11
|
"book": {
|
|
6
12
|
"PUBLISHING.md": "# Publishing your book\n\nOnce you have a finished book — the writing done, the cover artwork in place, a PDF or EPUB you're proud of — there's still a stretch of work between your folder and a reader. This file is a map of that stretch.\n\nunipress produces the files. What you do with them is up to you. These are notes on the most common paths.\n\n## What you'll need\n\nYou'll typically end up needing some or all of these:\n\n- **A print-ready PDF** for physical books. unipress produces this with `--format pdf`.\n- **An EPUB** for ebook stores. unipress produces this with `--format epub`.\n- **A cover image** that meets the requirements of wherever you're publishing — different platforms want different sizes, color profiles, and layouts. The placeholders in `assets/` work for testing but should be replaced before real publication.\n- **An ISBN**, in most cases. More on this below.\n- **Metadata**: title, subtitle, author, description, categories, keywords. You'll fill these in on the publishing platform; most aren't part of unipress.\n\n## Print-on-demand\n\nPrint-on-demand (POD) services print and ship your book one copy at a time as readers buy it. You don't hold inventory. The two main ones for English-language books:\n\n**[Kindle Direct Publishing (KDP)](https://kdp.amazon.com)** is Amazon's service. Free to set up; ships via Amazon. The most popular path for self-publishing fiction and many nonfiction books. Their submission requirements include specific PDF settings (bleed, trim size, color profile); read their [print formatting guide](https://kdp.amazon.com/help/topic/G201953020) before submitting.\n\n**[IngramSpark](https://www.ingramspark.com)** distributes to bookstores and libraries beyond Amazon — the path you want if you care about non-Amazon retail. There's a small per-title setup fee. Their [file creation guide](https://help.ingramspark.com/hc/en-us/categories/360002107591-File-Creation) covers what they need.\n\nMost authors who publish in print use both: KDP for Amazon's reach, IngramSpark for everywhere else. The same source files (with adjustments for each service's requirements) feed both.\n\nA note on cover artwork for print: print covers are wrap-around (front + spine + back as a single image) with bleed extending past the trim line. The width of the spine depends on your book's page count and paper. Each POD service has a cover template generator that gives you the exact dimensions for your specific book — use it. The placeholder front + back files in `assets/` won't satisfy this; for print, you'll need a real wrap-around cover that POD services accept (a single image), and the unipress book foundation references the front and back independently for digital outputs (EPUB thumbnails, PDF endpapers).\n\n## Ebook distribution\n\nFor ebooks, you have two strategies:\n\n**Direct distribution to each major store.** Set up accounts with KDP (for Kindle), [Apple Books](https://authors.apple.com), [Kobo Writing Life](https://www.kobo.com/writinglife), and [Google Play Books](https://play.google.com/books/publish). Upload your EPUB to each. More work; you keep more of the royalty.\n\n**Aggregator distribution.** Services like [Draft2Digital](https://draft2digital.com) and [Smashwords](https://www.smashwords.com) take your EPUB once and distribute it to multiple stores. Less work; they take a small cut. Common choice for first-time authors.\n\nEither way, your EPUB needs to pass validation. The free [EPUB Validator](https://validator.idpf.org) (also called epubcheck) tells you whether your file meets the EPUB standard. unipress produces valid EPUBs, but if you've heavily customized your foundation or included unusual content, validate before submitting.\n\n## ISBNs\n\nISBNs are the book industry's identifiers. Each format of your book (paperback, hardcover, ebook) gets a different ISBN.\n\nYou generally need an ISBN if you want to sell your book through retail bookstores or libraries. KDP will give you a free ISBN for ebooks and paperbacks sold through Amazon, but those ISBNs are tied to KDP — you can't use them elsewhere.\n\nIf you want an ISBN you fully own, you buy one (or a block) from your country's ISBN agency:\n\n- **United States**: [Bowker](https://www.myidentifiers.com)\n- **Canada, UK, Australia, and many other countries**: ISBNs are free or low-cost from your national agency. Search \"ISBN [your country]\" to find the right one.\n\nWhether to buy your own ISBNs or use the free ones from KDP is a personal decision. Owning your ISBN gives you flexibility; the free option saves money. Many first-time authors start with the free ones and switch later.\n\n## Cover artwork: a real one\n\nThe placeholder covers in `assets/` exist to make the sample book compile correctly. Before you publish — to anywhere — replace them with real artwork.\n\nReal cover artwork is its own discipline. A few realistic options:\n\n- **Hire a designer.** Sites like [Reedsy](https://reedsy.com), [99designs](https://99designs.com), and [Fiverr](https://www.fiverr.com) connect you with cover designers across a wide range of budgets. Expect $200–$2,000 for professional work, depending on the designer's experience.\n- **Use a cover-design tool.** Canva, Adobe Express, and similar tools have book-cover templates. Cheaper; takes longer; results vary with your design skill.\n- **Make your own** if you have the skills. The placeholder dimensions in `assets/` are a reasonable starting point; check the requirements of your target POD service for the exact specifications.\n\nWhatever path you take, your cover artwork has to meet the size, resolution, and bleed requirements of wherever you're publishing. KDP's print covers and Amazon's ebook thumbnails have different requirements than IngramSpark's. Your cover designer will know; if you're going alone, read each platform's specifications carefully.\n\n## Where to learn more\n\nSelf-publishing is a wide subject. A few resources worth knowing about:\n\n- **[Reedsy Learning](https://blog.reedsy.com)** publishes free guides on every aspect of self-publishing — writing, editing, design, marketing. Substantial and well-maintained.\n- **[The Alliance of Independent Authors](https://www.allianceindependentauthors.org)** is a professional association with member resources, watchdog reports on dishonest services, and the [Self-Publishing Advice blog](https://selfpublishingadvice.org).\n- **[Writer Beware](https://writerbeware.blog)** tracks scams and bad actors targeting writers. Worth bookmarking before you sign anything.\n- **[Jane Friedman's blog](https://www.janefriedman.com/blog/)** is one of the more thoughtful long-running publishing-industry blogs from someone genuinely independent.\n\n## What unipress does and doesn't do\n\nunipress produces the files: PDF, EPUB, Word document, spreadsheet. That's the technical handoff.\n\nunipress doesn't:\n\n- Submit your book to publishers or platforms. You upload your files yourself.\n- Generate ISBNs. You acquire them yourself.\n- Design your cover. You design it (or hire someone to).\n- Edit your prose. You edit it (or hire an editor).\n- Market your book. That's a whole separate craft.\n\nThe work between your folder and a reader is mostly outside unipress. unipress just makes sure the files at the start of that journey are real, professional, and ready.\n\nGood luck.\n",
|
|
7
13
|
"README.md": "# Your unipress book project\n\nThis folder is a unipress book project. It already contains a small sample book — title, chapters, cover placeholder, colophon — so you can compile it immediately and see what you're working with.\n\nWhen you're ready to make this folder yours, replace the sample content with your own writing.\n\n## Compile the sample\n\nFrom this folder's parent directory:\n\n```\nunipress compile my-book\n```\n\nYou'll get a PDF (`my-book.pdf`) next to this folder. Open it. That's your starting point.\n\nTo produce a different format:\n\n```\nunipress compile my-book --format epub\nunipress compile my-book --format docx\nunipress compile my-book --format typst # source bundle for further hand-editing\n```\n\n## What's in here\n\n```\nmy-book/\n├── README.md ← This file. Read first.\n├── PUBLISHING.md ← Notes about publishing your finished book.\n├── document.yml ← Settings: title, author, format, trim, fonts, cover.\n├── content/ ← Your writing. Markdown files, one per chapter.\n└── assets/ ← Image files used by your book, including the cover.\n```\n\nThe four files in `content/` are the sample chapters. They explain what the book template can do as you read through them. Replace them with your own writing when you're ready — keep the structure or change it; it's yours now.\n\nThe two files in `assets/` are placeholder front and back covers. Replace them with your own artwork when you have it. Real covers usually start as a single wrap-around image (front + spine + back) — see `PUBLISHING.md` for guidance on print-ready artwork.\n\n`document.yml` holds your project's settings: the title, the author, what format the book compiles to by default, what trim size to use, what fonts. After you run `unipress create`, it has sensible defaults; adjust them as you need.\n\n## Writing in this folder\n\nEach markdown file in `content/` becomes a chapter of the compiled book. The order is set by the `content:` list in `document.yml` — add a new file to `content/` and add its base name (without `.md`) to that list in the order you want it to appear.\n\nIf you'd rather rely on filename sorting and let unipress order things alphabetically, drop the explicit `content:` list from `document.yml` and the framework will read every `.md` file in `content/` in filename order. Numbering filenames (`01-welcome.md`, `02-formatting.md`, …) is the easiest way to control order; if you want to insert a new chapter between two others, give it a number that sorts between theirs.\n\n## Compile, look, revise\n\nMost of your time will be: edit a chapter, compile, open the PDF, see how it looks, edit again. The book template handles the typography; you focus on the words.\n\nA few things to know about compile:\n\n- It's fast. A short book takes a few seconds.\n- It's safe. unipress doesn't change anything in your `content/` folder.\n- It's deterministic. The same content produces the same PDF every time.\n\nIf something looks wrong in the output, run `unipress inspect my-book` to see how unipress parsed your content. Most surprises trace back to something the parser interpreted differently than you expected.\n\n## Adding citations (optional)\n\nIf your book cites sources — even occasionally — the same `@uniweb/book` foundation supports inline cites and a back-matter bibliography. Trade books rarely use this, so it's commented out in `document.yml`. To opt in:\n\n1. Uncomment the `citationStyle:` block under `book:` and the `collections:` block at the bottom of `document.yml`.\n2. Create `collections/bibliography/` and add one YAML file per source (`darwin1859.yml`, etc.) — the filename stem is the cite key.\n3. Cite in prose with `[@key]` (or `[@key]{page=42}`, `[@a; @b]`, `[@key]{suppress-author}`).\n4. Add a back-matter file (e.g. `99-bibliography.md`) with `type: Bibliography` and `data: bibliography` in its frontmatter.\n\nThe `monograph` template ships with this turned on as a worked example — `unipress create my-mono --template monograph` to see it.\n\n## When you're ready to publish\n\nSee [`PUBLISHING.md`](./PUBLISHING.md) for notes on what's next after you have a finished book — print-on-demand, ebook distribution, ISBNs, cover requirements, and where to find more help.\n\n## More documentation\n\n- The [unipress README](https://github.com/uniweb/unipress) has the broader picture and command reference.\n- `unipress --help` (or just typing it in a terminal) lists every option.\n- The book template's settings — trim presets, typography overrides, cover knobs, structure toggles — are documented at the [book template guide](https://github.com/uniweb/unipress/blob/main/docs/templates/book.md) and the [book foundation README](https://github.com/uniweb/unipress/blob/main/foundations/book/README.md).\n",
|
|
@@ -11,7 +17,7 @@ export const TEMPLATES = {
|
|
|
11
17
|
"content/02-formatting.md": "# What the Template Can Do\n\nThis chapter exercises the formatting features the book template supports. You can keep it around as a reference while you work, or delete it once you've seen what's here.\n\n## Headings\n\nA first-level heading like the one above starts a new chapter. Second-level headings like this one mark sections within a chapter. Third-level headings exist if you need them, but most books don't.\n\n## Emphasis and quotation\n\nBody text can be *italic* or **bold** or even ***both at once***. You can mark technical terms in `monospace`, which the template renders in a slightly smaller size to keep them from disrupting the line.\n\nFor longer quotations, blockquotes pull away from the body:\n\n> The book template treats quotations as a separate visual register, not just indented prose. The result is that a long passage of someone else's words looks like quotation, not like a paragraph that wandered off-margin.\n\nThat's the kind of detail typography handles for you.\n\n## Lists\n\nNumbered lists are useful when order matters:\n\n1. First, the order matters.\n2. Then, the order matters more.\n3. Finally, the order has mattered enough.\n\nBulleted lists are useful when it doesn't:\n\n- One thing.\n- Another thing.\n- A third thing, related to the first two.\n\n## Mathematics\n\nIf your book includes equations, write them in LaTeX style. Inline equations sit in the flow of a sentence, like $E = mc^2$, without disrupting the line. Displayed equations get their own line and centered position:\n\n$$\n\\int_{-\\infty}^{\\infty} e^{-x^2} \\, dx = \\sqrt{\\pi}\n$$\n\nThe book template knows how to size and space these properly.\n\n## Footnotes\n\nFootnotes attach to the end of the relevant page.[^1] Multiple footnotes work fine.[^2] You don't have to manage their numbering — the template does.\n\n[^1]: Like this one. The numbering is automatic; you can refer to footnotes by name in the markdown source and the template renumbers them in the order they appear.\n\n[^2]: This is the second footnote. It's longer, to show that footnotes wrap properly across multiple lines if they need to, and that the typography of footnote text is different from the body text.\n\n## Code blocks\n\nIf your book includes code, fenced code blocks render with monospace typography and proper spacing:\n\n```\nfunction example() {\n return \"this is a code block\"\n}\n```\n\nYou can name a language for syntax highlighting in printed and digital outputs.\n\n## Images\n\nImages live in your project's `assets/` folder. Reference them with standard markdown — site-absolute paths are checked against `assets/` automatically:\n\n```\n\n```\n\nThe book template handles figure numbering, captions, and placement.\n\nThat's most of it. The next chapter points you toward where to go from here.\n",
|
|
12
18
|
"content/03-where-next.md": "# Where to Go from Here\n\nYou've seen what a unipress book looks like. The next steps depend on what you're working on.\n\nIf you're starting a new book, replace the contents of these chapter files with your own writing. Adjust the title and author in `document.yml`. Add or remove chapters as your book takes shape.\n\nIf you want a different look — a different page size, different fonts, a different cover treatment — open `document.yml` and look at the settings the book template exposes. Most visual aspects are configurable without leaving the template.\n\nIf you have your own cover artwork, replace `assets/front.jpg` and `assets/back.jpg` with your own files (any common image format works — adjust the `book.covers.front` and `.back` paths in `document.yml` if you change the extension). The template will use yours instead of the placeholders.\n\nIf the book template isn't quite right for what you're writing — if you're producing an academic monograph, a thesis, a paper, or a report — try one of the other templates. Run `unipress list-templates` to see what's available.\n\nIf you want to take the typesetting further than the template allows, ask unipress for the Typst source: `unipress compile --format typst`. You'll get a folder of source files you can take into Typst directly, customize without limit, and compile yourself.\n\nWhen you're ready to share your book, the formats are at your disposal: PDF for printing or sharing, EPUB for ebook readers, Word for committees and collaborators. One source, many outputs.\n\nWhen you're ready to publish — to find readers, to put your book in stores or libraries — see the project's `PUBLISHING.md` for notes on what's next.\n\nThat's the tour. The blank chapters above this one are waiting for your own writing.\n",
|
|
13
19
|
"content/99-colophon.md": "---\ntype: BackMatter\ntitle: Colophon\n---\n\n# Colophon\n\nThis book was set in the book template's default typography and produced with [unipress](https://github.com/uniweb/unipress), a tool for turning markdown into finished documents. The text faces are the foundation's defaults; the cover image is a placeholder and should be replaced before publication.\n\nThe structure of this book — its chapters, its title page, its page numbering, its back matter — is the work of the book foundation, which is itself part of the [Uniweb](https://uniweb.app) ecosystem. The foundation handles the typography; the markdown handles the content. Each is what it does well.\n\nIf you're reading this in a published version of the book, it means the author kept the colophon. If you're reading it in the placeholder version that ships with unipress, replace it with your own when you're ready, or remove the file from `content/` if you don't want a colophon at all.\n",
|
|
14
|
-
"document.yml.hbs": "name: \"{{title}}\"\nfoundation: '@uniweb/book@0.
|
|
20
|
+
"document.yml.hbs": "name: \"{{title}}\"\nfoundation: '@uniweb/book@0.4.1'\nformat: pdf\n\nbook:\n title: \"{{title}}\"\n subtitle: \"\"\n author: \"{{author}}\"\n language: en\n rights: \"© {{year}} {{author}}. All rights reserved.\"\n\n # Trim size. Common values:\n # trade-6x9 (default), trade-7x10, crown-octavo, royal-octavo, a5\n trim: trade-6x9\n\n # Front matter and structure.\n structure:\n titlePage: true\n copyrightPage: true\n toc: true\n tocDepth: 2\n frontMatterNumbering: roman\n\n # Cover artwork. The scaffold ships placeholder front + back files so\n # the first compile produces a complete-looking book; replace both files\n # in `assets/` with your own artwork when you have it. The back cover\n # appears at the end of the PDF; the front is referenced by EPUB\n # readers and most PDF viewers as the book's thumbnail.\n covers:\n front: assets/front.jpg\n back: assets/back.jpg\n\n # Optional typography overrides. Defaults work for most books — uncomment\n # to customize.\n #\n # typography:\n # bodySize: 11pt\n # leading: 0.72em\n # firstLineIndent: 1.4em\n # bodyFont: [\"EB Garamond\", \"Garamond\", \"Georgia\"]\n # headingFont: [\"EB Garamond\", \"Garamond\", \"Georgia\"]\n\n # Optional citation system. Trade books rarely cite sources; the\n # `monograph` template ships with this turned on by default. To opt in\n # here, uncomment the two blocks below, add a `collections/bibliography/`\n # directory of YAML records, and a `99-bibliography.md` content file\n # with `type: Bibliography`. See the monograph template's README for\n # the full pattern.\n #\n # citationStyle: chicago-author-date\n # bibliography:\n # sortBy: author\n\n# collections:\n# bibliography:\n# path: collections/bibliography\n\n# Reading order. Files in content/ are sorted by name; the explicit list\n# below makes the order predictable. Add new chapters by creating\n# content/<name>.md and adding the base name (without `.md`) here.\ncontent:\n - 01-welcome\n - 02-formatting\n - 03-where-next\n - 99-colophon\n",
|
|
15
21
|
},
|
|
16
22
|
"data-report": {
|
|
17
23
|
"README.md": "# `data-report` template\n\nA data-driven report aggregating metrics across a set of records (members, publications, funding, supervisions). Pinned to `@uniweb/data` — outputs both an Excel workbook (one sheet per section) and a Word report.\n\n```bash\nunipress compile . --format xlsx --out my-report.xlsx\nunipress compile . --format docx --out my-report.docx\n```\n\n## What's here\n\n```\ndata-report/\n├── document.yml pinned to @uniweb/data\n├── theme.yml\n├── collections/\n│ ├── members/ three sample records (19th-century naturalists)\n│ └── queries/ saved query examples\n└── content/\n └── report/ cover, members, publications-by-*, funding, supervisions\n```\n\nThe starter ships three sample members (Darwin, Lyell, Wallace) so the first compile produces a non-empty workbook. Replace the YAML files under `collections/members/` with your own data — the foundation reads any record matching the queryable schema declared in `document.yml`.\n\n## Customize\n\n### Add a member\n\nDrop a YAML file under `collections/members/`:\n\n```yaml\n# collections/members/your-name.yml\nname: \"Jane Doe\"\ndepartment: biology\nrank: professor\ntenured: true\nstart_year: 2018\npublications:\n - { type: article, title: \"...\", year: 2024, journal: \"...\", doi: \"...\" }\nfunding:\n - { title: \"...\", amount: 250000, year: 2023, source: \"...\" }\nsupervisions:\n - { name: \"...\", level: PhD, year: 2024 }\n```\n\nThe Cover section's Loom expressions (`{COUNT OF members}`, `{totalPublications}`, etc.) update automatically.\n\n### Filter the active selection\n\nEdit `document.yml`'s `collections.members.queryable:` to declare the filterable fields you want exposed in the FilterPanel UI. Each field becomes a control; `enum` fields render as multi-select, `boolean` as toggles, `range` as numeric range inputs.\n\n### Switch from static files to a backend\n\nWhen the data outgrows the YAML files (`/data/members.json`-shaped backed by `collections/members/`), declare a backend fetcher:\n\n```yaml\ncollections:\n members:\n path: collections/members\n fetcher:\n url: https://api.example.com/members\n supports: [where, limit, sort]\n```\n\n`supports: [where]` ships the active where-object to the backend; the same foundation code, same components, same compile output — what changed is *where the predicate runs*.\n",
|
|
@@ -61,14 +67,14 @@ export const TEMPLATES = {
|
|
|
61
67
|
"content/02-introduction.md": "---\ntype: Chapter\ntitle: \"Introduction\"\n---\n\nEvery monograph opens with an introduction that does three things at once: it situates the work in its existing scholarship, it states the question the rest of the book will pursue, and it tells the reader what to expect chapter by chapter. The first task is the most contested — too brief and the reader doubts that you know the literature, too thorough and you have written a literature review instead of an introduction.\n\nThe starter chapter that follows treats Victorian-naturalist sources as a worked example of the citation apparatus this template ships with. The author of this template assumes you will replace both the sample bibliography (`collections/bibliography/`) and the chapter prose with your own material — the structural pattern is what's reusable.\n\n## The argument in brief\n\nState the argument once, plainly, in a paragraph. The remainder of the introduction can elaborate, but the reader should leave this section knowing the claim before the evidence arrives.\n\n## How citations work in this template\n\nInline citations use a Pandoc-style sugar: `[@key]` for a bare cite, `[@key]{page=42}` for a page locator, `[@a; @b]` for a multi-cite cluster, and `[@key]{suppress-author}` when the author is named in the running prose (\"Darwin (1859) showed…\"). The `key` is the BibTeX cite key in `collections/bibliography/refs.bib` — every `@entry{key, ...}` becomes one record the cites can reach. Pick a citation style by setting `book.citationStyle:` in `document.yml`; the same nine styles citestyle ships are wired in (Chicago author-date, APA, MLA, IEEE, Vancouver, Harvard, AMA, Nature, Science). Change one line and every cite plus the back-matter list re-formats to match.\n\n## The chapters ahead\n\nA monograph rewards readers who know the road map. Sketch each chapter in two or three sentences — what it does and why it has to be there.\n",
|
|
62
68
|
"content/03-chapter-one.md": "---\ntype: Chapter\ntitle: \"Variation and Its Discontents\"\n---\n\nThe first decade after the *Origin* unsettled the relations between three audiences for natural history — gentleman-naturalists in correspondence, Continental experimentalists working with quantitative methods, and the lay public reached by trade publishers. Each read the new mechanism through prior commitments. The geological gradualism that Darwin (1859) inherited from Lyell — laid out a generation earlier in the *Principles of Geology* [@lyell1830] — gave him a vocabulary of slow, cumulative change that the British reading public was already prepared to accept [@darwin1859]{suppress-author}. The independent paper Wallace had sent from Ternate the year before [@wallace1858] proposed the same mechanism in compatible terms; the joint reading at the Linnean Society in 1858 made the priority public without making either author the popular face of the idea.\n\n## Two readings of the mechanism\n\nTwo strands of reception ran through the 1860s. The first, exemplified by Huxley's polemical defense [@huxley1863], used the new framework to argue continuity between humans and other primates. The second, advanced most systematically in Spencer's *Principles* [@spencer1864], read selection as a special case of a more general law of progress that Spencer thought already operated in non-biological domains. The two strands diverged on what the mechanism was a mechanism *of*: in Huxley's reading it was a tool for reorganizing the boundary between the human and the animal; in Spencer's, it was confirmation of a metaphysics he had already published.\n\n## The variation problem\n\nSelection acts on heritable variation. Where the variation comes from, in what proportion, and whether it has any direction the organism contributes to — these were open questions in the 1860s that the *Origin* did not answer. Mendel's experiments on hybridization [@mendel1866]{page=12}, published in an obscure proceedings in Brünn the same decade, addressed the question of what was inherited at the level of single traits. The paper sat unread by the principals — Darwin, Wallace, Hooker, Huxley — for three and a half decades [@mendel1866]{suppress-author}. When it was rediscovered around 1900, it slotted into the variation problem in a way the Victorian generation could not have arranged for it to.\n\n### A note on sources\n\nFor the geographical-distribution side of the argument, Wallace's *Malay Archipelago* [@wallace1869]{page=78} remains the best-known synthesis. Hooker's flora-of-Australia essay [@hooker1859]{page=ii} predates the *Origin* by a few months and reads, in retrospect, as one of the cleanest pre-publication endorsements of the mechanism — Hooker had seen the manuscript. Darwin's later *Descent* [@darwin1871] makes the case for selection's reach into human evolution that Huxley had been making polemically since 1863 [@huxley1863]{page=125}.\n\n## The argument's afterlife\n\nBy the end of the 1870s, three of the period's central works — the *Origin*, the *Descent*, and Wallace's geographical essays — had crossed into the popular canon [@darwin1859; @darwin1871; @wallace1869]. The mechanism had found audiences the authors had not written for. The next chapter takes up the question of what those audiences read into it.\n",
|
|
63
69
|
"content/99-bibliography.md": "---\ntype: Bibliography\ntitle: References\ndata: bibliography\n---\n",
|
|
64
|
-
"document.yml.hbs": "name: \"{{title}}\"\nfoundation: '@uniweb/book@0.
|
|
70
|
+
"document.yml.hbs": "name: \"{{title}}\"\nfoundation: '@uniweb/book@0.4.1'\nformat: pdf\n\nbook:\n title: \"{{title}}\"\n subtitle: \"\"\n author: \"{{author}}\"\n language: en\n rights: \"© {{year}} {{author}}.\"\n trim: royal-octavo\n typography:\n bodySize: 10.5pt\n leading: 0.68em\n firstLineIndent: 1.2em\n bodyFont: [\"EB Garamond\", \"Garamond\", \"Georgia\"]\n headingFont: [\"EB Garamond\", \"Garamond\", \"Georgia\"]\n structure:\n titlePage: true\n copyrightPage: true\n toc: true\n tocDepth: 3\n frontMatterNumbering: roman\n # Citation style for inline cites and the back-matter bibliography.\n # Pick from: chicago-author-date (default — the scholarly humanities\n # convention), apa, mla, harvard, ieee, vancouver, ama, nature, science.\n # Switch the document's whole bibliographic apparatus by changing one\n # value here; nothing else in the project needs editing.\n citationStyle: chicago-author-date\n bibliography:\n sortBy: author\n\n# Bibliographic records the inline `[@key]` cites and the back-matter\n# Bibliography section read from. Drop a `.bib` file into\n# `collections/bibliography/` — every `@entry{key, ...}` becomes one\n# record, with the BibTeX cite key used as the lookup id. YAML and JSON\n# files in the same directory are merged in (handy for one-off entries\n# you want to maintain by hand alongside an exported `.bib`).\ncollections:\n bibliography:\n path: collections/bibliography\n\ncontent:\n - 01-preface\n - 02-introduction\n - 03-chapter-one\n - 99-bibliography\n",
|
|
65
71
|
},
|
|
66
72
|
"report": {
|
|
67
73
|
"README.md": "# `report` template\n\nA technical report: trade-7x10 trim, block paragraphs (no first-line indent), tables and code listings styled for clarity, code-block margin relief so wide content doesn't wrap awkwardly. Same `@uniweb/book` foundation as the `book` and `monograph` templates — configured for technical writing.\n\n```bash\nunipress compile . --format pdf --out my-report.pdf\nunipress compile . --format pagedjs --out my-report.html\n```\n\n## What's here\n\n```\nreport/\n├── document.yml pinned to @uniweb/book; trade-7x10, block paragraphs\n├── content/\n│ ├── 01-summary.md executive summary + recommendations table\n│ ├── 02-findings.md body, with code listing and pull-quote\n│ └── 03-methodology.md\n└── README.md this file\n```\n\n## When to pick `report` over `book` or `monograph`\n\n- The reader expects a summary up top and methodology at the bottom.\n- The body has tables, code, configuration snippets, or numbered findings.\n- Block paragraphs (no indent) read better than first-line-indented prose for the content.\n- A wider page (trade-7x10) gives long code lines and wide tables more room.\n\nFor prose-driven content, pick `book` (trade-6x9) or `monograph` (royal-octavo, classical typography).\n\n## Adding citations\n\nReports often cite — primary sources backing findings, prior work in methodology, regulatory references in compliance reports. The same `@uniweb/book` foundation supports inline cites and a back-matter bibliography; this template ships them commented out so a report that doesn't need them isn't carrying empty scaffolding. To opt in:\n\n1. Uncomment the `citationStyle:` block under `book:` and the `collections:` block at the bottom of `document.yml`.\n2. Create `collections/bibliography/` and drop a `.bib` file in (each `@entry{key, ...}` becomes one record; the cite key is what you reference with `[@key]`). Hand-written YAML in CSL-JSON shape works alongside it if you'd rather edit entries directly.\n3. Cite in prose: `[@smith2024]`, `[@smith2024]{page=12}` for a locator, `[@a; @b]` for a multi-cite cluster, `[@key]{suppress-author}` when the author is named in the running prose.\n4. Add a back-matter content file (e.g. `99-bibliography.md`) with `type: Bibliography` and `data: bibliography` in frontmatter.\n\nPick a citation style by setting `book.citationStyle:` to one of `chicago-author-date`, `apa`, `mla`, `harvard`, `ieee`, `vancouver`, `ama`, `nature`, `science`. For numbered styles (IEEE, Vancouver, Nature, etc.), the back-matter list reuses the same numbering as the inline cites — `[1]` inline matches `[1]` in the back-matter.\n\nThe `monograph` template ships with this turned on as a worked example — `unipress create my-mono --template monograph` to see it, including a Victorian-naturalist bibliography that exercises every cite shape.\n\n## Customize\n\nEdit `document.yml`:\n\n- `book.trim:` — `trade-7x10` (default), `trade-6x9`, `crown-octavo`, `royal-octavo`, `a5`.\n- `book.typography.codeMarginRelief:` — `0pt` to disable; `0.25in` (default) lets code blocks extend past the body column.\n- `book.typography.firstLineIndent:` — `0pt` (default for reports) for block paragraphs; `1.25em` for prose-style indented paragraphs.\n- `book.structure.copyrightPage:` — `false` (default for reports) to drop the copyright spread.\n- `book.citationStyle:` — one of nine supported styles; pairs with the `collections.bibliography` block above.\n",
|
|
68
74
|
"content/01-summary.md": "---\ntype: BackMatter\ntitle: Executive Summary\n---\n\n# Executive Summary\n\nA technical report opens with a summary because most readers will read no further. State the question, the headline answer, and the practical implications in three or four short paragraphs. Save the methodology, caveats, and full evidence for the body.\n\nThis template is configured for technical writing rather than narrative prose: a wider trade-7x10 trim, no first-line paragraph indent (block paragraphs read better with technical content), and a code-block margin relief that lets long code lines and wide tables push past the body column.\n\n## Headline result\n\nLead with the single most important finding. One sentence, plain language. The reader who stops here should still leave with the one thing you most want them to know.\n\n## Recommendations\n\n| # | Recommendation | Owner | Timeline |\n|---|----------------|-------|----------|\n| 1 | First action item, stated as an imperative. | Team A | Q3 |\n| 2 | Second action item. | Team B | Q4 |\n| 3 | Third — typically a measurement or follow-up. | Team A | Q4 |\n\nTables work in both PDF and EPUB output. The Paged.js stylesheet keeps rows together so a table doesn't break across pages mid-row.\n",
|
|
69
75
|
"content/02-findings.md": "# Findings\n\nThe body of the report. Replace this content with the substance of what you measured, observed, or analyzed. Each subsection is one finding; lead with the conclusion and follow with the evidence.\n\n## Finding one\n\nA finding is a claim plus a justification. State the claim in the heading or the first sentence; let the rest of the paragraph carry the evidence.\n\nThe numbers you cite should be reproducible. Where they came from, what time window they cover, and how you computed them should be obvious to a reader six months from now who finds the report and has lost the original spreadsheet.\n\n```js\n// A short, runnable code listing.\nconst median = (xs) =>\n xs.sort((a, b) => a - b)[Math.floor(xs.length / 2)]\n```\n\nThe `codeMarginRelief: 0.25in` setting in `document.yml` lets code blocks extend 0.25in past the body column on each side. For wide listings — long config files, full SQL queries, JSON snapshots — that extra room means lines don't wrap mid-statement.\n\n## Finding two\n\nA second finding, with its own claim and its own evidence. Keep the structure consistent across findings so the reader builds an expectation about how each section is going to be organized.\n\n> A pull-quote or block quotation can break up a long stretch of body text and signal that what follows deserves extra attention.\n\n## Finding three\n\nThe third typically points the reader toward whatever comes next — the methodology section that explains how the findings were obtained, an appendix with the raw data, or a follow-up report.\n",
|
|
70
76
|
"content/03-methodology.md": "# Methodology\n\nA reader who acts on the report's recommendations needs to know how the findings were obtained — not because they will redo the work, but because they need a sense of how confident the numbers are.\n\n## Data sources\n\nList the inputs. For each: what it is, when you collected it, who provided it, and any cleanup or filtering you applied before analysis.\n\n- Source A — origin, time window, sample size.\n- Source B — origin, time window, sample size.\n- Source C — origin, time window, sample size.\n\n## Analysis\n\nDescribe the steps from raw input to the numbers cited in the findings. A short section is fine — a paragraph per step, with code or formulas inline where they sharpen the explanation.\n\n## Limitations\n\nEvery report has limits. Naming them up front is more credible than letting a reader find them. A short list is enough — \"the sample only covers Q1–Q3, not the holiday period\" or \"we relied on self-reported timing rather than logged events\" — and it earns the reader's trust on the rest of the analysis.\n",
|
|
71
|
-
"document.yml.hbs": "name: \"{{title}}\"\nfoundation: '@uniweb/book@0.
|
|
77
|
+
"document.yml.hbs": "name: \"{{title}}\"\nfoundation: '@uniweb/book@0.4.1'\nformat: pdf\n\nbook:\n title: \"{{title}}\"\n subtitle: \"\"\n author: \"{{author}}\"\n language: en\n rights: \"© {{year}} {{author}}.\"\n trim: trade-7x10\n typography:\n bodySize: 10.5pt\n leading: 0.7em\n firstLineIndent: 0pt\n codeMarginRelief: 0.25in\n structure:\n titlePage: true\n copyrightPage: false\n toc: true\n tocDepth: 2\n frontMatterNumbering: none\n\n # Optional citation system. Reports often cite — primary sources for\n # findings, prior work in methodology, regulatory references. To opt\n # in, uncomment the two blocks below, add a `collections/bibliography/`\n # directory of YAML records, and a content file with `type: Bibliography`\n # at the back. See the `monograph` template's README for the full\n # pattern (style table, two YAML shapes, every cite spelling worked).\n #\n # citationStyle: chicago-author-date\n # bibliography:\n # sortBy: author\n\n# collections:\n# bibliography:\n# path: collections/bibliography\n\ncontent:\n - 01-summary\n - 02-findings\n - 03-methodology\n",
|
|
72
78
|
},
|
|
73
79
|
"thesis": {
|
|
74
80
|
"README.md": "# Thesis template (UofT-shaped)\n\nA starter unipress template for an academic thesis. Produces a\nPDF that meets the University of Toronto School of Graduate Studies\nformatting requirements via `latexmk` on the LaTeX path; comparable\ntypography is available on the Typst path. Theses from other\ninstitutions can switch the template by editing one value (see\n[Switching institutions](#switching-institutions) below).\n\n## Quick start\n\n```sh\nunipress create my-thesis --template thesis \\\n --title \"Your Thesis Title\" \\\n --author \"Your Name\"\n\ncd my-thesis\nunipress compile . --format latex --out my-thesis.zip\nunzip -o my-thesis.zip -d build\ncd build && latexmk -pdf main.tex\nopen main.pdf\n```\n\nThat gives you a clean PDF with a UofT title page, a stub abstract, an\nauto-generated list of figures, four chapters of placeholder\nalgorithmic-CS prose, a worked theorem and proof, a 12-entry\nbibliography, and one appendix.\n\n## What you edit, what you don't\n\nThe thesis template uses two distinct config files:\n\n**`document.yml`** holds technical configuration — output format,\nfoundation reference, citation style, structural settings. You'll\ntypically only touch the `citationStyle:` field, and only if you\nprefer something other than IEEE.\n\n**`thesis.yml`** holds your institutional / candidate metadata. This\nis where you replace four or five values that are personal to you:\n\n```yaml\ntitle: \"Your Thesis Title\"\ncandidate:\n name: \"Your Name\"\ndegree:\n level: M.Sc. # or Ph.D., M.A., M.Eng.\n field: Computer Science\ndepartment: Department of Computer Science\ninstitution: University of Toronto\nyear: 2026\n```\n\nThe `TitlePage` section type reads this and renders the canonical\nUofT-styled title page. You shouldn't need to edit `00-titlepage.md`\nitself — the section just declares its type and the foundation does\nthe rest.\n\n## Authoring chapters\n\nEach chapter is one Markdown file under `content/`. Files are emitted\nin alphabetical order by filename, so the `NN-` prefix gates the\nsequence. `00-` / `01-` / `02-` are conventionally front-matter,\n`10-`–`19-` are body chapters, `90-` is references / bibliography,\n`99-` is appendices.\n\nA chapter file looks like:\n\n```markdown\n---\ntype: Chapter\ntitle: \"Introduction\"\nid: sec-intro\n---\n\nThe introduction begins here. Cite earlier work like\n[@christofides1976] or with a locator [@cormen2009]{page=87}.\n\n## A subsection {#sec-method}\n\nReference the section with [#sec-method] later in the chapter.\n\nA figure with cross-reference:\n\n{#fig-1 caption=\"The diagram caption.\"}\n\nIn a later chapter, [#fig-1] resolves to \"Fig. 1\" or \"Figure 1\"\ndepending on the citation/xref preset.\n```\n\nThe frontmatter `id:` on a chapter sets the cross-reference label for\nthe chapter as a whole — `[#sec-intro]` resolves to the chapter\nnumber. Subsections get their own ids via `{#sec-id}` after the\nheading.\n\n## Theorems, lemmas, definitions, proofs\n\nThe foundation ships dedicated section types for math-style content:\n\n```markdown\n---\ntype: Theorem\nid: thm-main\nname: \"Main Result\"\n---\n\nThe body of the theorem statement.\n```\n\n```markdown\n---\ntype: Proof\n---\n\nThe body of the proof.\n```\n\nTheorem, Lemma, and Definition are numbered per chapter (\"Theorem\n4.1, Lemma 4.2\"). Lemma shares Theorem's counter so a Theorem\nfollowed by a Lemma in the same chapter numbers consecutively.\nDefinition has its own counter. Proof is unnumbered.\n\nCross-reference a labelled theorem from anywhere in the document with\n`[#thm-main]` — resolves to \"Theorem 4.1\" automatically via biblatex\nhyperref's `\\autoref`.\n\n## Switching citation style\n\n```yaml\n# document.yml\nbook:\n citationStyle: chicago-author-date # or apa, mla, harvard, ieee, vancouver, ama, nature, science\n```\n\nNine styles are available. The setting drives both the inline `[@key]`\ncite formatting and the back-matter `\\printbibliography` rendering.\nbiblatex picks up the change on next compile; biber is invoked\nautomatically by `latexmk`.\n\n## Switching institutions\n\nThe default template targets the UofT SGS formatting requirements\nvia the community-maintained `ut-thesis.cls` on CTAN. Other\ninstitutions usually publish their own LaTeX class. To switch:\n\n1. **Drop the institutional class file into the project**. If your\n institution publishes a `.cls` file (e.g. `mit-thesis.cls`), put\n it in `assets/` and the foundation will bundle it.\n2. **Or rely on `tlmgr install`** if your institution's class is on\n CTAN — most major research universities have one.\n3. **Comment out `book.kind: thesis-uoft`** in `document.yml` to fall\n back to the foundation's generic book template, then add a custom\n preamble to override `\\documentclass` to your institution's class.\n\nFuture versions of the foundation may ship parameterised\n`book.kind: 'thesis-mit'` / `'thesis-stanford'` etc. as the\necosystem matures. Open an issue if you want yours added.\n\n## ProQuest submission\n\nThe thesis-uoft template enables PDF/A-1b output for ProQuest\narchival via the `pdfx` package. Confirm your generated PDF is\nPDF/A-compliant before submission — Adobe Acrobat's\n\"Preflight → PDF/A → Verify Compliance\" or the open-source\n[veraPDF](https://verapdf.org/) tool both work.\n\nUofT's specific submission flow is covered at\nhttps://www.sgs.utoronto.ca/current-students/program-completion/\n— the thesis template aims to produce output that meets the\nformatting requirements there but cannot guarantee anti-drift; SGS\noccasionally updates margin / spacing rules. Re-validate against\ntheir current page before final submission.\n\n## What's NOT covered yet\n\nA few thesis features that authors sometimes want haven't been\nshipped as first-class section types yet:\n\n- **Glossary / list of abbreviations / list of symbols** — useful in\n long theses; for now, build via `BackMatter` sections.\n- **Custom theorem-style declarations** (\"Conjecture\", \"Observation\",\n \"Remark\") — extend `foundation.xref.kinds` per project to add new\n named environments.\n- **Equation labels for cross-reference** (`{#eq-id}` on a math\n display) — labels are emitted in the LaTeX source; runtime\n cross-referencing with `[#eq-id]` is implemented but the math\n display must carry the id explicitly via an inline attribute that\n is being rolled out.\n\nIf you need any of these badly, see the foundation's source under\n`@uniweb/book` and extend; or open an issue.\n\n## Verifying the structure compiles\n\n```sh\nunipress compile . --format pagedjs --out my-thesis.html\nunipress compile . --format epub --out my-thesis.epub\nunipress compile . --format typst --out my-thesis.zip\nunipress compile . --format latex --out my-thesis.zip\n```\n\nAll four outputs should produce files. Open `my-thesis.html` in a\nbrowser; extract the typst zip and compile via `typst compile main.typ`;\nextract the latex zip and compile via `latexmk -pdf main.tex`. UofT\nis satisfied by either the typst PDF or the latex PDF — pick by\nyour advisor's preference.\n\nThis template was last validated against SGS formatting guidelines on\n2026-04-26.\n",
|
|
@@ -96,6 +102,6 @@ export const TEMPLATES = {
|
|
|
96
102
|
"content/13-conclusion.md": "---\ntype: Chapter\ntitle: \"Conclusion and Future Work\"\nid: sec-conclusion\n---\n\nWe presented a refined analysis of randomised rounding for ATSP that\nimproves the leading constant in the approximation factor. The result\nin [#sec-main-result] complements the constant-factor approximation\nof [@svensson-tarnawski-vegh2018] and the structural insights of\n[@christofides1976] for the symmetric setting.\n\nThree directions for future work suggest themselves. First, the gap\nbetween our upper bound and the integrality gap of the Held–Karp\nrelaxation [@held-karp1970] remains a constant factor; closing it\nwould yield a tight analysis. Second, the rounding scheme in\n[#sec-main-result] is randomised; derandomisation by the method of\nconditional expectations [@motwani-raghavan1995] is a natural next\nstep. Third, we have not addressed the *Euclidean* asymmetric TSP, a\ngeometric specialisation where stronger structural properties may\nadmit a PTAS in the spirit of [@arora1998].\n",
|
|
97
103
|
"content/90-references.md": "---\ntype: Bibliography\ntitle: References\ndata: bibliography\n---\n",
|
|
98
104
|
"content/99-appendix-a.md": "---\ntype: Appendix\ntitle: \"Implementation Notes\"\n---\n\nThis appendix collects implementation details that would distract from\nthe main exposition. The randomised rounding scheme described in\n[#sec-main-result] is implemented in approximately 200 lines of\nPython; the source is available at the URL given in the\nacknowledgments.\n\nReplace this content with your own appendix material. Subsequent\nappendices land as `99-appendix-b.md`, `99-appendix-c.md`, … — the\nfoundation enumerates them A / B / C automatically based on document\norder, so the filenames just need to sort in the order you want.\n",
|
|
99
|
-
"document.yml.hbs": "name: \"{{title}}\"\nfoundation: '@uniweb/book@0.
|
|
105
|
+
"document.yml.hbs": "name: \"{{title}}\"\nfoundation: '@uniweb/book@0.4.1'\nformat: latex\n\nbook:\n # Selects the UofT-shaped LaTeX template. Requires `tlmgr install\n # ut-thesis` on your TeX install (the community-maintained UofT SGS-\n # compliant class on CTAN). Comment this out to use the generic book\n # template if you don't want the institution-specific layout —\n # everything else still works.\n kind: thesis-uoft\n title: \"{{title}}\"\n author: \"{{author}}\"\n language: en\n rights: \"© {{year}} {{author}}.\"\n # Citation style for inline cites and the back-matter bibliography.\n # Pick from: chicago-author-date (the humanities default), apa, mla,\n # harvard, ieee, vancouver, ama, nature, science. Switch the\n # document's whole bibliographic apparatus by changing this one\n # value; nothing else in the project needs editing. biblatex picks\n # up the change automatically on the next compile.\n citationStyle: ieee\n bibliography:\n sortBy: author\n\n# Bibliographic records. Drop a `.bib` file into collections/bibliography/\n# and every `@entry{key, ...}` becomes one record. YAML / JSON files in\n# the same directory are merged in.\ncollections:\n bibliography:\n path: collections/bibliography\n\n# Structured thesis metadata read by the TitlePage section type.\n# Edit these four-five values to your own thesis; the rest of the\n# title-page text is institutional boilerplate handled by the\n# foundation.\nthesis:\n title: \"{{title}}\"\n candidate:\n name: \"{{author}}\"\n degree:\n level: M.Sc. # or Ph.D., M.A., M.Eng.\n field: Computer Science\n department: Department of Computer Science\n institution: University of Toronto\n year: {{year}}\n\ncontent:\n - 00-titlepage\n - 01-abstract\n - 02-acknowledgments\n - 03-list-of-figures\n - 10-introduction\n - 11-preliminaries\n - 12-main-result\n - 12-main-result-theorem\n - 12-main-result-proof\n - 13-conclusion\n - 90-references\n - 99-appendix-a\n",
|
|
100
106
|
},
|
|
101
107
|
}
|