@ham2k/extension-sdk 0.1.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 +58 -0
- package/dist/activityAdifImport.js +45 -0
- package/dist/activityExports.js +157 -0
- package/dist/activityScoring.js +227 -0
- package/dist/appClock.js +42 -0
- package/dist/dxcc.js +25 -0
- package/dist/exportNames.js +57 -0
- package/dist/i18n/activityExports.en.json +4 -0
- package/dist/i18n/activityExports.es.json +4 -0
- package/dist/i18n/activityScoring.en.json +11 -0
- package/dist/i18n/activityScoring.es.json +11 -0
- package/dist/i18n.js +43 -0
- package/dist/index.d.ts +1361 -0
- package/dist/index.js +231 -0
- package/dist/location.js +19 -0
- package/dist/modes.js +28 -0
- package/dist/number-format.js +68 -0
- package/dist/referenceActivity.js +226 -0
- package/dist/scoring.js +163 -0
- package/dist/settingsPanels.js +59 -0
- package/dist/templateContext.js +113 -0
- package/dist/templates.js +76 -0
- package/dist/testOperation.js +6 -0
- package/dist/types.js +0 -0
- package/package.json +110 -0
package/dist/scoring.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const DAY_IN_MILLIS = 1e3 * 60 * 60 * 24;
|
|
2
|
+
function copyCheckpoint(sheet) {
|
|
3
|
+
return JSON.parse(JSON.stringify(sheet));
|
|
4
|
+
}
|
|
5
|
+
function dayOf(qso) {
|
|
6
|
+
const millis = Number(qso.startAtMillis ?? 0);
|
|
7
|
+
return millis - millis % DAY_IN_MILLIS;
|
|
8
|
+
}
|
|
9
|
+
function chronological(qsos) {
|
|
10
|
+
return [...qsos].sort((a, b) => Number(a.startAtMillis ?? 0) - Number(b.startAtMillis ?? 0));
|
|
11
|
+
}
|
|
12
|
+
function isEvent(qso) {
|
|
13
|
+
return qso.band === "event";
|
|
14
|
+
}
|
|
15
|
+
function startAtMillisOf(qso) {
|
|
16
|
+
const at = qso.startAtMillis;
|
|
17
|
+
return typeof at === "number" ? at : 0;
|
|
18
|
+
}
|
|
19
|
+
function contestScorer(scorer, options = {}) {
|
|
20
|
+
const scope = options.scope ?? "always";
|
|
21
|
+
const scopedRefTypes = typeof scope === "object" && Array.isArray(scope.refTypes) ? scope.refTypes : void 0;
|
|
22
|
+
function refOfScopedType(operation) {
|
|
23
|
+
if (!scopedRefTypes) return void 0;
|
|
24
|
+
const refs = operation.refs ?? [];
|
|
25
|
+
return refs.find((r) => typeof r?.type === "string" && scopedRefTypes.includes(r.type));
|
|
26
|
+
}
|
|
27
|
+
function fold(scoresheet, qsos, args, ctx, onQso, onDayEnd, segments) {
|
|
28
|
+
let sheet = scoresheet;
|
|
29
|
+
let currentDay;
|
|
30
|
+
let dayCount = 0;
|
|
31
|
+
const timeline = segments && segments.length > 1 ? segments : void 0;
|
|
32
|
+
let segmentIndex = 0;
|
|
33
|
+
for (const qso of qsos) {
|
|
34
|
+
if (isEvent(qso)) continue;
|
|
35
|
+
let operation = args.operation;
|
|
36
|
+
let ref = args.ref;
|
|
37
|
+
if (timeline) {
|
|
38
|
+
const at = startAtMillisOf(qso);
|
|
39
|
+
while (segmentIndex + 1 < timeline.length && timeline[segmentIndex + 1].fromMillis <= at) segmentIndex += 1;
|
|
40
|
+
operation = timeline[segmentIndex].operation;
|
|
41
|
+
if (scopedRefTypes) {
|
|
42
|
+
const segmentRef = refOfScopedType(operation);
|
|
43
|
+
if (!segmentRef) continue;
|
|
44
|
+
ref = segmentRef;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const day = dayOf(qso);
|
|
48
|
+
const isNewDay = currentDay !== day;
|
|
49
|
+
if (isNewDay) {
|
|
50
|
+
if (currentDay !== void 0) onDayEnd?.(currentDay, dayCount, sheet);
|
|
51
|
+
currentDay = day;
|
|
52
|
+
dayCount = 0;
|
|
53
|
+
}
|
|
54
|
+
dayCount += 1;
|
|
55
|
+
const result = scorer.scoreQso({ scoresheet: sheet, qso, operation, ref, isNewDay }, ctx);
|
|
56
|
+
sheet = result.scoresheet;
|
|
57
|
+
onQso?.(typeof qso.uuid === "string" ? qso.uuid : void 0, result.score);
|
|
58
|
+
}
|
|
59
|
+
if (currentDay !== void 0) onDayEnd?.(currentDay, dayCount, sheet);
|
|
60
|
+
return sheet;
|
|
61
|
+
}
|
|
62
|
+
async function liveScoresheet(args, ctx, excludeUuid) {
|
|
63
|
+
if (args.resumeFrom !== void 0) {
|
|
64
|
+
return copyCheckpoint(args.resumeFrom);
|
|
65
|
+
}
|
|
66
|
+
const uuid = typeof args.operation.uuid === "string" ? args.operation.uuid : void 0;
|
|
67
|
+
const log = uuid ? await ctx.getQsos?.(uuid) ?? [] : [];
|
|
68
|
+
const rest = chronological(log).filter(
|
|
69
|
+
(q) => !excludeUuid || q.uuid !== excludeUuid
|
|
70
|
+
);
|
|
71
|
+
return fold(
|
|
72
|
+
scorer.startScoresheet({ operation: args.operation, ref: args.ref }, ctx),
|
|
73
|
+
rest,
|
|
74
|
+
args,
|
|
75
|
+
ctx,
|
|
76
|
+
void 0,
|
|
77
|
+
void 0,
|
|
78
|
+
args.segments
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
scope,
|
|
83
|
+
async scoreQsos(args, ctx) {
|
|
84
|
+
const qsos = chronological(args.qsos);
|
|
85
|
+
const start = args.resumeFrom !== void 0 ? copyCheckpoint(args.resumeFrom) : scorer.startScoresheet({ operation: args.operation, ref: args.ref }, ctx);
|
|
86
|
+
const qsoScores = {};
|
|
87
|
+
const daySections = [];
|
|
88
|
+
const end = fold(
|
|
89
|
+
start,
|
|
90
|
+
qsos,
|
|
91
|
+
args,
|
|
92
|
+
ctx,
|
|
93
|
+
(uuid, score) => {
|
|
94
|
+
if (uuid) qsoScores[uuid] = score;
|
|
95
|
+
},
|
|
96
|
+
(day, count, sheet) => {
|
|
97
|
+
daySections.push({
|
|
98
|
+
day,
|
|
99
|
+
count,
|
|
100
|
+
scores: scorer.summarizeScore({ scoresheet: sheet, operation: args.operation, ref: args.ref, scope: "day" }, ctx)
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
args.segments
|
|
104
|
+
);
|
|
105
|
+
return {
|
|
106
|
+
qsoScores,
|
|
107
|
+
daySections,
|
|
108
|
+
operationSummary: scorer.summarizeScore(
|
|
109
|
+
{ scoresheet: end, operation: args.operation, ref: args.ref, scope: "operation" },
|
|
110
|
+
ctx
|
|
111
|
+
),
|
|
112
|
+
scoresheet: end
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
/// Live path: score the candidate against the log so far and report only
|
|
116
|
+
/// its notices/alerts. Nothing is recorded — the resulting scoresheet is
|
|
117
|
+
/// discarded, which is exactly why one `scoreQso` can serve both paths.
|
|
118
|
+
async scoreQso(args, ctx) {
|
|
119
|
+
const sheet = await liveScoresheet(args, ctx, args.excludeUuid);
|
|
120
|
+
const { score } = scorer.scoreQso(
|
|
121
|
+
{ scoresheet: sheet, qso: args.qso, operation: args.operation, ref: args.ref, isNewDay: false },
|
|
122
|
+
ctx
|
|
123
|
+
);
|
|
124
|
+
return { notices: score.notices ?? [], alerts: score.alerts ?? [] };
|
|
125
|
+
},
|
|
126
|
+
/// The same live judgement as `scoreQso`, for many candidates at once —
|
|
127
|
+
/// the Spots Panel annotating every spot on screen. The log is folded ONCE
|
|
128
|
+
/// and each candidate scored against its own copy of the result, so a
|
|
129
|
+
/// candidate can neither see nor become a duplicate of another (both spots
|
|
130
|
+
/// for the same unworked call read as unworked, which is what an operator
|
|
131
|
+
/// scanning a band expects).
|
|
132
|
+
///
|
|
133
|
+
/// The copy is per candidate rather than per call because
|
|
134
|
+
/// `ContestScorer.scoreQso` may mutate the scoresheet it is handed — the
|
|
135
|
+
/// contract that makes the batch fold linear. Without it the first
|
|
136
|
+
/// candidate's call would count as worked for every candidate after it.
|
|
137
|
+
async scoreCandidates(args, ctx) {
|
|
138
|
+
const base = await liveScoresheet(args, ctx);
|
|
139
|
+
const result = {};
|
|
140
|
+
for (const candidate of args.candidates) {
|
|
141
|
+
const { score } = scorer.scoreQso(
|
|
142
|
+
{
|
|
143
|
+
scoresheet: copyCheckpoint(base),
|
|
144
|
+
qso: candidate.qso,
|
|
145
|
+
operation: args.operation,
|
|
146
|
+
ref: args.ref,
|
|
147
|
+
isNewDay: false
|
|
148
|
+
},
|
|
149
|
+
ctx
|
|
150
|
+
);
|
|
151
|
+
result[candidate.key] = { notices: score.notices ?? [], alerts: score.alerts ?? [] };
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function tally(key, forScope, total, extra = {}) {
|
|
158
|
+
return { key, for: forScope, total, ...extra };
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
contestScorer,
|
|
162
|
+
tally
|
|
163
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function isValidHookKey(category, extensionKey, resolvedKey) {
|
|
2
|
+
if (category !== "settingsPanel" && category !== "account") return true;
|
|
3
|
+
return resolvedKey === extensionKey || resolvedKey.startsWith(`${extensionKey}_`);
|
|
4
|
+
}
|
|
5
|
+
function tier1FormDefinition(hook) {
|
|
6
|
+
return { title: hook.title, elements: hook.fields ?? [] };
|
|
7
|
+
}
|
|
8
|
+
function settingsPanelEntriesFor(record, descriptors) {
|
|
9
|
+
const hook = record.hook;
|
|
10
|
+
if (hook.kind === "simple") {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
panelKey: record.key,
|
|
14
|
+
hookKey: record.key,
|
|
15
|
+
extensionKey: record.extensionKey,
|
|
16
|
+
title: hook.title ?? null,
|
|
17
|
+
icon: hook.icon ?? null,
|
|
18
|
+
order: hook.order ?? null,
|
|
19
|
+
kind: "simple",
|
|
20
|
+
sidebar: hook.sidebar ?? true,
|
|
21
|
+
dataFilesSection: hook.dataFilesSection ?? false
|
|
22
|
+
}
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
if (hook.kind === "dynamic") {
|
|
26
|
+
if (descriptors) {
|
|
27
|
+
return descriptors.filter((d) => isValidHookKey("settingsPanel", record.extensionKey, d.key)).map((d) => ({
|
|
28
|
+
panelKey: d.key,
|
|
29
|
+
hookKey: record.key,
|
|
30
|
+
extensionKey: record.extensionKey,
|
|
31
|
+
title: d.title ?? null,
|
|
32
|
+
icon: d.icon ?? null,
|
|
33
|
+
order: d.order ?? null,
|
|
34
|
+
kind: "dynamic",
|
|
35
|
+
sidebar: d.sidebar ?? true,
|
|
36
|
+
dataFilesSection: d.dataFilesSection ?? false
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
return [
|
|
40
|
+
{
|
|
41
|
+
panelKey: record.key,
|
|
42
|
+
hookKey: record.key,
|
|
43
|
+
extensionKey: record.extensionKey,
|
|
44
|
+
title: null,
|
|
45
|
+
icon: null,
|
|
46
|
+
order: null,
|
|
47
|
+
kind: "dynamic",
|
|
48
|
+
sidebar: true,
|
|
49
|
+
dataFilesSection: false
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
isValidHookKey,
|
|
57
|
+
settingsPanelEntriesFor,
|
|
58
|
+
tier1FormDefinition
|
|
59
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
function utcDate(millis) {
|
|
2
|
+
return new Date(millis).toISOString().slice(0, 10);
|
|
3
|
+
}
|
|
4
|
+
function utcDateCompact(millis) {
|
|
5
|
+
return utcDate(millis).replace(/-/g, "");
|
|
6
|
+
}
|
|
7
|
+
function utcTime(millis) {
|
|
8
|
+
return new Date(millis).toISOString().slice(11, 16).replace(":", "");
|
|
9
|
+
}
|
|
10
|
+
function utcIso(millis) {
|
|
11
|
+
return new Date(millis).toISOString();
|
|
12
|
+
}
|
|
13
|
+
function dateValues(millis) {
|
|
14
|
+
return {
|
|
15
|
+
date: utcDate(millis),
|
|
16
|
+
dateCompact: utcDateCompact(millis),
|
|
17
|
+
time: utcTime(millis),
|
|
18
|
+
at: utcIso(millis)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function opValues(operation, extra = {}) {
|
|
22
|
+
const station = String(operation.stationCall ?? "");
|
|
23
|
+
const startMillis = Number(operation.startAtMillisMin ?? operation.startAtMillis ?? 0);
|
|
24
|
+
const endMillis = Number(operation.startAtMillisMax ?? 0);
|
|
25
|
+
const atMillis = extra.atMillis && extra.atMillis > 0 ? extra.atMillis : startMillis;
|
|
26
|
+
return {
|
|
27
|
+
...atMillis > 0 ? dateValues(atMillis) : { date: "", dateCompact: "", time: "", at: "" },
|
|
28
|
+
station,
|
|
29
|
+
// Deduped, like halo_core's `Operation.stationCalls`: "N0DEV, N0DEV" is a
|
|
30
|
+
// typo, and a template fanning out over this would otherwise repeat
|
|
31
|
+
// itself once per typo.
|
|
32
|
+
stations: [
|
|
33
|
+
...new Set(
|
|
34
|
+
station.split(",").map((call) => call.trim().toUpperCase()).filter((call) => call !== "")
|
|
35
|
+
)
|
|
36
|
+
],
|
|
37
|
+
operator: operation.operator ?? "",
|
|
38
|
+
// The generated, ref-derived title ("at US-1234") and the operator's own
|
|
39
|
+
// words, kept apart: an export that withholds private data withholds the
|
|
40
|
+
// second and keeps the first (see the adif hook's preamble).
|
|
41
|
+
title: operation.title ?? "",
|
|
42
|
+
userTitle: operation.userTitle ?? "",
|
|
43
|
+
grid: operation.grid ?? "",
|
|
44
|
+
refs: operation.refs ?? [],
|
|
45
|
+
uuid: operation.uuid ?? "",
|
|
46
|
+
qsoCount: extra.qsoCount ?? Number(operation.qsoCount ?? 0),
|
|
47
|
+
startDate: startMillis > 0 ? utcDate(startMillis) : "",
|
|
48
|
+
startTime: startMillis > 0 ? utcTime(startMillis) : "",
|
|
49
|
+
startAt: startMillis > 0 ? utcIso(startMillis) : "",
|
|
50
|
+
endDate: endMillis > 0 ? utcDate(endMillis) : "",
|
|
51
|
+
endTime: endMillis > 0 ? utcTime(endMillis) : "",
|
|
52
|
+
endAt: endMillis > 0 ? utcIso(endMillis) : ""
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function qsoValues(qso) {
|
|
56
|
+
const their = qso.their ?? {};
|
|
57
|
+
const our = qso.our ?? {};
|
|
58
|
+
const startMillis = Number(qso.startAtMillis ?? 0);
|
|
59
|
+
return {
|
|
60
|
+
call: their.call ?? "",
|
|
61
|
+
their,
|
|
62
|
+
our,
|
|
63
|
+
band: qso.band ?? "",
|
|
64
|
+
mode: qso.mode ?? "",
|
|
65
|
+
freq: qso.freq ?? "",
|
|
66
|
+
// Named for what an operator calls them, not for QSON's `sent` on both
|
|
67
|
+
// sides — `qso.our.sent` is what WE sent, i.e. the report they received.
|
|
68
|
+
rstSent: our.sent ?? "",
|
|
69
|
+
rstRcvd: their.sent ?? "",
|
|
70
|
+
notes: qso.notes ?? "",
|
|
71
|
+
refs: qso.refs ?? [],
|
|
72
|
+
...startMillis > 0 ? dateValues(startMillis) : { date: "", dateCompact: "", time: "", at: "" },
|
|
73
|
+
startAtMillis: startMillis
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function logValues(values) {
|
|
77
|
+
return {
|
|
78
|
+
station: values.station ?? "",
|
|
79
|
+
ref: values.ref ?? "",
|
|
80
|
+
refName: values.refName ?? "",
|
|
81
|
+
refShortName: values.refShortName ?? "",
|
|
82
|
+
activity: values.activity ?? "",
|
|
83
|
+
handlerName: values.handlerName ?? "",
|
|
84
|
+
handlerShortName: values.handlerShortName ?? "",
|
|
85
|
+
format: values.format ?? "",
|
|
86
|
+
exportType: values.exportType ?? "",
|
|
87
|
+
modifier: values.modifier ?? "",
|
|
88
|
+
extension: values.extension ?? "",
|
|
89
|
+
compact: values.compact === true
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function templateContext(args) {
|
|
93
|
+
const nowMillis = args.nowMillis ?? Date.now();
|
|
94
|
+
return {
|
|
95
|
+
app: { name: args.appName ?? "" },
|
|
96
|
+
now: utcIso(nowMillis),
|
|
97
|
+
...args.operation ? { op: opValues(args.operation, { qsoCount: args.qsoCount, atMillis: args.atMillis }) } : {},
|
|
98
|
+
...args.qso ? { qso: qsoValues(args.qso) } : {},
|
|
99
|
+
...args.log ? { log: logValues(args.log) } : {},
|
|
100
|
+
...args.config ? { config: args.config } : {}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
dateValues,
|
|
105
|
+
logValues,
|
|
106
|
+
opValues,
|
|
107
|
+
qsoValues,
|
|
108
|
+
templateContext,
|
|
109
|
+
utcDate,
|
|
110
|
+
utcDateCompact,
|
|
111
|
+
utcIso,
|
|
112
|
+
utcTime
|
|
113
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Liquid } from "liquidjs";
|
|
2
|
+
const RENDER_LIMIT_MS = 250;
|
|
3
|
+
let engine;
|
|
4
|
+
function liquid() {
|
|
5
|
+
if (engine) return engine;
|
|
6
|
+
engine = new Liquid({
|
|
7
|
+
// UTC, always. Amateur radio logs are kept in UTC, and liquid's `date`
|
|
8
|
+
// filter otherwise formats in the HOST's zone — wrong in a way no
|
|
9
|
+
// tester in UTC±0 can see, exactly like `toLocaleString` with a
|
|
10
|
+
// `timeZone` on this engine (docs/extensions/README.md).
|
|
11
|
+
//
|
|
12
|
+
// The sign is INVERTED from what the name suggests: this follows
|
|
13
|
+
// `Date.getTimezoneOffset()`, i.e. minutes BEHIND UTC, so -300 would
|
|
14
|
+
// render UTC+5. Zero is the only value with no trap in it.
|
|
15
|
+
timezoneOffset: 0,
|
|
16
|
+
// Lenient, because most templates are written by operators rather than
|
|
17
|
+
// by us: an unknown variable renders empty and an unknown filter passes
|
|
18
|
+
// its value through, so a typo costs one wrong line instead of the whole
|
|
19
|
+
// document. Our own templates are covered by tests instead.
|
|
20
|
+
strictVariables: false,
|
|
21
|
+
strictFilters: false,
|
|
22
|
+
renderLimit: RENDER_LIMIT_MS
|
|
23
|
+
});
|
|
24
|
+
registerFilters(engine);
|
|
25
|
+
return engine;
|
|
26
|
+
}
|
|
27
|
+
function registerFilters(liquid2) {
|
|
28
|
+
liquid2.registerFilter(
|
|
29
|
+
"dash",
|
|
30
|
+
(value) => String(value ?? "").replace(/[^A-Za-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")
|
|
31
|
+
);
|
|
32
|
+
liquid2.registerFilter("alnum", (value) => String(value ?? "").replace(/[^A-Za-z0-9]/g, ""));
|
|
33
|
+
}
|
|
34
|
+
class TemplateError extends Error {
|
|
35
|
+
constructor(message, options) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "TemplateError";
|
|
38
|
+
this.cause = options?.cause;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const parsed = /* @__PURE__ */ new Map();
|
|
42
|
+
const PARSE_CACHE_LIMIT = 64;
|
|
43
|
+
function renderTemplate(template, context, label) {
|
|
44
|
+
const source = template ?? "";
|
|
45
|
+
try {
|
|
46
|
+
let compiled = parsed.get(source);
|
|
47
|
+
if (!compiled) {
|
|
48
|
+
compiled = liquid().parse(source);
|
|
49
|
+
if (parsed.size >= PARSE_CACHE_LIMIT) parsed.clear();
|
|
50
|
+
parsed.set(source, compiled);
|
|
51
|
+
}
|
|
52
|
+
return liquid().renderSync(compiled, context);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
55
|
+
throw new TemplateError(label ? `${label}: ${message}` : message, { cause: e });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function oneLine(text) {
|
|
59
|
+
return text.replace(/\s+/g, " ").trim();
|
|
60
|
+
}
|
|
61
|
+
function triggersForTemplate(template) {
|
|
62
|
+
const text = (template ?? "").match(/\{\{.*?\}\}|\{%.*?%\}/gs)?.join(" ") ?? "";
|
|
63
|
+
const mentions = (name) => new RegExp(`\\b${name}\\b`).test(text);
|
|
64
|
+
const triggers = [];
|
|
65
|
+
if (mentions("qso")) triggers.push("qso", "lookup");
|
|
66
|
+
if (mentions("qsoCount")) triggers.push("qsoLogged");
|
|
67
|
+
if (mentions("op") || mentions("operation")) triggers.push("operation");
|
|
68
|
+
if (mentions("now")) triggers.push("tick:30");
|
|
69
|
+
return triggers;
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
TemplateError,
|
|
73
|
+
oneLine,
|
|
74
|
+
renderTemplate,
|
|
75
|
+
triggersForTemplate
|
|
76
|
+
};
|
package/dist/types.js
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ham2k/extension-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Write extensions for the Ham2K Logger: typed hook contracts and the host API",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ham2k",
|
|
7
|
+
"ham radio",
|
|
8
|
+
"amateur radio",
|
|
9
|
+
"extension",
|
|
10
|
+
"sdk",
|
|
11
|
+
"logging"
|
|
12
|
+
],
|
|
13
|
+
"license": "MPL-2.0",
|
|
14
|
+
"author": "Sebastian Delmont <sd@ham2k.com>",
|
|
15
|
+
"homepage": "https://ham2k.com",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/ham2k/halo.git",
|
|
19
|
+
"directory": "extensions/sdk"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"//sideEffects": [
|
|
31
|
+
"Importing any of these modules does nothing observable, so a bundler may",
|
|
32
|
+
"drop whichever ones an extension never reaches. dxcc is the exception and",
|
|
33
|
+
"is named rather than waved through: it registers the built-in country file",
|
|
34
|
+
"at import time, and a bundler that dropped it would leave callsign",
|
|
35
|
+
"annotation quietly answering with nothing."
|
|
36
|
+
],
|
|
37
|
+
"sideEffects": [
|
|
38
|
+
"./dist/dxcc.js"
|
|
39
|
+
],
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "node build.mjs"
|
|
49
|
+
},
|
|
50
|
+
"//peerDependencies": [
|
|
51
|
+
"The libraries the extension host carries. An extension does NOT need these",
|
|
52
|
+
"installed: the build preset rewrites each one the manifest declares into a",
|
|
53
|
+
"lookup on the host's single instance, before esbuild ever resolves it, and",
|
|
54
|
+
"the published .d.ts carries their types rolled in. They are here, optional,",
|
|
55
|
+
"for the one case that does reach the filesystem \u2014 `inline: [...]`, which",
|
|
56
|
+
"ships your own copy because you need a version the host does not carry."
|
|
57
|
+
],
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@ham2k/lib-callsigns": "*",
|
|
60
|
+
"@ham2k/lib-country-files": "*",
|
|
61
|
+
"@ham2k/lib-cqmag-data": "*",
|
|
62
|
+
"@ham2k/lib-dxcc-data": "*",
|
|
63
|
+
"@ham2k/lib-format-tools": "*",
|
|
64
|
+
"@ham2k/lib-geo-tools": "*",
|
|
65
|
+
"@ham2k/lib-operation-data": "*",
|
|
66
|
+
"@ham2k/lib-qson-adif": "*",
|
|
67
|
+
"@ham2k/lib-qson-cabrillo": "*",
|
|
68
|
+
"@ham2k/lib-qson-tools": "*",
|
|
69
|
+
"i18next": "*",
|
|
70
|
+
"liquidjs": "*"
|
|
71
|
+
},
|
|
72
|
+
"peerDependenciesMeta": {
|
|
73
|
+
"@ham2k/lib-callsigns": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"@ham2k/lib-country-files": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"@ham2k/lib-cqmag-data": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"@ham2k/lib-dxcc-data": {
|
|
83
|
+
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"@ham2k/lib-format-tools": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"@ham2k/lib-geo-tools": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"@ham2k/lib-operation-data": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"@ham2k/lib-qson-adif": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"@ham2k/lib-qson-cabrillo": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"@ham2k/lib-qson-tools": {
|
|
101
|
+
"optional": true
|
|
102
|
+
},
|
|
103
|
+
"i18next": {
|
|
104
|
+
"optional": true
|
|
105
|
+
},
|
|
106
|
+
"liquidjs": {
|
|
107
|
+
"optional": true
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|