@ham2k/extension-sdk 0.2.0 → 0.4.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/LICENSE +21 -0
- package/README.md +20 -4
- package/dist/activityExports.js +48 -11
- package/dist/activityScoring.js +2 -0
- package/dist/dxcc.js +15 -2
- package/dist/exportSettings.js +133 -0
- package/dist/index.d.ts +133 -5
- package/dist/index.js +17 -14
- package/dist/modes.js +20 -0
- package/dist/refTransforms.js +34 -0
- package/dist/referenceActivity.js +89 -18
- package/dist/scoring.js +11 -4
- package/dist/segments.js +17 -0
- package/dist/templateContext.js +4 -0
- package/docs/distribution.md +168 -2
- package/docs/forms.md +36 -0
- package/docs/hooks.md +193 -28
- package/docs/settings.md +11 -9
- package/docs/templates.md +27 -25
- package/package.json +11 -2
- package/samples/k2hrc-cqww/manifest.json +0 -1
- package/samples/k2hrc-cqww/src/index.ts +1 -1
- package/samples/k2hrc-hamqth/manifest.json +0 -1
- package/samples/k2hrc-hamqth/src/index.ts +1 -1
- package/samples/k2hrc-llota/manifest.json +1 -1
- package/samples/k2hrc-llota/src/index.ts +1 -1
- package/samples/k2hrc-radio/manifest.json +0 -1
- package/samples/k2hrc-radio/src/index.ts +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sebastian Delmont <sd@ham2k.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -53,6 +53,26 @@ offline and describe the version installed here rather than whatever the
|
|
|
53
53
|
repository says today. If an agent is writing your extension, point it at
|
|
54
54
|
`node_modules/@ham2k/extension-sdk/AGENTS.md` — `h2kext-init` does that for you.
|
|
55
55
|
|
|
56
|
+
## Export types and settings
|
|
57
|
+
|
|
58
|
+
Export hooks can register shared types with `getExportTypes()`. Use
|
|
59
|
+
`exportTypeDefinition(activationType, format, label)` for an identity shared
|
|
60
|
+
by extensions serving the same activity. `exportType` identifies the settings;
|
|
61
|
+
`exportKey` identifies an individual file, such as one park in an activation.
|
|
62
|
+
|
|
63
|
+
The `activityExportHook` and `huntingExportHook` helpers register their types
|
|
64
|
+
and forward export settings to the ADIF generator. Reference activity types
|
|
65
|
+
use `templateCategory: 'reference'`; other types use the other-activity defaults.
|
|
66
|
+
`GlobalExportSettings` provides separate normal filename, compact filename,
|
|
67
|
+
and title templates for each category, plus shared ADIF field templates.
|
|
68
|
+
|
|
69
|
+
When updating an exporter from SDK 0.3, keep a reference-specific identifier
|
|
70
|
+
in `exportKey`, register a stable `exportType`, and forward `exportSettings`,
|
|
71
|
+
`exportData`, `exportTitle`, and `includeLookupData` when delegating generation.
|
|
72
|
+
Per-type custom templates override inherited defaults only when enabled.
|
|
73
|
+
See `docs/hooks.md` §`export` for the complete contract and `docs/forms.md`
|
|
74
|
+
for the `textTemplate` form field.
|
|
75
|
+
|
|
56
76
|
## About the peer dependencies
|
|
57
77
|
|
|
58
78
|
This package lists the libraries the extension host carries — `@ham2k/lib-*`,
|
|
@@ -67,7 +87,3 @@ in, so your editor works with only this package installed.
|
|
|
67
87
|
|
|
68
88
|
Install one only to `inline` it — to ship your own copy because you need a
|
|
69
89
|
version the host does not carry.
|
|
70
|
-
|
|
71
|
-
## License
|
|
72
|
-
|
|
73
|
-
MPL-2.0
|
package/dist/activityExports.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { exportTypeDefinition } from "./exportSettings.js";
|
|
1
2
|
import { exportFilename, startMillisOf } from "./exportNames.js";
|
|
3
|
+
import { segmentsWith } from "./segments.js";
|
|
2
4
|
import { createCachedTranslator } from "./i18n.js";
|
|
3
5
|
import en from "./i18n/activityExports.en.json";
|
|
4
6
|
import es from "./i18n/activityExports.es.json";
|
|
@@ -20,11 +22,23 @@ function filenameFor(operation, qsos, ref, compact) {
|
|
|
20
22
|
function activityExportHook(rules) {
|
|
21
23
|
const prefix = `${rules.key}-adif`;
|
|
22
24
|
return {
|
|
25
|
+
async getExportTypes() {
|
|
26
|
+
return [{
|
|
27
|
+
...exportTypeDefinition(rules.activationType, "adif", rules.label),
|
|
28
|
+
templateCategory: "reference",
|
|
29
|
+
templateSample: rules.templateSample ?? {
|
|
30
|
+
log: { ref: "REF-1234", refName: "Example Reference" },
|
|
31
|
+
operation: { refs: [{ type: rules.activationType, ref: "REF-1234" }] }
|
|
32
|
+
}
|
|
33
|
+
}];
|
|
34
|
+
},
|
|
23
35
|
async suggestExportOptions({ operation, qsos, compactFilenames }, ctx) {
|
|
24
36
|
const refs = refsOfType(operation, rules.activationType);
|
|
25
37
|
const t = tFor(ctx);
|
|
26
38
|
return refs.map((ref) => ({
|
|
27
|
-
exportType: `${
|
|
39
|
+
exportType: `${rules.activationType}-adif`,
|
|
40
|
+
templateData: { ref: ref.ref, refName: ref.name ?? "", activity: rules.label },
|
|
41
|
+
exportKey: `${prefix}${SEPARATOR}${ref.ref}`,
|
|
28
42
|
format: "adif",
|
|
29
43
|
// The FORMAT leads: a park activated on a contest weekend offers
|
|
30
44
|
// several files for the same reference, and "POTA US-1234" alone said
|
|
@@ -43,21 +57,29 @@ function activityExportHook(rules) {
|
|
|
43
57
|
},
|
|
44
58
|
async generateExport(args, ctx) {
|
|
45
59
|
const exportType = String(args.exportType ?? "");
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
const exportKey = String(args.exportKey ?? "");
|
|
61
|
+
if (exportType !== `${rules.activationType}-adif` || !exportKey.startsWith(`${prefix}${SEPARATOR}`)) {
|
|
62
|
+
throw new Error(`${rules.key}: unknown export type/key '${exportType}' / '${exportKey}'`);
|
|
48
63
|
}
|
|
49
|
-
const wanted =
|
|
64
|
+
const wanted = exportKey.slice(prefix.length + SEPARATOR.length);
|
|
50
65
|
if (!refsOfType(args.operation, rules.activationType).some((r) => r.ref === wanted)) {
|
|
51
66
|
throw new Error(`${rules.key}: operation has no ${rules.activationType} reference '${wanted}'`);
|
|
52
67
|
}
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
const claimOnly = (operation) => ({
|
|
69
|
+
...operation,
|
|
70
|
+
refs: (operation.refs ?? []).filter(
|
|
71
|
+
(r) => r?.type !== rules.activationType || r?.ref === wanted
|
|
72
|
+
)
|
|
73
|
+
});
|
|
57
74
|
const content = await adifForExport({
|
|
58
|
-
operation,
|
|
75
|
+
operation: claimOnly(args.operation),
|
|
59
76
|
qsos: args.qsos,
|
|
77
|
+
segments: segmentsWith(args.segments, claimOnly),
|
|
60
78
|
includePrivateData: args.includePrivateData,
|
|
79
|
+
includeLookupData: args.includeLookupData,
|
|
80
|
+
exportSettings: args.exportSettings,
|
|
81
|
+
exportData: args.exportData,
|
|
82
|
+
exportTitle: args.exportTitle,
|
|
61
83
|
mainHandler: rules.key,
|
|
62
84
|
includeFieldsFrom: rules.includeFieldsFrom
|
|
63
85
|
});
|
|
@@ -79,17 +101,22 @@ function filenameForHunt(operation, qsos, activity, compact) {
|
|
|
79
101
|
});
|
|
80
102
|
}
|
|
81
103
|
function huntingExportHook(rules) {
|
|
82
|
-
const exportType = `${rules.
|
|
104
|
+
const exportType = `${rules.huntingType}-hunter`;
|
|
83
105
|
function huntedQsos(qsos) {
|
|
84
106
|
return qsos.filter((qso) => refsOfType(qso, rules.huntingType).length > 0);
|
|
85
107
|
}
|
|
86
108
|
return {
|
|
109
|
+
async getExportTypes() {
|
|
110
|
+
return [{ exportType, format: "adif", label: `${rules.label} Hunter Log` }];
|
|
111
|
+
},
|
|
87
112
|
async suggestExportOptions({ operation, qsos, compactFilenames }, ctx) {
|
|
88
113
|
if (refsOfType(operation, rules.activationType).length > 0) return [];
|
|
89
114
|
if (huntedQsos(qsos ?? []).length === 0) return [];
|
|
90
115
|
const t = tFor(ctx);
|
|
91
116
|
return [{
|
|
92
117
|
exportType,
|
|
118
|
+
qsoCount: huntedQsos(qsos ?? []).length,
|
|
119
|
+
templateData: { activity: rules.label },
|
|
93
120
|
format: "adif",
|
|
94
121
|
label: t("adifForHunter", { program: rules.label }),
|
|
95
122
|
filename: filenameForHunt(operation, qsos ?? [], rules.label, compactFilenames),
|
|
@@ -113,7 +140,12 @@ function huntingExportHook(rules) {
|
|
|
113
140
|
const content = await adifForExport({
|
|
114
141
|
operation: args.operation,
|
|
115
142
|
qsos: huntedQsos(args.qsos ?? []),
|
|
143
|
+
segments: args.segments,
|
|
116
144
|
includePrivateData: args.includePrivateData,
|
|
145
|
+
includeLookupData: args.includeLookupData,
|
|
146
|
+
exportSettings: args.exportSettings,
|
|
147
|
+
exportData: args.exportData,
|
|
148
|
+
exportTitle: args.exportTitle,
|
|
117
149
|
// The hunter file is submitted to ONE program too — see
|
|
118
150
|
// `activityExportHook.generateExport`. `rules.key` is the ACTIVITY's
|
|
119
151
|
// key ('pota'), which is what its `adifFields` hook registers under;
|
|
@@ -131,15 +163,20 @@ function huntingExportHook(rules) {
|
|
|
131
163
|
}
|
|
132
164
|
};
|
|
133
165
|
}
|
|
134
|
-
async function adifForExport({ operation, qsos, includePrivateData, mainHandler, includeFieldsFrom }) {
|
|
166
|
+
async function adifForExport({ operation, qsos, segments, includePrivateData, includeLookupData, exportSettings, exportData, exportTitle, mainHandler, includeFieldsFrom }) {
|
|
135
167
|
const { hooks } = await import("@ham2k/extension-sdk");
|
|
136
168
|
const entries = await hooks.invokeOne("export", "adif", "generateExport", {
|
|
137
169
|
operation,
|
|
138
170
|
qsos,
|
|
171
|
+
segments,
|
|
139
172
|
exportType: "adif",
|
|
140
173
|
// Forwarded, or the delegate defaults to withholding and the user's
|
|
141
174
|
// "include private info" setting silently does nothing here.
|
|
142
175
|
includePrivateData,
|
|
176
|
+
includeLookupData,
|
|
177
|
+
exportSettings,
|
|
178
|
+
exportData,
|
|
179
|
+
exportTitle,
|
|
143
180
|
mainHandler,
|
|
144
181
|
includeFieldsFrom
|
|
145
182
|
});
|
package/dist/activityScoring.js
CHANGED
|
@@ -127,6 +127,8 @@ function activityScorer(rules) {
|
|
|
127
127
|
score.alerts = ["duplicate"];
|
|
128
128
|
} else if (verdict) {
|
|
129
129
|
score.notices = [NOTICE_KEYS[verdict]];
|
|
130
|
+
} else if (priors.length > 0 && score.value > 0 && freshRefRescuesActivation) {
|
|
131
|
+
score.dupe = false;
|
|
130
132
|
}
|
|
131
133
|
const packed = packContact(day, band, mode, huntedRefs);
|
|
132
134
|
if (priors.length > 0) priors.push(packed);
|
package/dist/dxcc.js
CHANGED
|
@@ -2,8 +2,8 @@ import { parseCallsign } from "@ham2k/lib-callsigns";
|
|
|
2
2
|
import { annotateFromCountryFile, useBuiltinCountryFile } from "@ham2k/lib-country-files";
|
|
3
3
|
import { DXCC_BY_CODE } from "@ham2k/lib-dxcc-data";
|
|
4
4
|
useBuiltinCountryFile();
|
|
5
|
-
function annotateCallAgainstCountryFile(call) {
|
|
6
|
-
return annotateFromCountryFile(parseCallsign(call));
|
|
5
|
+
function annotateCallAgainstCountryFile(call, options) {
|
|
6
|
+
return annotateFromCountryFile(parseCallsign(call), options);
|
|
7
7
|
}
|
|
8
8
|
function entityPrefixForCall(call) {
|
|
9
9
|
if (!call) return void 0;
|
|
@@ -17,8 +17,21 @@ function regionAndCountryForDxccCode(dxccCode) {
|
|
|
17
17
|
const entity = DXCC_BY_CODE[dxccCode];
|
|
18
18
|
return { ituRegion: entity?.ituRegion, countryCode: entity?.countryCode };
|
|
19
19
|
}
|
|
20
|
+
function countryPrefixForCall(call) {
|
|
21
|
+
if (!call) return void 0;
|
|
22
|
+
const annotated = annotateCallAgainstCountryFile(call);
|
|
23
|
+
const { countryCode } = regionAndCountryForDxccCode(annotated.dxccCode);
|
|
24
|
+
return countryCode ? countryCode.toUpperCase() : annotated.entityPrefix;
|
|
25
|
+
}
|
|
26
|
+
function entityForCall(call) {
|
|
27
|
+
if (!call) return {};
|
|
28
|
+
const { dxccCode, continent, entityName } = annotateCallAgainstCountryFile(call);
|
|
29
|
+
return { dxccCode, continent, entityName };
|
|
30
|
+
}
|
|
20
31
|
export {
|
|
21
32
|
annotateCallAgainstCountryFile,
|
|
33
|
+
countryPrefixForCall,
|
|
34
|
+
entityForCall,
|
|
22
35
|
entityPrefixForCall,
|
|
23
36
|
flagForDxccCode,
|
|
24
37
|
regionAndCountryForDxccCode
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { startMillisOf } from "./exportNames.js";
|
|
2
|
+
import { templateContext } from "./templateContext.js";
|
|
3
|
+
import { oneLine, renderTemplate } from "./templates.js";
|
|
4
|
+
import { isTestOperation } from "./testOperation.js";
|
|
5
|
+
const EXPORT_DEFAULTS = {
|
|
6
|
+
includePrivateData: false,
|
|
7
|
+
includeLookupData: true,
|
|
8
|
+
customTemplates: false,
|
|
9
|
+
filenameTemplate: "{{ op.date }} {{ log.station }}{% if log.ref != blank %} at {{ log.ref }}{% elsif log.activity != blank %} for {{ log.activity }}{% endif %} {{ log.modifier }}",
|
|
10
|
+
compactFilenameTemplate: "{{ log.station | dash }}{% if log.ref != blank %}@{{ log.ref | dash }}{% elsif log.activity != blank %}-{{ log.activity | downcase | dash }}{% endif %}-{{ op.dateCompact }}{% if log.modifier != blank %}-{{ log.modifier | downcase | dash }}{% endif %}",
|
|
11
|
+
titleTemplate: "{{ log.station }}{% if log.ref != blank %}: {{ log.activity }} at {{ log.ref }} {{ log.refName }}{% elsif log.activity != blank %}: {{ log.activity }}{% endif %} on {{ op.date }}",
|
|
12
|
+
adifNotesTemplate: "{{ qso.notes }}",
|
|
13
|
+
adifCommentTemplate: "{{ qso.notes }}",
|
|
14
|
+
adifQslMessageTemplate: ""
|
|
15
|
+
};
|
|
16
|
+
const GLOBAL_EXPORT_DEFAULTS = {
|
|
17
|
+
...EXPORT_DEFAULTS,
|
|
18
|
+
referenceFilenameTemplate: "{{ op.date }} {{ log.station }} at {{ log.ref }} {{ log.modifier }}",
|
|
19
|
+
referenceCompactFilenameTemplate: "{{ log.station | dash }}@{{ log.ref | dash }}-{{ op.dateCompact }}{% if log.modifier != blank %}-{{ log.modifier | downcase | dash }}{% endif %}",
|
|
20
|
+
referenceTitleTemplate: "{{ log.station }}: {{ log.activity }} at {{ log.ref }} {{ log.refName }} on {{ op.date }}",
|
|
21
|
+
otherFilenameTemplate: "{{ op.date }} {{ log.station }}{% if log.activity != blank %} for {{ log.activity }}{% endif %} {{ log.modifier }}",
|
|
22
|
+
otherCompactFilenameTemplate: "{{ log.station | dash }}{% if log.activity != blank %}-{{ log.activity | downcase | dash }}{% endif %}-{{ op.dateCompact }}{% if log.modifier != blank %}-{{ log.modifier | downcase | dash }}{% endif %}",
|
|
23
|
+
otherTitleTemplate: "{{ log.station }}{% if log.activity != blank %}: {{ log.activity }}{% endif %} on {{ op.date }}"
|
|
24
|
+
};
|
|
25
|
+
function resolveGlobalExportSettings(global = {}) {
|
|
26
|
+
const resolved = { ...GLOBAL_EXPORT_DEFAULTS, ...global };
|
|
27
|
+
for (const prefix of ["reference", "other"]) {
|
|
28
|
+
for (const suffix of ["FilenameTemplate", "CompactFilenameTemplate", "TitleTemplate"]) {
|
|
29
|
+
const key = `${prefix}${suffix}`;
|
|
30
|
+
const legacy = `${suffix[0].toLowerCase()}${suffix.slice(1)}`;
|
|
31
|
+
if (global[key] === void 0 && typeof global[legacy] === "string") {
|
|
32
|
+
Object.assign(resolved, { [key]: global[legacy] });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return resolved;
|
|
37
|
+
}
|
|
38
|
+
function exportTypeDefinition(activationType, format, label, defaults) {
|
|
39
|
+
return { exportType: `${activationType}-${format}`, activationType, format, label, defaults };
|
|
40
|
+
}
|
|
41
|
+
const templateKeys = [
|
|
42
|
+
"filenameTemplate",
|
|
43
|
+
"compactFilenameTemplate",
|
|
44
|
+
"titleTemplate",
|
|
45
|
+
"adifNotesTemplate",
|
|
46
|
+
"adifCommentTemplate",
|
|
47
|
+
"adifQslMessageTemplate"
|
|
48
|
+
];
|
|
49
|
+
function resolveExportSettings(definition, global = {}, overrides = {}) {
|
|
50
|
+
const defaults = resolveGlobalExportSettings(global);
|
|
51
|
+
const reference = definition.templateCategory === "reference";
|
|
52
|
+
const resolved = {
|
|
53
|
+
includePrivateData: defaults.includePrivateData,
|
|
54
|
+
includeLookupData: defaults.includeLookupData,
|
|
55
|
+
adifNotesTemplate: defaults.adifNotesTemplate,
|
|
56
|
+
adifCommentTemplate: defaults.adifCommentTemplate,
|
|
57
|
+
adifQslMessageTemplate: defaults.adifQslMessageTemplate,
|
|
58
|
+
filenameTemplate: reference ? defaults.referenceFilenameTemplate : defaults.otherFilenameTemplate,
|
|
59
|
+
compactFilenameTemplate: reference ? defaults.referenceCompactFilenameTemplate : defaults.otherCompactFilenameTemplate,
|
|
60
|
+
titleTemplate: reference ? defaults.referenceTitleTemplate : defaults.otherTitleTemplate,
|
|
61
|
+
...definition.defaults
|
|
62
|
+
};
|
|
63
|
+
for (const key of ["includePrivateData", "includeLookupData"]) {
|
|
64
|
+
if (typeof overrides[key] === "boolean") resolved[key] = overrides[key];
|
|
65
|
+
}
|
|
66
|
+
resolved.customTemplates = overrides.customTemplates === true;
|
|
67
|
+
if (resolved.customTemplates) {
|
|
68
|
+
for (const key of templateKeys) {
|
|
69
|
+
if (typeof overrides[key] === "string") resolved[key] = overrides[key];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (definition.format !== "adif") {
|
|
73
|
+
delete resolved.includePrivateData;
|
|
74
|
+
delete resolved.includeLookupData;
|
|
75
|
+
for (const key of templateKeys.slice(2)) delete resolved[key];
|
|
76
|
+
}
|
|
77
|
+
return resolved;
|
|
78
|
+
}
|
|
79
|
+
function exportQso(qso, settings) {
|
|
80
|
+
const copy = { ...qso };
|
|
81
|
+
for (const side of ["their", "our"]) {
|
|
82
|
+
if (copy[side] && typeof copy[side] === "object" && !Array.isArray(copy[side])) {
|
|
83
|
+
const station = { ...copy[side] };
|
|
84
|
+
if (settings.includeLookupData === false) delete station.guess;
|
|
85
|
+
if (settings.includePrivateData === false) {
|
|
86
|
+
for (const key of ["name", "city", "email", "notes", "lat", "lon", "multiIdentifier"]) delete station[key];
|
|
87
|
+
if (typeof station.grid === "string") station.grid = station.grid.slice(0, 6);
|
|
88
|
+
if (station.guess && typeof station.guess === "object") {
|
|
89
|
+
const guess = { ...station.guess };
|
|
90
|
+
for (const key of ["name", "city", "email", "notes", "lat", "lon", "multiIdentifier"]) delete guess[key];
|
|
91
|
+
if (typeof guess.grid === "string") guess.grid = guess.grid.slice(0, 6);
|
|
92
|
+
station.guess = guess;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
copy[side] = station;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (settings.includePrivateData === false) delete copy.notes;
|
|
99
|
+
return copy;
|
|
100
|
+
}
|
|
101
|
+
function prepareExportOption(option, operation, qsos, compact) {
|
|
102
|
+
const settings = option.exportSettings;
|
|
103
|
+
const extension = { adif: "adi", cabrillo: "log", reg1test: "edi" }[option.format] ?? option.filename?.split(".").pop() ?? "txt";
|
|
104
|
+
const data = {
|
|
105
|
+
station: String(operation.stationCall ?? ""),
|
|
106
|
+
ref: "",
|
|
107
|
+
activity: "",
|
|
108
|
+
modifier: "",
|
|
109
|
+
...option.templateData,
|
|
110
|
+
exportType: option.exportType,
|
|
111
|
+
format: option.format,
|
|
112
|
+
extension,
|
|
113
|
+
compact
|
|
114
|
+
};
|
|
115
|
+
if (isTestOperation(operation.stationCall)) data.modifier = [data.modifier, "Testing"].filter(Boolean).join(" ");
|
|
116
|
+
const context = templateContext({ operation, qsoCount: option.qsoCount ?? qsos.length, atMillis: startMillisOf(operation, qsos) ?? Date.now(), log: data });
|
|
117
|
+
const template = compact ? settings.compactFilenameTemplate : settings.filenameTemplate;
|
|
118
|
+
const rendered = oneLine(renderTemplate(template ?? "", context, "Export filename"));
|
|
119
|
+
const stem = rendered.replace(/[/\\:*?"<>|\x00-\x1f]+/g, "-").replace(/[. ]+$/g, "").trim() || "export";
|
|
120
|
+
const filename = stem.toLowerCase().endsWith(`.${extension}`) ? stem : `${stem}.${extension}`;
|
|
121
|
+
const titleContext = settings.includePrivateData === false ? { ...context, op: { ...context.op, title: "", userTitle: "", userNotes: "", grid: String(context.op.grid ?? "").slice(0, 6) }, log: { ...context.log, modifier: "" } } : context;
|
|
122
|
+
const title = oneLine(renderTemplate(settings.titleTemplate ?? "", titleContext, "Export title"));
|
|
123
|
+
return { ...option, filename, exportTitle: title, exportData: settings.includePrivateData === false ? { ...data, modifier: "" } : data };
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
EXPORT_DEFAULTS,
|
|
127
|
+
GLOBAL_EXPORT_DEFAULTS,
|
|
128
|
+
exportQso,
|
|
129
|
+
exportTypeDefinition,
|
|
130
|
+
prepareExportOption,
|
|
131
|
+
resolveExportSettings,
|
|
132
|
+
resolveGlobalExportSettings
|
|
133
|
+
};
|