@airmark/quiver 0.27.0 → 0.28.0
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 +79 -31
- package/package.json +7 -5
- package/quills/af4141/0.1.0/Quill.yaml +21 -11
- package/quills/af4141/0.1.0/packages/typst-af4141/lib.typ +17 -3
- package/quills/af4141/0.1.0/plate.typ +11 -4
- package/quills/daf1206/0.1.0/Quill.yaml +3 -1
- package/quills/daf4392/0.1.0/Quill.yaml +12 -9
- package/quills/daf4392/0.1.0/packages/daf4392page2_pkg/lib.typ +21 -4
- package/quills/usaf_memo/0.2.0/Quill.yaml +46 -33
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/utils.typ +14 -5
- package/quills/usaf_memo/0.2.0/plate.typ +15 -3
- package/quills/af4141/0.1.0/design/TASK.md +0 -20
- package/quills/af4141/0.1.0/design/af4141.pdf +0 -0
- package/quills/daf1206/0.1.0/DAFForm_1206_prepared.pdf +0 -0
- package/quills/usaf_memo/0.2.0/.quillignore +0 -31
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @airmark/quiver
|
|
2
2
|
|
|
3
|
-
A [Quillmark](https://github.com/nibsbin/quillmark)
|
|
4
|
-
and DAF official-document
|
|
3
|
+
A [Quillmark](https://github.com/nibsbin/quillmark) source quiver of Air Force
|
|
4
|
+
and DAF official-document quills, sourced from
|
|
5
5
|
[tonguetoquill-collection](https://github.com/nibsbin/tonguetoquill-collection)
|
|
6
6
|
and aligned to the current Quillmark spec.
|
|
7
7
|
|
|
@@ -20,28 +20,31 @@ and aligned to the current Quillmark spec.
|
|
|
20
20
|
npm install @airmark/quiver @quillmark/quiver @quillmark/wasm
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
This package ships only the
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
over HTTP.
|
|
23
|
+
This package ships only the source-quiver assets (`Quiver.yaml` + `quills/`) and
|
|
24
|
+
exposes no JavaScript API. Loading is `@quillmark/quiver`'s, rendering is
|
|
25
|
+
`@quillmark/wasm`'s.
|
|
27
26
|
|
|
28
27
|
## Usage
|
|
29
28
|
|
|
30
|
-
### Load from the
|
|
29
|
+
### Load from the installed directory (Node)
|
|
31
30
|
|
|
32
|
-
Resolve
|
|
33
|
-
|
|
31
|
+
Resolve this package's `Quiver.yaml` from your own module — your dependencies are
|
|
32
|
+
reachable from you, not from the loader's install location — and hand its
|
|
33
|
+
directory to `fromDir`:
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
import {
|
|
37
|
-
import
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
36
|
+
import { createRequire } from 'node:module';
|
|
37
|
+
import { dirname } from 'node:path';
|
|
38
|
+
import { Engine, init } from '@quillmark/wasm';
|
|
39
|
+
import { fromDir } from '@quillmark/quiver/node';
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
const
|
|
41
|
+
// `init` is the only door to `Document`: there is no static export to reach early.
|
|
42
|
+
const { Document } = await init();
|
|
43
|
+
|
|
44
|
+
const root = dirname(createRequire(import.meta.url).resolve('@airmark/quiver/Quiver.yaml'));
|
|
45
|
+
const quiver = await fromDir(root);
|
|
46
|
+
const engine = new Engine();
|
|
43
47
|
|
|
44
|
-
const engine = new Quillmark();
|
|
45
48
|
const doc = Document.fromMarkdown(`~~~
|
|
46
49
|
$quill: usaf_memo@0.2
|
|
47
50
|
$kind: main
|
|
@@ -53,19 +56,31 @@ signature_block: ["FIRST M. LAST, Rank, USAF", "Duty Title"]
|
|
|
53
56
|
|
|
54
57
|
Body of the memo.`);
|
|
55
58
|
|
|
56
|
-
const quill = await quiver.getQuill(doc.quillRef
|
|
57
|
-
const { artifacts } =
|
|
59
|
+
const quill = await quiver.getQuill(doc.quillRef);
|
|
60
|
+
const { artifacts } = await engine.render(quill, doc, { format: 'pdf' });
|
|
58
61
|
```
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
A quill from `getQuill` is borrowed: every caller asking for that ref gets the
|
|
64
|
+
same instance, so it is not yours to `free()`.
|
|
65
|
+
|
|
66
|
+
### Load over HTTP (browser)
|
|
67
|
+
|
|
68
|
+
A browser cannot read the source layout, so pack it at deploy time and serve the
|
|
69
|
+
output as static files:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { build } from '@quillmark/quiver/node'; // build step
|
|
73
|
+
await build(root, './public/quivers/airmark');
|
|
74
|
+
```
|
|
61
75
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
```ts
|
|
77
|
+
import { Quiver } from '@quillmark/quiver'; // browser runtime
|
|
78
|
+
const quiver = await Quiver.fromBuiltUrl('/quivers/airmark/');
|
|
79
|
+
```
|
|
65
80
|
|
|
66
81
|
## Layout
|
|
67
82
|
|
|
68
|
-
This package is a [
|
|
83
|
+
This package is a [source quiver](https://www.npmjs.com/package/@quillmark/quiver)
|
|
69
84
|
conforming to the canonical layout:
|
|
70
85
|
|
|
71
86
|
```
|
|
@@ -79,20 +94,53 @@ quills/
|
|
|
79
94
|
packages/
|
|
80
95
|
```
|
|
81
96
|
|
|
82
|
-
##
|
|
97
|
+
## Working on a quill
|
|
83
98
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`
|
|
99
|
+
Two verbs, and they answer different questions.
|
|
100
|
+
|
|
101
|
+
Both verbs are [`quillkit`](https://www.npmjs.com/package/quillkit)'s, the quill
|
|
102
|
+
author's toolchain. It resolves `@quillmark/quiver` and `@quillmark/wasm` out of
|
|
103
|
+
this collection's own `node_modules`, so the versions pinned here are the format
|
|
104
|
+
the quiver is packed in and the wasm the gate renders through.
|
|
105
|
+
|
|
106
|
+
**Does it work?** `quillkit test` loads the collection with `fromDir`, compiles
|
|
107
|
+
every quill, and renders each one's example document — the blueprint seeded from
|
|
108
|
+
the `example:` values in `Quill.yaml`. It is the gate CI runs, so a validation
|
|
109
|
+
failure surfaces here rather than on a consumer's build:
|
|
89
110
|
|
|
90
111
|
```bash
|
|
91
112
|
npm install
|
|
92
113
|
npm test
|
|
93
114
|
```
|
|
94
115
|
|
|
116
|
+
The gate renders nothing the schema did not write, so the coverage is the
|
|
117
|
+
`example:` block: a field with no example is a field no render exercises.
|
|
118
|
+
|
|
119
|
+
**What is it like to use?** `npm run dev` is `quillkit studio`: it packs this
|
|
120
|
+
quiver, serves the studio client over it, and repacks on every save — pick a
|
|
121
|
+
quill, edit the seeded document, watch it paint, read the diagnostics. The
|
|
122
|
+
client is quillkit's own, so there is nothing to install for it and nothing to
|
|
123
|
+
keep in step.
|
|
124
|
+
|
|
125
|
+
Reload to pick up a repack. Editing a quill into an invalid state is not a
|
|
126
|
+
failure of the loop: it packs, and the client says what is wrong with that quill
|
|
127
|
+
and where, while the others stay usable.
|
|
128
|
+
|
|
129
|
+
The two verbs answer to different authorities. `quillkit test` renders through
|
|
130
|
+
the wasm pinned here and is what CI blocks on; the client renders through the
|
|
131
|
+
wasm it was built against, and nothing at runtime reconciles the two. The gate
|
|
132
|
+
is authoritative, studio is advisory.
|
|
133
|
+
|
|
134
|
+
`npm run site` writes the arrangement a deploy serves into `site/` — the client
|
|
135
|
+
at the root, a built quiver at `quiver/` beneath it — without serving it. CI
|
|
136
|
+
uploads that on every run, so a pull request — a fork's included — is reviewed
|
|
137
|
+
by downloading the artifact and serving the directory.
|
|
138
|
+
|
|
139
|
+
`main` is deployed to GitHub Pages by `.github/workflows/studio.yml`, which runs
|
|
140
|
+
that same command and keeps the deploy here: nothing outside this repository
|
|
141
|
+
holds `pages: write`.
|
|
142
|
+
|
|
95
143
|
## License
|
|
96
144
|
|
|
97
|
-
Apache-2.0. Individual
|
|
98
|
-
`packages/` directory inside each
|
|
145
|
+
Apache-2.0. Individual quills carry their own licensing terms; see the
|
|
146
|
+
`packages/` directory inside each quill for upstream font and template licenses.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@airmark/quiver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Airmark Quiver — Air Force and DAF document Quills for Quillmark.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
"node": ">=24"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@quillmark/quiver": "0.
|
|
20
|
-
"@quillmark/wasm": "^0.
|
|
19
|
+
"@quillmark/quiver": "0.21.0",
|
|
20
|
+
"@quillmark/wasm": "^0.103.0",
|
|
21
|
+
"quillkit": "0.2.0"
|
|
21
22
|
},
|
|
22
23
|
"scripts": {
|
|
23
|
-
"test": "
|
|
24
|
-
"
|
|
24
|
+
"test": "quillkit test",
|
|
25
|
+
"site": "quillkit site --out site",
|
|
26
|
+
"dev": "quillkit studio"
|
|
25
27
|
},
|
|
26
28
|
"keywords": [
|
|
27
29
|
"quillmark",
|
|
@@ -2,9 +2,11 @@ quill:
|
|
|
2
2
|
name: af4141
|
|
3
3
|
version: 0.1.0
|
|
4
4
|
backend: typst
|
|
5
|
-
plate_file: plate.typ
|
|
6
5
|
description: "AF Form 4141 — Individual's Record of Duties and Experience, Ground Environment Personnel"
|
|
7
6
|
|
|
7
|
+
typst:
|
|
8
|
+
plate_file: plate.typ
|
|
9
|
+
|
|
8
10
|
main:
|
|
9
11
|
body:
|
|
10
12
|
enabled: false
|
|
@@ -36,58 +38,66 @@ card_kinds:
|
|
|
36
38
|
description: Each card represents one row in the Record of Experience table. Rows fill page 1 (up to 16 rows) then page 2 (up to 21 rows), for a maximum of 37 rows.
|
|
37
39
|
ui:
|
|
38
40
|
title: Record of Experience
|
|
41
|
+
groups: [record]
|
|
39
42
|
body:
|
|
40
43
|
enabled: false
|
|
41
44
|
fields:
|
|
42
45
|
date:
|
|
43
|
-
type:
|
|
46
|
+
type: date
|
|
44
47
|
default: ""
|
|
48
|
+
example: "2026-03-14"
|
|
45
49
|
ui:
|
|
46
|
-
group:
|
|
50
|
+
group: record
|
|
47
51
|
description: Date of the action (column A).
|
|
48
52
|
|
|
49
53
|
action:
|
|
50
54
|
type: string
|
|
51
55
|
default: ""
|
|
56
|
+
example: Upgraded to 5-level; assumed ADO duties
|
|
52
57
|
ui:
|
|
53
|
-
group:
|
|
58
|
+
group: record
|
|
54
59
|
description: One type of action per line (column B).
|
|
55
60
|
|
|
56
61
|
written_grade:
|
|
57
62
|
type: string
|
|
58
63
|
default: ""
|
|
64
|
+
example: "3"
|
|
59
65
|
ui:
|
|
60
|
-
group:
|
|
66
|
+
group: record
|
|
61
67
|
compact: true
|
|
62
68
|
description: Written grade, if applicable (column C).
|
|
63
69
|
|
|
64
70
|
written_grade_date:
|
|
65
|
-
type:
|
|
71
|
+
type: date
|
|
66
72
|
default: ""
|
|
73
|
+
example: "2026-02-28"
|
|
67
74
|
ui:
|
|
68
|
-
group:
|
|
75
|
+
group: record
|
|
69
76
|
compact: true
|
|
70
77
|
description: Date of written grade (column D). Omit or leave blank when there is no written grade.
|
|
71
78
|
|
|
72
79
|
positional_grade:
|
|
73
80
|
type: string
|
|
74
81
|
default: ""
|
|
82
|
+
example: "2"
|
|
75
83
|
ui:
|
|
76
|
-
group:
|
|
84
|
+
group: record
|
|
77
85
|
compact: true
|
|
78
86
|
description: Positional grade, if applicable (column E).
|
|
79
87
|
|
|
80
88
|
positional_grade_date:
|
|
81
|
-
type:
|
|
89
|
+
type: date
|
|
82
90
|
default: ""
|
|
91
|
+
example: "2026-03-14"
|
|
83
92
|
ui:
|
|
84
|
-
group:
|
|
93
|
+
group: record
|
|
85
94
|
compact: true
|
|
86
95
|
description: Date of positional grade (column F). Omit or leave blank when there is no positional grade.
|
|
87
96
|
|
|
88
97
|
auth_or_remarks:
|
|
89
98
|
type: string
|
|
90
99
|
default: ""
|
|
100
|
+
example: "J.A.D."
|
|
91
101
|
ui:
|
|
92
|
-
group:
|
|
102
|
+
group: record
|
|
93
103
|
description: Authentication or remarks entry (column G).
|
|
@@ -11,7 +11,15 @@
|
|
|
11
11
|
/// True for short/narrow fields with brief content (grades, ranks, dates).
|
|
12
12
|
#let should-shrink-to-fit(display, width, height) = {
|
|
13
13
|
let aspect = width / height
|
|
14
|
-
|
|
14
|
+
// A date field's content is a single `text` element, and the string inside it
|
|
15
|
+
// is what decides the shrink, as a plain value's own does. Any other content
|
|
16
|
+
// (a styled label, a sequence) has no such string and counts as long, so only
|
|
17
|
+
// the dimension conditions apply.
|
|
18
|
+
let char-count = if type(display) == str {
|
|
19
|
+
display.len()
|
|
20
|
+
} else {
|
|
21
|
+
display.at("text", default: "0" * 99).len()
|
|
22
|
+
}
|
|
15
23
|
char-count <= 10 or height < 20pt or aspect > 4.0
|
|
16
24
|
}
|
|
17
25
|
|
|
@@ -81,8 +89,14 @@
|
|
|
81
89
|
/// - field (dictionary): raw field entry from the schema
|
|
82
90
|
#let render-field(field-type, value, width, height, field) = {
|
|
83
91
|
if field-type == "text" {
|
|
84
|
-
if value != none
|
|
85
|
-
|
|
92
|
+
if value != none {
|
|
93
|
+
// A date field arrives as content carrying its click-to-edit region, so it
|
|
94
|
+
// is placed as it stands; a plain string is placed only when non-empty.
|
|
95
|
+
if type(value) == content {
|
|
96
|
+
render-text-field(value, width, height, 1.5pt, 1pt)
|
|
97
|
+
} else if str(value) != "" {
|
|
98
|
+
render-text-field(str(value), width, height, 1.5pt, 1pt)
|
|
99
|
+
}
|
|
86
100
|
}
|
|
87
101
|
} else if field-type == "checkbox" {
|
|
88
102
|
if value == true {
|
|
@@ -3,13 +3,20 @@
|
|
|
3
3
|
|
|
4
4
|
#set text(font: "NimbusRomNo9L")
|
|
5
5
|
|
|
6
|
-
// `type: date`
|
|
7
|
-
//
|
|
6
|
+
// A `type: date` field lowers to a click-to-edit value object
|
|
7
|
+
// `(value: datetime, display: closure)`; `display` is a stored closure on a dict
|
|
8
|
+
// rather than a method, so it is called through parentheses, and the content it
|
|
9
|
+
// returns carries the region that makes the glyphs clickable. A native datetime
|
|
10
|
+
// is still accepted. Missing / blank dates arrive as `none`; empty strings may
|
|
11
|
+
// also appear from input.
|
|
8
12
|
#let form-cell(v) = {
|
|
13
|
+
let pattern = "[month padding:none]/[day padding:none]/[year]"
|
|
9
14
|
if v == none { "" }
|
|
10
15
|
else if type(v) == str and v == "" { "" }
|
|
11
|
-
else if type(v) ==
|
|
12
|
-
v.display(
|
|
16
|
+
else if type(v) == dictionary {
|
|
17
|
+
(v.display)(pattern)
|
|
18
|
+
} else if type(v) == datetime {
|
|
19
|
+
v.display(pattern)
|
|
13
20
|
} else {
|
|
14
21
|
str(v)
|
|
15
22
|
}
|
|
@@ -2,9 +2,11 @@ quill:
|
|
|
2
2
|
name: daf4392
|
|
3
3
|
version: 0.1.0
|
|
4
4
|
backend: typst
|
|
5
|
-
plate_file: plate.typ
|
|
6
5
|
description: "DAF Form 4392 - Pre-Departure Safety Briefing (Page 2)"
|
|
7
6
|
|
|
7
|
+
typst:
|
|
8
|
+
plate_file: plate.typ
|
|
9
|
+
|
|
8
10
|
main:
|
|
9
11
|
body:
|
|
10
12
|
enabled: false
|
|
@@ -16,7 +18,7 @@ main:
|
|
|
16
18
|
description: "Matches the check box."
|
|
17
19
|
|
|
18
20
|
departure_date:
|
|
19
|
-
type:
|
|
21
|
+
type: date
|
|
20
22
|
default: ""
|
|
21
23
|
example: "2026-06-01"
|
|
22
24
|
ui:
|
|
@@ -59,7 +61,7 @@ main:
|
|
|
59
61
|
description: "Unit / Organization."
|
|
60
62
|
|
|
61
63
|
briefed_date:
|
|
62
|
-
type:
|
|
64
|
+
type: date
|
|
63
65
|
default: ""
|
|
64
66
|
example: "2026-05-22"
|
|
65
67
|
ui:
|
|
@@ -96,41 +98,42 @@ card_kinds:
|
|
|
96
98
|
description: "Each card represents one row in the travel itinerary table. Up to 10 rows are supported."
|
|
97
99
|
ui:
|
|
98
100
|
title: Proposed Travel Itinerary
|
|
101
|
+
groups: [itinerary_entry]
|
|
99
102
|
body:
|
|
100
103
|
enabled: false
|
|
101
104
|
fields:
|
|
102
105
|
date:
|
|
103
|
-
type:
|
|
106
|
+
type: date
|
|
104
107
|
default: ""
|
|
105
108
|
ui:
|
|
106
|
-
group:
|
|
109
|
+
group: itinerary_entry
|
|
107
110
|
compact: true
|
|
108
111
|
description: "Date for the itinerary row."
|
|
109
112
|
departure_point:
|
|
110
113
|
type: string
|
|
111
114
|
default: ""
|
|
112
115
|
ui:
|
|
113
|
-
group:
|
|
116
|
+
group: itinerary_entry
|
|
114
117
|
compact: true
|
|
115
118
|
description: "Point of departure."
|
|
116
119
|
arrival_point:
|
|
117
120
|
type: string
|
|
118
121
|
default: ""
|
|
119
122
|
ui:
|
|
120
|
-
group:
|
|
123
|
+
group: itinerary_entry
|
|
121
124
|
compact: true
|
|
122
125
|
description: "Point of arrival."
|
|
123
126
|
rest_length:
|
|
124
127
|
type: string
|
|
125
128
|
default: ""
|
|
126
129
|
ui:
|
|
127
|
-
group:
|
|
130
|
+
group: itinerary_entry
|
|
128
131
|
compact: true
|
|
129
132
|
description: "Duration of stay at arrival point"
|
|
130
133
|
mileage:
|
|
131
134
|
type: string
|
|
132
135
|
default: ""
|
|
133
136
|
ui:
|
|
134
|
-
group:
|
|
137
|
+
group: itinerary_entry
|
|
135
138
|
compact: true
|
|
136
139
|
description: "Approximate mileage for this leg (leave blank if not applicable, e.g., flights)."
|
|
@@ -31,11 +31,22 @@
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/// Format a date value for display as `DD Mon YYYY`.
|
|
34
|
+
///
|
|
35
|
+
/// Dispatches on the shape a date can arrive in:
|
|
36
|
+
/// - dict: a Quillmark `date` field lowers to a click-to-edit value object
|
|
37
|
+
/// `(value: datetime, display: closure)`. `display` is a stored closure on a
|
|
38
|
+
/// dict rather than a method, so it is called through parentheses, and the
|
|
39
|
+
/// content it returns carries the region that makes the glyphs clickable.
|
|
40
|
+
/// - datetime: a native Typst datetime, from a plate building its own value.
|
|
41
|
+
/// - str: shown as-is.
|
|
34
42
|
#let format-date(value) = {
|
|
43
|
+
let pattern = "[day padding:zero] [month repr:short] [year]"
|
|
35
44
|
if value == none {
|
|
36
45
|
none
|
|
46
|
+
} else if type(value) == dictionary {
|
|
47
|
+
(value.display)(pattern)
|
|
37
48
|
} else if type(value) == datetime {
|
|
38
|
-
value.display(
|
|
49
|
+
value.display(pattern)
|
|
39
50
|
} else if type(value) == str and value == "" {
|
|
40
51
|
""
|
|
41
52
|
} else {
|
|
@@ -45,11 +56,17 @@
|
|
|
45
56
|
|
|
46
57
|
/// Should this field shrink text to a single line rather than word-wrap?
|
|
47
58
|
/// True for short/narrow fields with brief content (grades, ranks, dates).
|
|
48
|
-
/// `display` may be a string or content; char-count is skipped for content.
|
|
49
59
|
#let should-shrink-to-fit(display, width, height) = {
|
|
50
60
|
let aspect = width / height
|
|
51
|
-
//
|
|
52
|
-
|
|
61
|
+
// A date field's content is a single `text` element, and the string inside it
|
|
62
|
+
// is what decides the shrink, as a plain value's own does. Any other content
|
|
63
|
+
// (a styled label, a sequence) has no such string and counts as long, so only
|
|
64
|
+
// the dimension conditions apply.
|
|
65
|
+
let char-count = if type(display) == str {
|
|
66
|
+
display.len()
|
|
67
|
+
} else {
|
|
68
|
+
display.at("text", default: "0" * 99).len()
|
|
69
|
+
}
|
|
53
70
|
char-count <= 10 or height < 20pt or aspect > 4.0
|
|
54
71
|
}
|
|
55
72
|
|
|
@@ -2,10 +2,14 @@ quill:
|
|
|
2
2
|
name: usaf_memo
|
|
3
3
|
version: 0.2.0
|
|
4
4
|
backend: typst
|
|
5
|
-
plate_file: plate.typ
|
|
6
5
|
description: U.S. Air Force and Space Force Official Memorandum
|
|
7
6
|
|
|
7
|
+
typst:
|
|
8
|
+
plate_file: plate.typ
|
|
9
|
+
|
|
8
10
|
main:
|
|
11
|
+
ui:
|
|
12
|
+
groups: [addressing, letterhead, classification, additional]
|
|
9
13
|
body:
|
|
10
14
|
example: |
|
|
11
15
|
The first paragraph. Top-level paragraphs are auto-numbered; do not add manual numbering.
|
|
@@ -20,7 +24,8 @@ main:
|
|
|
20
24
|
- ORG1/SYMBOL
|
|
21
25
|
- ORG2/SYMBOL
|
|
22
26
|
ui:
|
|
23
|
-
group:
|
|
27
|
+
group: addressing
|
|
28
|
+
compact: true
|
|
24
29
|
description: "Organization/office symbol in UPPERCASE. To address a specific person, add their rank and name in parentheses (e.g., 'ORG/SYMBOL (LT COL JANE DOE)'). For many recipients, enter 'SEE DISTRIBUTION' and list them in the Distribution field."
|
|
25
30
|
|
|
26
31
|
memo_from:
|
|
@@ -34,14 +39,15 @@ main:
|
|
|
34
39
|
- 123 Street Ave
|
|
35
40
|
- City ST 12345-6789
|
|
36
41
|
ui:
|
|
37
|
-
group:
|
|
42
|
+
group: addressing
|
|
43
|
+
compact: true
|
|
38
44
|
description: "If recipients are on the same installation, use only the office symbol. For recipients on other installations, include the full mailing address to enable return correspondence. Omit entirely to produce a Memorandum for Record (no MEMO FROM line)."
|
|
39
45
|
|
|
40
46
|
subject:
|
|
41
47
|
type: string
|
|
42
48
|
example: Subject of the Memorandum
|
|
43
49
|
ui:
|
|
44
|
-
group:
|
|
50
|
+
group: addressing
|
|
45
51
|
description: Be brief and clear. Capitalize the first letter of each word except articles, prepositions, and conjunctions. Include suspense dates in parentheses if applicable.
|
|
46
52
|
|
|
47
53
|
signature_block:
|
|
@@ -52,14 +58,15 @@ main:
|
|
|
52
58
|
- "FIRST M. LAST, Rank, USAF"
|
|
53
59
|
- Duty Title
|
|
54
60
|
ui:
|
|
55
|
-
group:
|
|
61
|
+
group: addressing
|
|
62
|
+
compact: true
|
|
56
63
|
description: "Line 1: Name in UPPERCASE as signed, grade, and service. Line 2: Duty title."
|
|
57
64
|
|
|
58
65
|
letterhead_title:
|
|
59
66
|
type: string
|
|
60
67
|
default: DEPARTMENT OF THE AIR FORCE
|
|
61
68
|
ui:
|
|
62
|
-
group:
|
|
69
|
+
group: letterhead
|
|
63
70
|
description: Change only for Joint Commands or DoW agencies.
|
|
64
71
|
|
|
65
72
|
letterhead_caption:
|
|
@@ -69,7 +76,7 @@ main:
|
|
|
69
76
|
example:
|
|
70
77
|
- HEADQUARTERS [UNIT NAME]
|
|
71
78
|
ui:
|
|
72
|
-
group:
|
|
79
|
+
group: letterhead
|
|
73
80
|
description: The full organization name of your unit.
|
|
74
81
|
|
|
75
82
|
letterhead_seal:
|
|
@@ -79,7 +86,7 @@ main:
|
|
|
79
86
|
- dod
|
|
80
87
|
default: dow
|
|
81
88
|
ui:
|
|
82
|
-
group:
|
|
89
|
+
group: letterhead
|
|
83
90
|
compact: true
|
|
84
91
|
description: Department of War (DoW) or Department of Defense (DoD) seal shown in the letterhead.
|
|
85
92
|
|
|
@@ -87,15 +94,16 @@ main:
|
|
|
87
94
|
type: string
|
|
88
95
|
default: ""
|
|
89
96
|
ui:
|
|
90
|
-
group:
|
|
97
|
+
group: letterhead
|
|
91
98
|
compact: true
|
|
92
99
|
description: Bold-caps line below the seal; blank to omit.
|
|
93
100
|
|
|
94
101
|
tag_line:
|
|
95
|
-
type:
|
|
102
|
+
type: richtext
|
|
103
|
+
inline: true
|
|
96
104
|
default: ""
|
|
97
105
|
ui:
|
|
98
|
-
group:
|
|
106
|
+
group: letterhead
|
|
99
107
|
compact: true
|
|
100
108
|
description: "Organizational motto at the bottom of the page. Supports Markdown emphasis (e.g. *italics*, **bold**)."
|
|
101
109
|
|
|
@@ -110,7 +118,7 @@ main:
|
|
|
110
118
|
- TOP SECRET
|
|
111
119
|
default: ""
|
|
112
120
|
ui:
|
|
113
|
-
group:
|
|
121
|
+
group: classification
|
|
114
122
|
compact: true
|
|
115
123
|
description: "Select the classification marking shown in the header and footer banner. Unclassified documents typically omit this banner entirely — leave blank unless the document is CUI or classified. Mark per DAFI 16-1404 (DoDM 5200.48 for CUI) and applicable DoD guidance."
|
|
116
124
|
|
|
@@ -118,7 +126,7 @@ main:
|
|
|
118
126
|
type: string
|
|
119
127
|
default: ""
|
|
120
128
|
ui:
|
|
121
|
-
group:
|
|
129
|
+
group: classification
|
|
122
130
|
compact: true
|
|
123
131
|
description: "Banner dissemination suffix appended after double slash (e.g. 'NF' renders as CUI//NF in the header/footer). Leave blank for no suffix."
|
|
124
132
|
|
|
@@ -126,7 +134,7 @@ main:
|
|
|
126
134
|
type: string
|
|
127
135
|
default: ""
|
|
128
136
|
ui:
|
|
129
|
-
group:
|
|
137
|
+
group: classification
|
|
130
138
|
compact: true
|
|
131
139
|
description: "Office or organization that designated this information as CUI (e.g., 'SAF/AA'). Required by DoDM 5200.48 when classification is CUI."
|
|
132
140
|
|
|
@@ -134,7 +142,7 @@ main:
|
|
|
134
142
|
type: string
|
|
135
143
|
default: ""
|
|
136
144
|
ui:
|
|
137
|
-
group:
|
|
145
|
+
group: classification
|
|
138
146
|
compact: true
|
|
139
147
|
description: "CUI category from the DoD CUI Registry (e.g., 'Privacy/MED', 'CTI', 'PRVCY'). Leave blank to omit."
|
|
140
148
|
|
|
@@ -142,7 +150,7 @@ main:
|
|
|
142
150
|
type: string
|
|
143
151
|
default: ""
|
|
144
152
|
ui:
|
|
145
|
-
group:
|
|
153
|
+
group: classification
|
|
146
154
|
compact: true
|
|
147
155
|
description: "Limited dissemination control shown in the indicator block (e.g., 'FEDONLY', 'NOFORN'). Independent from the banner suffix above."
|
|
148
156
|
|
|
@@ -150,20 +158,21 @@ main:
|
|
|
150
158
|
type: string
|
|
151
159
|
default: ""
|
|
152
160
|
ui:
|
|
153
|
-
group:
|
|
161
|
+
group: classification
|
|
154
162
|
compact: true
|
|
155
163
|
description: "Point of contact for CUI questions (e.g., 'Capt J. Smith, DSN 555-1234, john.smith@us.af.mil'). Required by DoDM 5200.48 when classification is CUI."
|
|
156
164
|
|
|
157
165
|
references:
|
|
158
166
|
type: array
|
|
159
167
|
items:
|
|
160
|
-
type:
|
|
168
|
+
type: richtext
|
|
169
|
+
inline: true
|
|
161
170
|
default: []
|
|
162
171
|
example:
|
|
163
172
|
- "AFMAN 33-326, 25 November 2011, *Preparing Official Communications*"
|
|
164
173
|
- "AFI 33-360, *Publications and Forms Management*"
|
|
165
174
|
ui:
|
|
166
|
-
group:
|
|
175
|
+
group: additional
|
|
167
176
|
description: "References, auto-lettered (a), (b), (c) per AFH 33-337. Cite each by organization, type, date, and title; italicize publication titles with *asterisks* (standard Markdown emphasis)."
|
|
168
177
|
|
|
169
178
|
cc:
|
|
@@ -174,7 +183,7 @@ main:
|
|
|
174
183
|
example:
|
|
175
184
|
- Rank and Name, ORG/SYMBOL
|
|
176
185
|
ui:
|
|
177
|
-
group:
|
|
186
|
+
group: additional
|
|
178
187
|
description: List office symbols of recipients to receive copies.
|
|
179
188
|
|
|
180
189
|
distribution:
|
|
@@ -186,7 +195,7 @@ main:
|
|
|
186
195
|
- ORG1/SYMBOL
|
|
187
196
|
- ORG2/SYMBOL
|
|
188
197
|
ui:
|
|
189
|
-
group:
|
|
198
|
+
group: additional
|
|
190
199
|
description: Complete list of recipients if 'SEE DISTRIBUTION' is used in the 'Memo For' field.
|
|
191
200
|
|
|
192
201
|
attachments:
|
|
@@ -197,7 +206,7 @@ main:
|
|
|
197
206
|
example:
|
|
198
207
|
- Attachment description, YYYY MMM DD
|
|
199
208
|
ui:
|
|
200
|
-
group:
|
|
209
|
+
group: additional
|
|
201
210
|
description: List attachments in the order they are mentioned in the memo. Briefly describe each; do not use 'as stated' or abbreviations.
|
|
202
211
|
|
|
203
212
|
memo_style:
|
|
@@ -207,7 +216,7 @@ main:
|
|
|
207
216
|
- daf
|
|
208
217
|
default: usaf
|
|
209
218
|
ui:
|
|
210
|
-
group:
|
|
219
|
+
group: additional
|
|
211
220
|
compact: true
|
|
212
221
|
description: "USAF memorandum or DAF headquarters memorandum; DAF changes date format and body paragraph layout per AFH 33-337."
|
|
213
222
|
|
|
@@ -215,15 +224,15 @@ main:
|
|
|
215
224
|
type: number
|
|
216
225
|
default: 12
|
|
217
226
|
ui:
|
|
218
|
-
group:
|
|
227
|
+
group: additional
|
|
219
228
|
compact: true
|
|
220
229
|
description: Body text size in points; decimals allowed (e.g. 11.5).
|
|
221
230
|
|
|
222
231
|
date:
|
|
223
|
-
type:
|
|
232
|
+
type: date
|
|
224
233
|
default: ""
|
|
225
234
|
ui:
|
|
226
|
-
group:
|
|
235
|
+
group: additional
|
|
227
236
|
compact: true
|
|
228
237
|
description: YYYY-MM-DD. Leave blank to use today's date.
|
|
229
238
|
|
|
@@ -232,18 +241,21 @@ card_kinds:
|
|
|
232
241
|
description: Chain of routing endorsements. Each endorsement block adds an official response or forwarding action to the original memo.
|
|
233
242
|
ui:
|
|
234
243
|
title: Routing indorsement
|
|
244
|
+
groups: [addressing, additional]
|
|
235
245
|
fields:
|
|
236
246
|
from:
|
|
237
247
|
type: string
|
|
238
248
|
example: ORG/SYMBOL
|
|
239
249
|
ui:
|
|
240
|
-
group:
|
|
250
|
+
group: addressing
|
|
251
|
+
compact: true
|
|
241
252
|
description: Office symbol or Rank Name of the endorsing official.
|
|
242
253
|
for:
|
|
243
254
|
type: string
|
|
244
255
|
example: ORG/SYMBOL
|
|
245
256
|
ui:
|
|
246
|
-
group:
|
|
257
|
+
group: addressing
|
|
258
|
+
compact: true
|
|
247
259
|
description: Office symbol or organization receiving the endorsed memo.
|
|
248
260
|
signature_block:
|
|
249
261
|
type: array
|
|
@@ -253,7 +265,8 @@ card_kinds:
|
|
|
253
265
|
- "FIRST M. LAST, Rank, USAF"
|
|
254
266
|
- Duty Title
|
|
255
267
|
ui:
|
|
256
|
-
group:
|
|
268
|
+
group: addressing
|
|
269
|
+
compact: true
|
|
257
270
|
description: "Line 1: Name in UPPERCASE as signed, grade, and service. Line 2: Duty title."
|
|
258
271
|
format:
|
|
259
272
|
type: string
|
|
@@ -263,7 +276,7 @@ card_kinds:
|
|
|
263
276
|
- separate_page
|
|
264
277
|
default: standard
|
|
265
278
|
ui:
|
|
266
|
-
group:
|
|
279
|
+
group: additional
|
|
267
280
|
compact: true
|
|
268
281
|
description: "'standard' (formal, same page), 'informal' (less formal routing), or 'separate_page' (begins on a new page)."
|
|
269
282
|
action:
|
|
@@ -275,13 +288,13 @@ card_kinds:
|
|
|
275
288
|
- disapprove
|
|
276
289
|
default: ""
|
|
277
290
|
ui:
|
|
278
|
-
group:
|
|
291
|
+
group: additional
|
|
279
292
|
compact: true
|
|
280
293
|
description: "Action taken by the endorser. Leave blank to hide the Approve/Disapprove line entirely, 'undecided' to display the line with neither option circled, 'approve' or 'disapprove' to circle the selected option."
|
|
281
294
|
date:
|
|
282
|
-
type:
|
|
295
|
+
type: date
|
|
283
296
|
default: ""
|
|
284
297
|
ui:
|
|
285
|
-
group:
|
|
298
|
+
group: additional
|
|
286
299
|
compact: true
|
|
287
300
|
description: "YYYY-MM-DD. Date the endorser signs/forwards the memo (per AFH 33-337 Ch. 14, this is distinct from the originating memo's date). Leave blank to render a fill-in line for the endorser to date by hand when signing."
|
|
@@ -89,10 +89,15 @@
|
|
|
89
89
|
|
|
90
90
|
/// Formats a date for the memo heading.
|
|
91
91
|
///
|
|
92
|
-
///
|
|
93
|
-
/// -
|
|
94
|
-
///
|
|
95
|
-
///
|
|
92
|
+
/// Dispatches on the shape a date field can arrive in:
|
|
93
|
+
/// - str: shown as-is (fixed text like a placeholder).
|
|
94
|
+
/// - datetime: a native Typst datetime (e.g. `datetime.today()`) — USAF style
|
|
95
|
+
/// `DD Month YYYY`, DAF style `Month DD, YYYY`.
|
|
96
|
+
/// - dict: a Quillmark `date`/`datetime` field lowers to a click-to-edit value
|
|
97
|
+
/// object `{ value: datetime, display: closure }`; `display` is a stored closure
|
|
98
|
+
/// over `.display`, so call it through parentheses — a dict has no `.display` method.
|
|
99
|
+
///
|
|
100
|
+
/// - date (str | datetime | dict): Date to format for display
|
|
96
101
|
/// - memo-style (str): `"usaf"` or `"daf"`
|
|
97
102
|
/// -> content
|
|
98
103
|
#let display-date(date, memo-style: "usaf") = {
|
|
@@ -108,7 +113,11 @@
|
|
|
108
113
|
} else {
|
|
109
114
|
"[day padding:none] [month repr:long] [year]"
|
|
110
115
|
}
|
|
111
|
-
date
|
|
116
|
+
if type(date) == datetime {
|
|
117
|
+
date.display(pattern)
|
|
118
|
+
} else {
|
|
119
|
+
(date.display)(pattern)
|
|
120
|
+
}
|
|
112
121
|
}
|
|
113
122
|
}
|
|
114
123
|
|
|
@@ -54,7 +54,10 @@
|
|
|
54
54
|
memo_for_cols: 1,
|
|
55
55
|
)
|
|
56
56
|
|
|
57
|
-
// Mainmatter
|
|
57
|
+
// Mainmatter. The body's region needs no recovery step here: the package's
|
|
58
|
+
// render-body rebuilds paragraphs through a state buffer (AFH 33-337
|
|
59
|
+
// auto-numbering), but the rebuilt glyphs keep their spans, which is what
|
|
60
|
+
// the backend reads regions from.
|
|
58
61
|
#mainmatter[
|
|
59
62
|
#data.at("$body")
|
|
60
63
|
]
|
|
@@ -63,7 +66,7 @@
|
|
|
63
66
|
#backmatter(
|
|
64
67
|
// Signature block
|
|
65
68
|
signature_block: data.signature_block,
|
|
66
|
-
signing_field: signature-field("Signature"),
|
|
69
|
+
signing_field: signature-field("Signature", field: "signature_block"),
|
|
67
70
|
|
|
68
71
|
// Optional cc
|
|
69
72
|
..if "cc" in data { (cc: data.cc) },
|
|
@@ -94,11 +97,20 @@
|
|
|
94
97
|
} else {
|
|
95
98
|
card_date
|
|
96
99
|
}
|
|
100
|
+
// The card's `$path` prefix composes its canonical schema addresses
|
|
101
|
+
// (`$cards.indorsement.<n>.…`, per-kind ordinal) — the absolute loop
|
|
102
|
+
// index `i` is NOT that ordinal once kinds interleave, so it stays a
|
|
103
|
+
// widget-name suffix only. The card body's region rides its own glyph
|
|
104
|
+
// spans through the package rebuild, per-card because each card's body
|
|
105
|
+
// has its own backend-generated eval site.
|
|
97
106
|
indorsement(
|
|
98
107
|
from: card.at("from", default: ""),
|
|
99
108
|
to: card.at("for", default: ""),
|
|
100
109
|
signature_block: card.signature_block,
|
|
101
|
-
signing_field: signature-field(
|
|
110
|
+
signing_field: signature-field(
|
|
111
|
+
"Ind_" + str(i) + "_Signature",
|
|
112
|
+
field: card.at("$path") + "signature_block",
|
|
113
|
+
),
|
|
102
114
|
format: card.at("format", default: "standard"),
|
|
103
115
|
date: resolved_date,
|
|
104
116
|
..if "action" in card { (action: card.action) },
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
I visually mapped out a bunch of fields on the form to the typst field names. The intent is to write a plate and field schema that interfaces with the Typst form and exposes an ergonomic API to the Quill consumer. See [Creating Quills](https://quillmark.readthedocs.io/en/latest/guides/creating-quills/) to learn how to write the field schema.
|
|
2
|
-
|
|
3
|
-
Note that the following field names are not authortative; use your discretion to name them ergonomically.
|
|
4
|
-
|
|
5
|
-
## Admin Fields
|
|
6
|
-
|
|
7
|
-
Name: `commonforms_text_p1_1`
|
|
8
|
-
Unit: `commonforms_text_p1_2`
|
|
9
|
-
Grade/CCC Level: `commonforms_text_p1_3`
|
|
10
|
-
|
|
11
|
-
## Record of Experience table fields
|
|
12
|
-
|
|
13
|
-
Date: `commonforms_text_p1_{4,7,18,...,109}` and `commonforms_text_p2_{1,8,15,...141}`
|
|
14
|
-
Mandatory Actions to be Reported: `commonforms_text_p1_{5,12,19,...,110}` and `commonforms_text_p2_{2,9,16,...,142}`
|
|
15
|
-
written_grade: `commonforms_text_p1_{6,13,...111}` and `commonforms_text_p2_{3,10,...,143}`
|
|
16
|
-
writen_grade_date: `commonforms_text_p1_{7,14,...,112}` and `commonforms_text_p2_{4,11,144}`
|
|
17
|
-
positional_grade: `commonforms_text_p1_{8,15,...,113}` and `commonforms_text_p2_{5,12,...,145}`
|
|
18
|
-
positional_grade_date: `commonforms_text_p1_{9,...,114}` and
|
|
19
|
-
`commonforms_text_p2_{6,...,145}`
|
|
20
|
-
auth_or_remarks: `commonforms_text_p1_{10,...,115}` and `commonforms_text_p2_{7,...,146}`
|
|
Binary file
|
|
Binary file
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Example .quillignore for usaf_memo template
|
|
2
|
-
# This demonstrates the .quillignore functionality
|
|
3
|
-
|
|
4
|
-
# Ignore temporary and backup files
|
|
5
|
-
*.tmp
|
|
6
|
-
*.bak
|
|
7
|
-
*~
|
|
8
|
-
*.swp
|
|
9
|
-
|
|
10
|
-
# Ignore build artifacts
|
|
11
|
-
target/
|
|
12
|
-
dist/
|
|
13
|
-
build/
|
|
14
|
-
|
|
15
|
-
# Ignore OS-specific files
|
|
16
|
-
.DS_Store
|
|
17
|
-
Thumbs.db
|
|
18
|
-
desktop.ini
|
|
19
|
-
|
|
20
|
-
# Ignore version control
|
|
21
|
-
.git/
|
|
22
|
-
.svn/
|
|
23
|
-
|
|
24
|
-
# Ignore editor files
|
|
25
|
-
.vscode/
|
|
26
|
-
.idea/
|
|
27
|
-
*.sublime-*
|
|
28
|
-
|
|
29
|
-
# Ignore log files
|
|
30
|
-
*.log
|
|
31
|
-
logs/
|