@airmark/quiver 0.14.1 → 0.16.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/package.json +2 -2
- package/quills/daf1206/0.1.0/Quill.yaml +5 -2
- package/quills/usaf_memo/0.2.0/Quill.yaml +8 -4
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/backmatter.typ +2 -1
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/frontmatter.typ +2 -2
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/indorsement.typ +13 -9
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/mainmatter.typ +1 -1
- package/quills/usaf_memo/0.2.0/packages/tonguetoquill-usaf-memo/src/primitives.typ +29 -17
- package/quills/usaf_memo/0.2.0/plate.typ +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@airmark/quiver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Airmark Quiver — Air Force and DAF document Quills for Quillmark.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@quillmark/quiver": "^0.7.0",
|
|
20
|
-
"@quillmark/wasm": "0.
|
|
20
|
+
"@quillmark/wasm": "0.77.0"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "node --test quiver.test.js",
|
|
@@ -8,7 +8,9 @@ quill:
|
|
|
8
8
|
|
|
9
9
|
main:
|
|
10
10
|
body:
|
|
11
|
-
|
|
11
|
+
example: |
|
|
12
|
+
- First accomplishment bullet.
|
|
13
|
+
- Second accomplishment bullet.
|
|
12
14
|
fields:
|
|
13
15
|
award:
|
|
14
16
|
type: string
|
|
@@ -43,5 +45,6 @@ card_types:
|
|
|
43
45
|
ui:
|
|
44
46
|
title: Specific Accomplishments (Continued — Page 2)
|
|
45
47
|
body:
|
|
46
|
-
|
|
48
|
+
example: |
|
|
49
|
+
- Continued accomplishment bullet.
|
|
47
50
|
fields: {}
|
|
@@ -4,11 +4,14 @@ quill:
|
|
|
4
4
|
backend: typst
|
|
5
5
|
plate_file: plate.typ
|
|
6
6
|
example_file: example.md
|
|
7
|
-
description:
|
|
7
|
+
description: U.S. Air Force and Space Force Official Memorandum
|
|
8
8
|
|
|
9
9
|
main:
|
|
10
10
|
body:
|
|
11
|
-
|
|
11
|
+
example: |
|
|
12
|
+
The first paragraph. Top-level paragraphs are auto-numbered; do not add manual numbering.
|
|
13
|
+
|
|
14
|
+
- Nested bullets are automatically lettered.
|
|
12
15
|
fields:
|
|
13
16
|
memo_for:
|
|
14
17
|
type: array
|
|
@@ -222,13 +225,14 @@ card_types:
|
|
|
222
225
|
action:
|
|
223
226
|
type: string
|
|
224
227
|
enum:
|
|
225
|
-
-
|
|
228
|
+
- ""
|
|
226
229
|
- undecided
|
|
227
230
|
- approve
|
|
228
231
|
- disapprove
|
|
232
|
+
default: ""
|
|
229
233
|
ui:
|
|
230
234
|
group: Addressing
|
|
231
|
-
description: "Action taken by the endorser.
|
|
235
|
+
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."
|
|
232
236
|
attachments:
|
|
233
237
|
type: array
|
|
234
238
|
ui:
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
#let backmatter(
|
|
13
13
|
signature_block: none,
|
|
14
14
|
signature_blank_lines: 4,
|
|
15
|
+
signing_field: none,
|
|
15
16
|
attachments: none,
|
|
16
17
|
cc: none,
|
|
17
18
|
distribution: none,
|
|
18
19
|
leading_pagebreak: false,
|
|
19
20
|
) = {
|
|
20
21
|
// Render backmatter sections without paragraph numbering
|
|
21
|
-
render-signature-block(signature_block, signature-blank-lines: signature_blank_lines)
|
|
22
|
+
render-signature-block(signature_block, signature-blank-lines: signature_blank_lines, signing-field: signing_field)
|
|
22
23
|
render-backmatter-sections(
|
|
23
24
|
attachments: attachments,
|
|
24
25
|
cc: cc,
|
|
@@ -136,14 +136,14 @@
|
|
|
136
136
|
LINE_STRIDE.update(line-stride)
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
metadata((
|
|
139
|
+
[#metadata((
|
|
140
140
|
subject: subject,
|
|
141
141
|
original_date: actual_date,
|
|
142
142
|
original_from: first-or-value(memo_from),
|
|
143
143
|
body_font: body_font,
|
|
144
144
|
font_size: font_size,
|
|
145
145
|
memo_style: memo_style,
|
|
146
|
-
))
|
|
146
|
+
)) <usaf-memo-config>]
|
|
147
147
|
|
|
148
148
|
render-date-section(actual_date, memo-style: memo_style)
|
|
149
149
|
render-for-section(memo_for, memo_for_cols)
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
to: none,
|
|
19
19
|
signature_block: none,
|
|
20
20
|
signature_blank_lines: 4,
|
|
21
|
+
signing_field: none,
|
|
21
22
|
attachments: none,
|
|
22
23
|
cc: none,
|
|
23
24
|
date: none,
|
|
@@ -57,12 +58,18 @@
|
|
|
57
58
|
// or whitespace-only; comparing against `[]` reliably detects that.
|
|
58
59
|
let body_empty = content == []
|
|
59
60
|
|
|
61
|
+
let effective_action = if action == none or type(action) != str or action.trim() == "" {
|
|
62
|
+
none
|
|
63
|
+
} else {
|
|
64
|
+
action
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
if format != "informal" {
|
|
61
68
|
// Step the counter BEFORE the context block to avoid read-then-update loop
|
|
62
69
|
counters.indorsement.step()
|
|
63
70
|
|
|
64
71
|
context {
|
|
65
|
-
let config = query(
|
|
72
|
+
let config = query(<usaf-memo-config>).first().value
|
|
66
73
|
let memo-style = config.at("memo_style", default: "usaf")
|
|
67
74
|
let original_subject = config.subject
|
|
68
75
|
let original_date = config.original_date
|
|
@@ -103,27 +110,24 @@
|
|
|
103
110
|
}
|
|
104
111
|
// Header→content gap. Skipped when there is neither an action line nor
|
|
105
112
|
// body to follow — render-signature-block supplies its own 4-line gap.
|
|
106
|
-
if
|
|
113
|
+
if effective_action != none or not body_empty {
|
|
107
114
|
blank-line()
|
|
108
115
|
}
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
// Show action line only when an action decision is set (not `none`)
|
|
112
|
-
if
|
|
113
|
-
render-action-line(
|
|
119
|
+
if effective_action != none {
|
|
120
|
+
render-action-line(effective_action, trailing-blank-line: not body_empty)
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
if not body_empty {
|
|
117
124
|
context {
|
|
118
|
-
let memo-style =
|
|
119
|
-
let items = query(metadata)
|
|
120
|
-
if items.len() > 0 { items.last().value.at("memo_style", default: "usaf") } else { "usaf" }
|
|
121
|
-
}
|
|
125
|
+
let memo-style = query(<usaf-memo-config>).first().value.at("memo_style", default: "usaf")
|
|
122
126
|
render-body(content, memo-style: memo-style)
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
render-signature-block(signature_block, signature-blank-lines: signature_blank_lines)
|
|
130
|
+
render-signature-block(signature_block, signature-blank-lines: signature_blank_lines, signing-field: signing_field)
|
|
127
131
|
|
|
128
132
|
render-backmatter-sections(attachments: attachments, cc: cc)
|
|
129
133
|
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
/// - content (content): The body content to render
|
|
22
22
|
/// -> content
|
|
23
23
|
#let mainmatter(it) = context {
|
|
24
|
-
let config = query(
|
|
24
|
+
let config = query(<usaf-memo-config>).first().value
|
|
25
25
|
let memo-style = config.at("memo_style", default: "usaf")
|
|
26
26
|
render-body(it, memo-style: memo-style)
|
|
27
27
|
}
|
|
@@ -54,16 +54,16 @@
|
|
|
54
54
|
} else {
|
|
55
55
|
// Isolate seal column from document `font_size`: stack `em` spacing and subtitle
|
|
56
56
|
// must not scale with body text (see frontmatter `set text(size: font_size)`).
|
|
57
|
-
|
|
57
|
+
// Subtitle is wrapped in `box` so it stays on one line and may extend past
|
|
58
|
+
// the seal's 2in column rather than wrapping.
|
|
59
|
+
block[
|
|
58
60
|
#set text(9pt, font: font, fill: LETTERHEAD_COLOR, weight: "bold")
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
)
|
|
66
|
-
]
|
|
61
|
+
// Spacing applies between positional stack children only, not one `[…]` body.
|
|
62
|
+
#stack(
|
|
63
|
+
spacing: 0.5em,
|
|
64
|
+
fit-box(width: 2in, height: 1in)[#letterhead-seal],
|
|
65
|
+
box(upper(ensure-string(letterhead-seal-subtitle))),
|
|
66
|
+
)
|
|
67
67
|
]
|
|
68
68
|
}
|
|
69
69
|
place(
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
// AFH 33-337 long-name example: "Signature block adjusted to the left" when a
|
|
149
149
|
// long name would otherwise exceed the right margin.
|
|
150
150
|
|
|
151
|
-
#let render-signature-block(signature-lines, signature-blank-lines: 4) = {
|
|
151
|
+
#let render-signature-block(signature-lines, signature-blank-lines: 4, signing-field: none) = {
|
|
152
152
|
signature-lines = ensure-array(signature-lines)
|
|
153
153
|
// AFH 33-337: "fifth line below" = 4 blank lines between text and signature block.
|
|
154
154
|
// breakable: false discourages orphaning the signature block onto a page by itself.
|
|
@@ -177,6 +177,20 @@
|
|
|
177
177
|
default-pad
|
|
178
178
|
}
|
|
179
179
|
block(breakable: false)[
|
|
180
|
+
#if signing-field != none {
|
|
181
|
+
let stride = {
|
|
182
|
+
let s = LINE_STRIDE.get()
|
|
183
|
+
if s == none {
|
|
184
|
+
let one-line = measure(par(spacing: 0pt)[x]).height
|
|
185
|
+
measure(par(spacing: 0pt)[x#linebreak()x]).height - one-line
|
|
186
|
+
} else { s }
|
|
187
|
+
}
|
|
188
|
+
place(
|
|
189
|
+
dx: left-pad,
|
|
190
|
+
dy: -(stride * signature-blank-lines),
|
|
191
|
+
box(width: body-width - left-pad, height: stride * signature-blank-lines, signing-field),
|
|
192
|
+
)
|
|
193
|
+
}
|
|
180
194
|
#align(left)[
|
|
181
195
|
#pad(left: left-pad)[
|
|
182
196
|
#text(hyphenate: false)[
|
|
@@ -196,16 +210,14 @@
|
|
|
196
210
|
// ACTION LINE RENDERING
|
|
197
211
|
// =============================================================================
|
|
198
212
|
// Renders the Approve / Disapprove action line for indorsement memos.
|
|
199
|
-
// action: "
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
// action is "undecided", "approve", or "disapprove". "none" suppresses
|
|
203
|
-
// the line entirely (handled by the caller).
|
|
213
|
+
// action: "undecided" = both options rendered plain (no circle),
|
|
214
|
+
// "approve" = Approve circled, "disapprove" = Disapprove circled.
|
|
215
|
+
// Empty/none suppression is handled by the caller before this is invoked.
|
|
204
216
|
|
|
205
217
|
#let render-action-line(action, trailing-blank-line: true) = {
|
|
206
218
|
assert(
|
|
207
|
-
action in ("
|
|
208
|
-
message: "action must be \"
|
|
219
|
+
action in ("undecided", "approve", "disapprove"),
|
|
220
|
+
message: "action must be \"undecided\", \"approve\", or \"disapprove\"",
|
|
209
221
|
)
|
|
210
222
|
// No leading blank-line: the caller (indorsement.typ) already emits the
|
|
211
223
|
// header→content gap once. The action line's `block(sticky: true)`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#import "@local/quillmark-helper:0.1.0": data
|
|
1
|
+
#import "@local/quillmark-helper:0.1.0": data, signature-field
|
|
2
2
|
#import "@local/tonguetoquill-usaf-memo:3.0.0": backmatter, frontmatter, indorsement, mainmatter
|
|
3
3
|
|
|
4
4
|
// Frontmatter configuration
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
#backmatter(
|
|
58
58
|
// Signature block
|
|
59
59
|
signature_block: data.signature_block,
|
|
60
|
+
signing_field: signature-field("Signature"),
|
|
60
61
|
|
|
61
62
|
// Optional cc
|
|
62
63
|
..if "cc" in data { (cc: data.cc) },
|
|
@@ -69,7 +70,7 @@
|
|
|
69
70
|
)
|
|
70
71
|
|
|
71
72
|
// Indorsements - iterate through CARDS array and filter by CARD type
|
|
72
|
-
#for card in data.CARDS {
|
|
73
|
+
#for (i, card) in data.CARDS.enumerate() {
|
|
73
74
|
if card.CARD == "indorsement" {
|
|
74
75
|
// The quillmark helper leaves an unset/whitespace-only markdown body as
|
|
75
76
|
// the empty string `""`; only non-empty bodies are eval'd into content.
|
|
@@ -90,6 +91,7 @@
|
|
|
90
91
|
from: card.at("from", default: ""),
|
|
91
92
|
to: card.at("for", default: ""),
|
|
92
93
|
signature_block: card.signature_block,
|
|
94
|
+
signing_field: signature-field("Ind_" + str(i) + "_Signature"),
|
|
93
95
|
..if "attachments" in card { (attachments: card.attachments) },
|
|
94
96
|
..if "cc" in card { (cc: card.cc) },
|
|
95
97
|
format: card.at("format", default: "standard"),
|