@ham2k/extension-sdk 0.5.0 → 0.5.2
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/dist/index.d.ts +1 -1
- package/dist/templateContext.js +20 -0
- package/dist/templates.js +22 -0
- package/docs/templates.md +35 -4
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/templateContext.js
CHANGED
|
@@ -60,6 +60,7 @@ function qsoValues(qso) {
|
|
|
60
60
|
const their = qso.their ?? {};
|
|
61
61
|
const our = qso.our ?? {};
|
|
62
62
|
const startMillis = Number(qso.startAtMillis ?? 0);
|
|
63
|
+
const serialSent = refField(qso.refs, "ourSerial");
|
|
63
64
|
return {
|
|
64
65
|
call: their.call ?? "",
|
|
65
66
|
their,
|
|
@@ -71,12 +72,31 @@ function qsoValues(qso) {
|
|
|
71
72
|
// sides — `qso.our.sent` is what WE sent, i.e. the report they received.
|
|
72
73
|
rstSent: our.sent ?? "",
|
|
73
74
|
rstRcvd: their.sent ?? "",
|
|
75
|
+
// Unpadded, as stored: `001` or `1` is the operator's call, not the
|
|
76
|
+
// contest's, so it is left to the `pad` filter.
|
|
77
|
+
serialSent,
|
|
78
|
+
serialRcvd: refField(qso.refs, "theirSerial"),
|
|
79
|
+
// The short forms are the SENT side: a CW message is what we send, and
|
|
80
|
+
// that is where these get typed.
|
|
81
|
+
rst: our.sent ?? "",
|
|
82
|
+
serial: serialSent,
|
|
74
83
|
notes: qso.notes ?? "",
|
|
75
84
|
refs: qso.refs ?? [],
|
|
76
85
|
...startMillis > 0 ? dateValues(startMillis) : { date: "", dateCompact: "", time: "", at: "" },
|
|
77
86
|
startAtMillis: startMillis
|
|
78
87
|
};
|
|
79
88
|
}
|
|
89
|
+
function refField(refs, field) {
|
|
90
|
+
if (!Array.isArray(refs)) return "";
|
|
91
|
+
for (const ref of refs) {
|
|
92
|
+
if (ref === null || typeof ref !== "object" || Array.isArray(ref)) continue;
|
|
93
|
+
const value = ref[field];
|
|
94
|
+
if (typeof value !== "string" && typeof value !== "number") continue;
|
|
95
|
+
const text = String(value).trim();
|
|
96
|
+
if (text !== "") return text;
|
|
97
|
+
}
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
80
100
|
function logValues(values) {
|
|
81
101
|
return {
|
|
82
102
|
station: values.station ?? "",
|
package/dist/templates.js
CHANGED
|
@@ -30,7 +30,29 @@ function registerFilters(liquid2) {
|
|
|
30
30
|
(value) => String(value ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")
|
|
31
31
|
);
|
|
32
32
|
liquid2.registerFilter("alnum", (value) => String(value ?? "").replace(/[^A-Za-z0-9]/g, ""));
|
|
33
|
+
liquid2.registerFilter("pad", (value, width = 3) => {
|
|
34
|
+
const text = String(value ?? "").trim();
|
|
35
|
+
return text === "" ? "" : text.padStart(Number(width) || 0, "0");
|
|
36
|
+
});
|
|
37
|
+
liquid2.registerFilter("cut", (value, digits = "09") => {
|
|
38
|
+
const chosen = String(digits ?? "");
|
|
39
|
+
return String(value ?? "").replace(/[0159]/g, (digit) => chosen.includes(digit) ? CUT_NUMBERS[digit] : digit);
|
|
40
|
+
});
|
|
41
|
+
liquid2.registerFilter(
|
|
42
|
+
"refLabels",
|
|
43
|
+
(value) => (Array.isArray(value) ? value : []).flatMap((ref) => {
|
|
44
|
+
const label = [ref?.shortLabel, ref?.label, ref?.ref].map((text) => typeof text === "string" || typeof text === "number" ? String(text).trim() : "").find((text) => text !== "");
|
|
45
|
+
return label === void 0 ? [] : [label];
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
liquid2.registerFilter("sentence", (value, separator = ", ", final = " & ") => {
|
|
49
|
+
if (!Array.isArray(value)) return String(value ?? "");
|
|
50
|
+
const items = value.map((item) => String(item ?? "").trim()).filter((item) => item !== "");
|
|
51
|
+
if (items.length < 2) return items.join("");
|
|
52
|
+
return `${items.slice(0, -1).join(String(separator))}${String(final)}${items[items.length - 1]}`;
|
|
53
|
+
});
|
|
33
54
|
}
|
|
55
|
+
const CUT_NUMBERS = { "0": "T", "1": "A", "5": "E", "9": "N" };
|
|
34
56
|
class TemplateError extends Error {
|
|
35
57
|
constructor(message, options) {
|
|
36
58
|
super(message);
|
package/docs/templates.md
CHANGED
|
@@ -34,7 +34,9 @@ about 2 µs each.
|
|
|
34
34
|
the namespaces are deliberately polo's. The syntax around them does not:
|
|
35
35
|
`{{#if}}` is `{% if %}{% endif %}`, `{{#each}}` is `{% for %}{% endfor %}`,
|
|
36
36
|
`{{> Partial}}` has no equivalent, and helpers are filters
|
|
37
|
-
(`{{ dash x }}` → `{{ x | dash }}`).
|
|
37
|
+
(`{{ dash x }}` → `{{ x | dash }}`). polo's
|
|
38
|
+
`{{#join op.refs separator=", " final=" & "}}{{or shortLabel label key}}{{/join}}`
|
|
39
|
+
is `{{ op.refs | refLabels | sentence }}`.
|
|
38
40
|
|
|
39
41
|
## What a template can name
|
|
40
42
|
|
|
@@ -74,12 +76,22 @@ that contact's length.
|
|
|
74
76
|
|
|
75
77
|
### `qso` — one contact
|
|
76
78
|
`call`, `their`, `our` (both whole, so `qso.their.guess.name` reaches the
|
|
77
|
-
lookup's answer), `band`, `mode`, `freq`, `rstSent`, `rstRcvd`, `
|
|
78
|
-
`date`, `dateCompact`, `time`, `at`, `startAtMillis`,
|
|
79
|
+
lookup's answer), `band`, `mode`, `freq`, `rstSent`, `rstRcvd`, `serialSent`,
|
|
80
|
+
`serialRcvd`, `notes`, `date`, `dateCompact`, `time`, `at`, `startAtMillis`,
|
|
81
|
+
`refs` — and two short forms for the sent side, `rst` (= `rstSent`) and
|
|
82
|
+
`serial` (= `serialSent`), since a CW message is what we send and that is
|
|
83
|
+
where these get typed: `{{ qso.call }} {{ qso.rst | cut }} {{ qso.serial | pad: 3 | cut }}`.
|
|
79
84
|
|
|
80
85
|
`rstSent` is what WE sent — QSON stores each side's report under its own
|
|
81
86
|
`sent`, and these are named for the operator's view of the contact.
|
|
82
87
|
|
|
88
|
+
`serialSent` / `serialRcvd` are a contest's serial numbers, blank outside
|
|
89
|
+
one. A contest keeps them on its own ref, so they are found by field name —
|
|
90
|
+
the first ref carrying `ourSerial` / `theirSerial` — which is what lets one
|
|
91
|
+
message serve every serial contest (docs/design/contests.md §5.8). In a CW
|
|
92
|
+
message `serialSent` is the number the serial field is SHOWING, the one the
|
|
93
|
+
QSO will be logged with. It is unpadded (`7`, not `007`): use `pad`.
|
|
94
|
+
|
|
83
95
|
### `config` — a panel's own form values
|
|
84
96
|
Whatever that panel's `form` declared, under the keys it used.
|
|
85
97
|
|
|
@@ -112,12 +124,27 @@ with nothing to say it was invented.
|
|
|
112
124
|
|
|
113
125
|
All of Liquid's own (`date`, `downcase`, `upcase`, `strip`, `default`,
|
|
114
126
|
`join`, `size`, `first`, `last`, `round`, `truncate`, `replace`, `map`,
|
|
115
|
-
`where`, …), plus
|
|
127
|
+
`where`, …), plus six:
|
|
116
128
|
|
|
117
129
|
| filter | does | example |
|
|
118
130
|
|---|---|---|
|
|
119
131
|
| `dash` | non-alphanumerics → `-`, collapsed and trimmed, **case preserved** | `N0CALL/P` → `N0CALL-P` |
|
|
120
132
|
| `alnum` | strip to alphanumerics | `2026-07-27` → `20260727` |
|
|
133
|
+
| `pad: n` | zero-pad on the left to `n` characters (default 3); **blank stays blank** | `7` → `007` |
|
|
134
|
+
| `cut: "digits"` | CW cut numbers for the listed digits, of `0`→`T` `1`→`A` `5`→`E` `9`→`N`; default `"09"` | `599` → `5NN`, `{{ 9 \| pad: 3 \| cut }}` → `TTN` |
|
|
135
|
+
| `refLabels` | a list of refs → each one's `shortLabel`, else `label`, else `ref`; a ref with none of them is skipped — an operation's contest ref HAS a label (`CQ WPX CW`) and is listed | `op.refs` → `POTA US-1234`, `KFF-9999` |
|
|
136
|
+
| `sentence: "sep", "final"` | joins a list with a different LAST separator (defaults `", "` and `" & "`); blanks are dropped | `A, B & C`, `A & B` |
|
|
137
|
+
|
|
138
|
+
`pad` leaves a blank alone because a CW message can be keyed before the
|
|
139
|
+
serial field has a number, and `000` sent in that gap is a serial the log
|
|
140
|
+
will never agree with. Order matters with `cut`: pad first, or the padding
|
|
141
|
+
zeros go out uncut.
|
|
142
|
+
|
|
143
|
+
`refLabels` exists because `map` reads one field: `map: "shortLabel"` drops a
|
|
144
|
+
reference that resolved to nothing, since it carries no labels — an
|
|
145
|
+
operation at an unlisted park would print as though it had none. Liquid's own
|
|
146
|
+
`array_to_sentence_string` is the near miss for `sentence`: it always writes
|
|
147
|
+
the serial comma (`A, B, & C`).
|
|
121
148
|
|
|
122
149
|
`alnum` is app-polo's `compact` helper under a different name: Liquid already
|
|
123
150
|
has a `compact` (it drops nils from an **array**, and `op.refs`/`qso.refs` are
|
|
@@ -140,6 +167,10 @@ Each of these was measured against the shipped runtime, not assumed.
|
|
|
140
167
|
number as SECONDS, so epoch millis render in the year 58567. Use the `…At`
|
|
141
168
|
strings.
|
|
142
169
|
|
|
170
|
+
**`cut`'s digit list must be quoted.** Liquid reads a bare `0159` as the
|
|
171
|
+
number 159 before the filter sees it, so `{{ n | cut: 09 }}` cuts only the
|
|
172
|
+
nines — the zero is gone with no error. Write `cut: "09"`.
|
|
173
|
+
|
|
143
174
|
**Unknown names are silent.** An unknown variable renders empty and an
|
|
144
175
|
unknown filter passes its value through. That is deliberate — an operator's
|
|
145
176
|
typo costs one line, not the document — but it means a misspelled
|