@airmark/quiver 0.35.7 → 0.35.8
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/package.json +4 -4
- package/quills/af4141/0.1.0/Quill.yaml +39 -49
- package/quills/af4141/0.1.0/plate.typ +7 -7
- package/quills/afmc_moa/0.0.1/Quill.yaml +126 -153
- package/quills/afmc_moa/0.0.1/plate.typ +11 -11
- package/quills/classic_resume/0.1.0/Quill.yaml +34 -40
- package/quills/classic_resume/0.1.0/plate.typ +11 -11
- package/quills/cyber_career_plan/0.1.0/Quill.yaml +258 -287
- package/quills/cyber_career_plan/0.1.0/plate.typ +92 -70
- package/quills/daf1206/0.1.0/plate.typ +18 -18
- package/quills/daf4392/0.1.0/Quill.yaml +33 -38
- package/quills/daf4392/0.1.0/plate.typ +16 -16
- package/quills/usaf_letter/0.1.0/Quill.yaml +143 -158
- package/quills/usaf_letter/0.1.0/plate.typ +26 -28
- package/quills/usaf_memo/0.2.0/Quill.yaml +217 -247
- package/quills/usaf_memo/0.2.0/plate.typ +28 -28
- package/quills/usaf_memo/0.3.0/Quill.yaml +219 -245
- package/quills/usaf_memo/0.3.0/plate.typ +35 -36
- package/templates/cyber_career_plan.md +56 -0
- package/templates/templates.json +7 -0
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
#import "@local/quillmark-helper:0.
|
|
2
|
-
data, display, form-field, signature-field,
|
|
3
|
-
)
|
|
1
|
+
#import "@local/quillmark-helper:0.2.0" as qm
|
|
4
2
|
#import "@local/tonguetoquill-usaf-memo:5.0.0": (
|
|
5
3
|
backmatter, date-pattern, frontmatter, indorsement, mainmatter,
|
|
6
4
|
)
|
|
7
5
|
|
|
8
6
|
// A memo has no "no style" state, so the blank takes the package's default.
|
|
9
7
|
// Resolved once here: `frontmatter` and the date's pattern must agree.
|
|
10
|
-
#let memo_style = if data.memo_style != "" { data.memo_style } else { "usaf" }
|
|
8
|
+
#let memo_style = if qm.data.memo_style != "" { qm.data.memo_style } else { "usaf" }
|
|
11
9
|
|
|
12
10
|
// The letterhead arrives as one ordered array of lines. Element 0 is the title,
|
|
13
11
|
// set larger; the rest are caption lines beneath it. A single element renders
|
|
14
12
|
// the title alone — the package skips a falsey caption. An empty array renders
|
|
15
13
|
// no letterhead text at all, silently: the block is `place`d, so an empty title
|
|
16
14
|
// leaves no glyphs and takes no space from the flow, and the seal stands alone.
|
|
17
|
-
#let letterhead_lines = data.letterhead_title
|
|
15
|
+
#let letterhead_lines = qm.data.letterhead_title
|
|
18
16
|
|
|
19
17
|
// Body text size, in points. Also the height of a blank date's fill-in widget.
|
|
20
|
-
#let body_font_size = data.font_size * 1pt
|
|
18
|
+
#let body_font_size = qm.data.font_size * 1pt
|
|
21
19
|
|
|
22
20
|
// A blank date's fill-in slot: an empty AcroForm text box the signer types the
|
|
23
21
|
// date into, set in the body face, the value flush right in the date line's
|
|
@@ -34,7 +32,7 @@
|
|
|
34
32
|
#let date_slot(name, field) = context box(
|
|
35
33
|
width: date_slot_width,
|
|
36
34
|
height: measure[0].height,
|
|
37
|
-
place(horizon + left, form-field(
|
|
35
|
+
place(horizon + left, qm.form-field(
|
|
38
36
|
name,
|
|
39
37
|
type: "text",
|
|
40
38
|
width: date_slot_width,
|
|
@@ -49,10 +47,10 @@
|
|
|
49
47
|
#show: frontmatter.with(
|
|
50
48
|
letterhead-title: letterhead_lines.at(0, default: ""),
|
|
51
49
|
letterhead-caption: if letterhead_lines.len() > 1 { letterhead_lines.slice(1) } else { () },
|
|
52
|
-
letterhead-seal-subtitle: data.letterhead_seal_subtitle,
|
|
50
|
+
letterhead-seal-subtitle: qm.data.letterhead_seal_subtitle,
|
|
53
51
|
// A memo has no seal-less state, so the blank takes the default, DoW.
|
|
54
52
|
letterhead-seal: image(
|
|
55
|
-
if data.letterhead_seal == "dod" {
|
|
53
|
+
if qm.data.letterhead_seal == "dod" {
|
|
56
54
|
"assets/dod_seal.png"
|
|
57
55
|
} else {
|
|
58
56
|
"assets/dow_seal.png"
|
|
@@ -61,44 +59,44 @@
|
|
|
61
59
|
|
|
62
60
|
// Date. `data.date` is the native `datetime` and would render identically,
|
|
63
61
|
// but its ink would be born inside the package and carry no schema address.
|
|
64
|
-
// `
|
|
62
|
+
// `qm.ink` places the field's *content* projection instead: the glyphs are
|
|
65
63
|
// born in the generated helper, so the memo date stays click-to-edit however
|
|
66
64
|
// deep the package formats it.
|
|
67
65
|
//
|
|
68
66
|
// A blank date is left for the signer: a memo is dated when it is signed,
|
|
69
67
|
// which is generally not when it is rendered, so the slot is fillable rather
|
|
70
68
|
// than stamped with the compile date.
|
|
71
|
-
date:
|
|
69
|
+
date: qm.ink(qm.data, "date", date-pattern(memo-style: memo_style)),
|
|
72
70
|
date-field: date_slot("Date", "date"),
|
|
73
71
|
|
|
74
|
-
memo-for: data.memo_for,
|
|
72
|
+
memo-for: qm.data.memo_for,
|
|
75
73
|
|
|
76
74
|
// A memo with no FROM line is a Memorandum for Record.
|
|
77
|
-
..if data.memo_from.len() > 0 { (memo-from: data.memo_from) },
|
|
75
|
+
..if qm.data.memo_from.len() > 0 { (memo-from: qm.data.memo_from) },
|
|
78
76
|
|
|
79
|
-
subject: data.subject,
|
|
77
|
+
subject: qm.data.subject,
|
|
80
78
|
|
|
81
|
-
..if data.references.len() > 0 { (references: data.references) },
|
|
79
|
+
..if qm.data.references.len() > 0 { (references: qm.data.references) },
|
|
82
80
|
|
|
83
|
-
footer-tag-line: data.tag_line,
|
|
81
|
+
footer-tag-line: qm.data.tag_line,
|
|
84
82
|
|
|
85
83
|
// The blank reads as no banner, which is what the package's own
|
|
86
84
|
// `classification-level: none` default means.
|
|
87
|
-
classification-level: data.classification.value,
|
|
85
|
+
classification-level: qm.data.classification.value,
|
|
88
86
|
|
|
89
|
-
dissemination: data.dissemination,
|
|
87
|
+
dissemination: qm.data.dissemination,
|
|
90
88
|
|
|
91
89
|
// CUI designation indicator block fields (DoDM 5200.48). `classification`
|
|
92
90
|
// declares a `CUI` variant, so these four exist only where the discriminant
|
|
93
91
|
// reads CUI, and the branch is what makes reading them total: inside it every
|
|
94
92
|
// declared field of that world is present, outside it none is. The package's
|
|
95
93
|
// own `cui_*: none` defaults cover the worlds that omit them.
|
|
96
|
-
..if data.classification.value == "CUI" {
|
|
94
|
+
..if qm.data.classification.value == "CUI" {
|
|
97
95
|
(
|
|
98
|
-
cui-controlled-by: data.classification.controlled_by,
|
|
99
|
-
cui-category: data.classification.category,
|
|
100
|
-
cui-limited-dissemination: data.classification.limited_dissemination,
|
|
101
|
-
cui-poc: data.classification.poc,
|
|
96
|
+
cui-controlled-by: qm.data.classification.controlled_by,
|
|
97
|
+
cui-category: qm.data.classification.category,
|
|
98
|
+
cui-limited-dissemination: qm.data.classification.limited_dissemination,
|
|
99
|
+
cui-poc: qm.data.classification.poc,
|
|
102
100
|
)
|
|
103
101
|
},
|
|
104
102
|
|
|
@@ -115,26 +113,26 @@
|
|
|
115
113
|
// the rebuilt glyphs keep their spans, which is what the backend reads regions
|
|
116
114
|
// from.
|
|
117
115
|
#mainmatter[
|
|
118
|
-
#data.at("$body")
|
|
116
|
+
#qm.data.at("$body")
|
|
119
117
|
]
|
|
120
118
|
|
|
121
119
|
#backmatter(
|
|
122
|
-
authority-line: data.authority_line,
|
|
123
|
-
signature-block: data.signature_block,
|
|
120
|
+
authority-line: qm.data.authority_line,
|
|
121
|
+
signature-block: qm.data.signature_block,
|
|
124
122
|
// The widget sits at the bottom of AFH 33-337's four blank lines and is
|
|
125
123
|
// sized to two and a half of them, so the line and a half above it stays
|
|
126
124
|
// clear of the body text without moving the block off the fifth line.
|
|
127
|
-
signing-field: signature-field(
|
|
125
|
+
signing-field: qm.signature-field(
|
|
128
126
|
"Signature",
|
|
129
127
|
field: "signature_block",
|
|
130
128
|
height: body_font_size * 2.5,
|
|
131
129
|
),
|
|
132
130
|
|
|
133
|
-
..if data.cc.len() > 0 { (cc: data.cc) },
|
|
131
|
+
..if qm.data.cc.len() > 0 { (cc: qm.data.cc) },
|
|
134
132
|
|
|
135
|
-
..if data.distribution.len() > 0 { (distribution: data.distribution) },
|
|
133
|
+
..if qm.data.distribution.len() > 0 { (distribution: qm.data.distribution) },
|
|
136
134
|
|
|
137
|
-
..if data.attachments.len() > 0 { (attachments: data.attachments) },
|
|
135
|
+
..if qm.data.attachments.len() > 0 { (attachments: qm.data.attachments) },
|
|
138
136
|
)
|
|
139
137
|
|
|
140
138
|
// The action line's wording follows the indorsement's place in the chain, not
|
|
@@ -149,7 +147,7 @@
|
|
|
149
147
|
// expression ends at a line break stops there, and the next line's leading
|
|
150
148
|
// `.at(…)` would be read as text.
|
|
151
149
|
#let last_indorsement_index = (
|
|
152
|
-
data
|
|
150
|
+
qm.data
|
|
153
151
|
.at("$cards")
|
|
154
152
|
.enumerate()
|
|
155
153
|
.filter(entry => entry.at(1).at("$kind", default: none) == "indorsement")
|
|
@@ -157,7 +155,7 @@
|
|
|
157
155
|
.at(-1, default: -1)
|
|
158
156
|
)
|
|
159
157
|
|
|
160
|
-
#for (i, card) in data.at("$cards").enumerate() {
|
|
158
|
+
#for (i, card) in qm.data.at("$cards").enumerate() {
|
|
161
159
|
if card.at("$kind", default: none) == "indorsement" {
|
|
162
160
|
// The quillmark helper leaves an unset/whitespace-only markdown body as
|
|
163
161
|
// the empty string `""`; only non-empty bodies are eval'd into content.
|
|
@@ -169,11 +167,12 @@
|
|
|
169
167
|
// it (distinct from the originating memo's date). The signing date is
|
|
170
168
|
// generally unknown at compile time, so a blank or omitted date leaves the
|
|
171
169
|
// date slot fillable rather than stamping the compile date into it.
|
|
172
|
-
// `
|
|
170
|
+
// `qm.ink` reads the card's own address, so a per-card call yields a per-card region
|
|
173
171
|
// even though every iteration shares one `card` loop variable, and it
|
|
174
172
|
// returns `none` for a blank date — the fill-in case below.
|
|
175
|
-
let resolved_date =
|
|
176
|
-
card
|
|
173
|
+
let resolved_date = qm.ink(
|
|
174
|
+
card,
|
|
175
|
+
"date",
|
|
177
176
|
date-pattern(memo-style: memo_style),
|
|
178
177
|
)
|
|
179
178
|
// The card's `$path` prefix composes its canonical schema addresses
|
|
@@ -187,7 +186,7 @@
|
|
|
187
186
|
to: card.at("for", default: ""),
|
|
188
187
|
authority-line: card.authority_line,
|
|
189
188
|
signature-block: card.signature_block,
|
|
190
|
-
signing-field: signature-field(
|
|
189
|
+
signing-field: qm.signature-field(
|
|
191
190
|
"Ind_" + str(i) + "_Signature",
|
|
192
191
|
field: card.at("$path") + "signature_block",
|
|
193
192
|
height: body_font_size * 2.5,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
~~~
|
|
2
|
+
$quill: cyber_career_plan@0.1
|
|
3
|
+
$kind: main
|
|
4
|
+
name: Capt John A. Snuffy
|
|
5
|
+
afsc: 17S3
|
|
6
|
+
duty_title: Instructor
|
|
7
|
+
unit: 333 TRS, Keesler AFB
|
|
8
|
+
date_arrived_station: 2025-06-15
|
|
9
|
+
move_year: 2028
|
|
10
|
+
move_cycle: Summer
|
|
11
|
+
commissioning_yg: 2022
|
|
12
|
+
date_of_rank: 2026-05-01
|
|
13
|
+
advanced_degree: MS Cyber Operations, AFIT
|
|
14
|
+
constraints:
|
|
15
|
+
- { note: "Join-spouse, no OCONUS", through: 2029 }
|
|
16
|
+
vectors:
|
|
17
|
+
- track: Vector 1
|
|
18
|
+
tours:
|
|
19
|
+
- { title: 333 TRS/DO, years: 2 }
|
|
20
|
+
- { title: IDE in residence, years: 1, school: true }
|
|
21
|
+
- { title: "Sq/CC, Scott AFB", years: 2 }
|
|
22
|
+
- track: Vector 2
|
|
23
|
+
tours:
|
|
24
|
+
- { title: 17X CFM Staff, years: 2.5 }
|
|
25
|
+
- { title: MAJCOM A3 Division Chief, years: 2 }
|
|
26
|
+
- track: Vector 3
|
|
27
|
+
tours:
|
|
28
|
+
- { title: Joint Staff J6, years: 3 }
|
|
29
|
+
- { title: USCYBERCOM JFHQ-C, years: 2.5 }
|
|
30
|
+
stratifications:
|
|
31
|
+
- { year: 2026, rater: "#1/14 Capts", hlr: "#1/45 Capts" }
|
|
32
|
+
- { year: 2025, rater: "#2/12 Lts", hlr: "#3/40 CGOs" }
|
|
33
|
+
- { year: 2024, rater: "#1/8 Lts, #2/40 Instructors" }
|
|
34
|
+
awards:
|
|
35
|
+
- Wing CGOQ x2
|
|
36
|
+
- HAF A2/6 CGOY 2025
|
|
37
|
+
certifications:
|
|
38
|
+
- SANS FOR577 (GLIR)
|
|
39
|
+
- CISSP
|
|
40
|
+
- Security+
|
|
41
|
+
deployments:
|
|
42
|
+
- 1x short tour, returned Jan 2024
|
|
43
|
+
qualifications:
|
|
44
|
+
operations:
|
|
45
|
+
dodin_ops: true
|
|
46
|
+
dco: { detail: 23 IOS }
|
|
47
|
+
crew_cc: true
|
|
48
|
+
staff:
|
|
49
|
+
instructor: { detail: 333 TRS }
|
|
50
|
+
education:
|
|
51
|
+
sos: { detail: DG }
|
|
52
|
+
cyber_200: { detail: "2025" }
|
|
53
|
+
timeline_start_year: 2026
|
|
54
|
+
~~~
|
|
55
|
+
|
|
56
|
+
What I want out of this discussion: the DO seat at the 333 TRS when my instructor tour ends, then IDE in residence. I'd rather not go to staff before I've had a DO job.
|
package/templates/templates.json
CHANGED
|
@@ -61,5 +61,12 @@
|
|
|
61
61
|
"description": "Classic single-page resume",
|
|
62
62
|
"file": "classic_resume.md",
|
|
63
63
|
"tags": ["resume", "career"]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "cyber-career-plan",
|
|
67
|
+
"name": "Cyber Career Plan",
|
|
68
|
+
"description": "17X Cyber Operations Officer career plan: current assignment, vectors and record",
|
|
69
|
+
"file": "cyber_career_plan.md",
|
|
70
|
+
"tags": ["career", "cyber", "development"]
|
|
64
71
|
}
|
|
65
72
|
]
|