riverbankcms 0.23.0 → 0.25.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/dist/acceptance/matrix.mjs +3 -9
- package/dist/cli/index.mjs +1653 -1210
- package/dist/cli/init-docs/content/cli-reference.md +0 -11
- package/dist/cli/init-docs/content/content-management.md +31 -12
- package/dist/{entryCommands-BdN5YrqI.mjs → entryCommands-nATGxgma.mjs} +6687 -5462
- package/dist/{eventUpsertInputSchema-kp-U13TZ.mjs → eventUpsertInputSchema-XRzH6yFb.mjs} +9 -4
- package/dist/{load-config-D5DDfmv1.mjs → load-config-DwiS-Ibn.mjs} +2 -2
- package/dist/load-config-zu397zDX.mjs +4 -0
- package/dist/{plan-6m8gsGI8.mjs → plan-ChNV88KF.mjs} +1 -1
- package/dist/scaffold/index.mjs +2 -2
- package/dist/{sdk-event-upsert-BnBxyTR6.mjs → sdk-event-upsert-4t4e1z-h.mjs} +12017 -9460
- package/dist/{src-BPh_OivC.mjs → src-LhtqTjH7.mjs} +1538 -89
- package/dist/testing.mjs +4 -4
- package/package.json +1 -1
- package/dist/load-config-C7Rd822n.mjs +0 -4
|
@@ -52,7 +52,7 @@ const unsafeAsPageId = (id) => id;
|
|
|
52
52
|
const unsafeAsPageSurfaceId = (id) => id;
|
|
53
53
|
const unsafeAsEditLeaseToken = (id) => id;
|
|
54
54
|
const unsafeAsEditorClientId = (id) => id;
|
|
55
|
-
const
|
|
55
|
+
const unsafeAsEditorSessionGeneration = (id) => id;
|
|
56
56
|
const unsafeAsOccurrenceId = (id) => id;
|
|
57
57
|
const unsafeAsEventOfferingId = (id) => id;
|
|
58
58
|
const unsafeAsEventSeriesId = (id) => id;
|
|
@@ -167,7 +167,7 @@ const parsePageId = (value) => parseUuidBrand(value, unsafeAsPageId);
|
|
|
167
167
|
const parsePageSurfaceId = (value) => parseUuidBrand(value, unsafeAsPageSurfaceId);
|
|
168
168
|
const parseEditLeaseToken = (value) => parseUuidBrand(value, unsafeAsEditLeaseToken);
|
|
169
169
|
const parseEditorClientId = (value) => parseUuidBrand(value, unsafeAsEditorClientId);
|
|
170
|
-
const
|
|
170
|
+
const parseEditorSessionGeneration = (value) => parseUuidBrand(value, unsafeAsEditorSessionGeneration);
|
|
171
171
|
const parseOccurrenceId = (value) => parseUuidBrand(value, unsafeAsOccurrenceId);
|
|
172
172
|
const parseEventOfferingId = (value) => parseUuidBrand(value, unsafeAsEventOfferingId);
|
|
173
173
|
const parseEventAttendeeTicketLineId = (value) => parseUuidBrand(value, unsafeAsEventAttendeeTicketLineId);
|
|
@@ -1478,7 +1478,7 @@ function toPositiveTicketQuantity(value) {
|
|
|
1478
1478
|
* null when the total is not a positive whole number.
|
|
1479
1479
|
*/
|
|
1480
1480
|
function deriveTotalTicketQuantity(lines) {
|
|
1481
|
-
const total = lines.reduce((sum, line) => sum + line.quantity, 0);
|
|
1481
|
+
const total = lines.reduce((sum, line$1) => sum + line$1.quantity, 0);
|
|
1482
1482
|
const parsed = toPositiveTicketQuantity(total);
|
|
1483
1483
|
return parsed.ok ? parsed.value : null;
|
|
1484
1484
|
}
|
|
@@ -2001,8 +2001,8 @@ function resolveBookingTimezone(venueTimezone, siteDefaultTimezone) {
|
|
|
2001
2001
|
return venueTimezone ?? siteDefaultTimezone;
|
|
2002
2002
|
}
|
|
2003
2003
|
function resolveBookingTimeZoneId(...candidates) {
|
|
2004
|
-
for (const candidate of candidates) {
|
|
2005
|
-
const normalized = typeof candidate === "string" ? candidate.trim() : "";
|
|
2004
|
+
for (const candidate$1 of candidates) {
|
|
2005
|
+
const normalized = typeof candidate$1 === "string" ? candidate$1.trim() : "";
|
|
2006
2006
|
if (normalized && isValidBookingTimeZone(normalized)) {
|
|
2007
2007
|
return normalized;
|
|
2008
2008
|
}
|
|
@@ -2222,8 +2222,8 @@ function parseBookingLocalDate(value) {
|
|
|
2222
2222
|
const year = Number(match.groups.year);
|
|
2223
2223
|
const month = Number(match.groups.month);
|
|
2224
2224
|
const day = Number(match.groups.day);
|
|
2225
|
-
const candidate = new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
|
|
2226
|
-
if (Number.isNaN(candidate.getTime()) || candidate.getUTCFullYear() !== year || candidate.getUTCMonth() + 1 !== month || candidate.getUTCDate() !== day) {
|
|
2225
|
+
const candidate$1 = new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
|
|
2226
|
+
if (Number.isNaN(candidate$1.getTime()) || candidate$1.getUTCFullYear() !== year || candidate$1.getUTCMonth() + 1 !== month || candidate$1.getUTCDate() !== day) {
|
|
2227
2227
|
throw new Error(`Invalid booking date format: "${value}" (expected YYYY-MM-DD)`);
|
|
2228
2228
|
}
|
|
2229
2229
|
return {
|
|
@@ -2567,12 +2567,12 @@ function decideRangeLabels(span, context, profile) {
|
|
|
2567
2567
|
label: start
|
|
2568
2568
|
};
|
|
2569
2569
|
}
|
|
2570
|
-
function presentRangeEndpoint(value, context, profile, label) {
|
|
2570
|
+
function presentRangeEndpoint(value, context, profile, label$1) {
|
|
2571
2571
|
const date = presentDate({
|
|
2572
2572
|
kind: "instant",
|
|
2573
2573
|
value
|
|
2574
2574
|
}, context, profile);
|
|
2575
|
-
const time = label === undefined ? presentTime(value, context, profile) : `${presentClock(value, context, profile.clock)} ${label}`;
|
|
2575
|
+
const time = label$1 === undefined ? presentTime(value, context, profile) : `${presentClock(value, context, profile.clock)} ${label$1}`;
|
|
2576
2576
|
return `${date}, ${time}`;
|
|
2577
2577
|
}
|
|
2578
2578
|
function presentNativeRange(span, context, format) {
|
|
@@ -3024,7 +3024,7 @@ function selectCategoryView(categories, services, filterCategoryId) {
|
|
|
3024
3024
|
layout: publicLayout()
|
|
3025
3025
|
};
|
|
3026
3026
|
}
|
|
3027
|
-
const category = categories.find((candidate) => candidate.id === filterCategoryId);
|
|
3027
|
+
const category = categories.find((candidate$1) => candidate$1.id === filterCategoryId);
|
|
3028
3028
|
if (category === undefined) {
|
|
3029
3029
|
return {
|
|
3030
3030
|
kind: "fallback-all",
|
|
@@ -3113,6 +3113,169 @@ const RECTIFICATION_HISTORY_PROPAGATION_PLANS = {
|
|
|
3113
3113
|
blocked_immutable_history: { kind: "blocked_immutable_history" }
|
|
3114
3114
|
};
|
|
3115
3115
|
|
|
3116
|
+
//#endregion
|
|
3117
|
+
//#region ../core/src/map-drawing/model.ts
|
|
3118
|
+
const MAX_DRAWING_BYTES = 1024 * 1024;
|
|
3119
|
+
const DRAWING_SIZE = 800;
|
|
3120
|
+
const DRAWING_SCHEMA_VERSION = 2;
|
|
3121
|
+
const DRAWING_GENERATOR_VERSION = 3;
|
|
3122
|
+
/** Current real city extracts fit with >2x headroom; reservations precede allocation. */
|
|
3123
|
+
const DRAWING_DECODE_LIMITS = {
|
|
3124
|
+
pathsPerFeature: 2048,
|
|
3125
|
+
verticesPerPath: 8192,
|
|
3126
|
+
verticesPerFeature: 32768,
|
|
3127
|
+
featuresPerLayer: 1e4,
|
|
3128
|
+
featuresPerTile: 16e3,
|
|
3129
|
+
featuresPerAttempt: 6e4,
|
|
3130
|
+
verticesPerAttempt: 1e6
|
|
3131
|
+
};
|
|
3132
|
+
/**
|
|
3133
|
+
* The two fixed framings. Neighbourhood draws 1 m per view-box unit, wider 4 m, so the same stroke and label
|
|
3134
|
+
* sizes read well in both. The wider view leaves out buildings, distant paths and small green patches, which turn to
|
|
3135
|
+
* noise at that scale. `minParkArea` is in square view-box units. Labels go to the nearest streets first, with a
|
|
3136
|
+
* minor street counted as `minorLabelDistance` times further away than it is, so the wider view names main roads.
|
|
3137
|
+
*/
|
|
3138
|
+
const FRAMINGS = [{
|
|
3139
|
+
name: "neighbourhood",
|
|
3140
|
+
radiusMeters: 400,
|
|
3141
|
+
zoom: 15,
|
|
3142
|
+
roads: [
|
|
3143
|
+
"major",
|
|
3144
|
+
"minor",
|
|
3145
|
+
"path"
|
|
3146
|
+
],
|
|
3147
|
+
maxPathDistance: 800,
|
|
3148
|
+
buildings: true,
|
|
3149
|
+
minParkArea: .3,
|
|
3150
|
+
maxLabels: 6,
|
|
3151
|
+
minorLabelDistance: 1.5
|
|
3152
|
+
}, {
|
|
3153
|
+
name: "wider",
|
|
3154
|
+
radiusMeters: 1600,
|
|
3155
|
+
zoom: 14,
|
|
3156
|
+
roads: [
|
|
3157
|
+
"major",
|
|
3158
|
+
"minor",
|
|
3159
|
+
"path"
|
|
3160
|
+
],
|
|
3161
|
+
maxPathDistance: 100,
|
|
3162
|
+
buildings: false,
|
|
3163
|
+
minParkArea: 40,
|
|
3164
|
+
maxLabels: 5,
|
|
3165
|
+
minorLabelDistance: 4
|
|
3166
|
+
}];
|
|
3167
|
+
const tilePoint = z.tuple([z.number().finite().min(-8192).max(12288), z.number().finite().min(-8192).max(12288)]).readonly();
|
|
3168
|
+
const paths = z.array(z.array(tilePoint).min(2).max(DRAWING_DECODE_LIMITS.verticesPerPath).readonly()).min(1).max(DRAWING_DECODE_LIMITS.pathsPerFeature).readonly();
|
|
3169
|
+
const featureFields = {
|
|
3170
|
+
kind: z.string().max(80),
|
|
3171
|
+
name: z.string().trim().max(60).regex(/^[^<>\u0000-\u001f\u007f]*$/).nullish().transform((value) => value === undefined || value === null || value === "" ? null : value),
|
|
3172
|
+
sortRank: z.number().finite().min(-1e4).max(1e4).default(0),
|
|
3173
|
+
paths: paths.refine((value) => value.reduce((n, path) => n + path.length, 0) <= DRAWING_DECODE_LIMITS.verticesPerFeature)
|
|
3174
|
+
};
|
|
3175
|
+
/** An unusable optional label must not discard otherwise valid road geometry. */
|
|
3176
|
+
function normalizeMapDrawingName(value) {
|
|
3177
|
+
const parsed = featureFields.name.safeParse(value);
|
|
3178
|
+
return parsed.success ? parsed.data : null;
|
|
3179
|
+
}
|
|
3180
|
+
const feature = z.discriminatedUnion("geometry", [z.strictObject({
|
|
3181
|
+
...featureFields,
|
|
3182
|
+
layer: z.literal("roads"),
|
|
3183
|
+
geometry: z.literal("line")
|
|
3184
|
+
}).readonly(), z.strictObject({
|
|
3185
|
+
...featureFields,
|
|
3186
|
+
layer: z.enum([
|
|
3187
|
+
"earth",
|
|
3188
|
+
"water",
|
|
3189
|
+
"landuse",
|
|
3190
|
+
"buildings"
|
|
3191
|
+
]),
|
|
3192
|
+
geometry: z.literal("polygon")
|
|
3193
|
+
}).refine((value) => value.paths.every((ring) => ring.length >= 3)).readonly()]);
|
|
3194
|
+
const tile = z.strictObject({
|
|
3195
|
+
z: z.union([z.literal(14), z.literal(15)]),
|
|
3196
|
+
x: z.number().int().nonnegative(),
|
|
3197
|
+
y: z.number().int().nonnegative(),
|
|
3198
|
+
extent: z.literal(4096),
|
|
3199
|
+
features: z.array(feature).max(DRAWING_DECODE_LIMITS.featuresPerTile).readonly()
|
|
3200
|
+
}).refine((value) => value.x < 2 ** value.z && value.y < 2 ** value.z).readonly();
|
|
3201
|
+
const mapDrawingInputSchema = z.strictObject({
|
|
3202
|
+
buildId: z.string().regex(/^\d{8}$/),
|
|
3203
|
+
tileSchemaVersion: z.string().regex(/^4\.\d+\.\d+$/),
|
|
3204
|
+
pin: z.lazy(() => locationPinSchema),
|
|
3205
|
+
tiles: z.array(tile).min(1).max(96).readonly()
|
|
3206
|
+
}).superRefine((value, ctx) => {
|
|
3207
|
+
const features = value.tiles.flatMap((item) => item.features);
|
|
3208
|
+
if (features.length > DRAWING_DECODE_LIMITS.featuresPerAttempt || features.reduce((sum, item) => sum + item.paths.reduce((count, path) => count + path.length, 0), 0) > DRAWING_DECODE_LIMITS.verticesPerAttempt) {
|
|
3209
|
+
ctx.addIssue({
|
|
3210
|
+
code: "custom",
|
|
3211
|
+
message: "Decoded geometry exceeds the attempt budget."
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
}).readonly();
|
|
3215
|
+
const point = z.tuple([z.number().min(0).max(DRAWING_SIZE), z.number().min(0).max(DRAWING_SIZE)]).readonly();
|
|
3216
|
+
const polygon = z.strictObject({
|
|
3217
|
+
kind: z.literal("polygon"),
|
|
3218
|
+
fillRule: z.literal("evenodd"),
|
|
3219
|
+
className: z.enum([
|
|
3220
|
+
"map-land",
|
|
3221
|
+
"map-water",
|
|
3222
|
+
"map-park",
|
|
3223
|
+
"map-building"
|
|
3224
|
+
]),
|
|
3225
|
+
rings: z.array(z.array(point).min(3).max(8192).readonly()).min(1).max(DRAWING_DECODE_LIMITS.pathsPerFeature).readonly()
|
|
3226
|
+
}).readonly();
|
|
3227
|
+
const line = z.strictObject({
|
|
3228
|
+
kind: z.literal("line"),
|
|
3229
|
+
className: z.enum([
|
|
3230
|
+
"map-road-major-casing",
|
|
3231
|
+
"map-road-major",
|
|
3232
|
+
"map-road-minor-casing",
|
|
3233
|
+
"map-road-minor",
|
|
3234
|
+
"map-road-path"
|
|
3235
|
+
]),
|
|
3236
|
+
points: z.array(point).min(2).max(8192).readonly()
|
|
3237
|
+
}).readonly();
|
|
3238
|
+
const drawingPathSchema = z.discriminatedUnion("kind", [polygon, line]);
|
|
3239
|
+
const label = z.strictObject({
|
|
3240
|
+
className: z.literal("map-label"),
|
|
3241
|
+
haloClassName: z.literal("map-label-halo"),
|
|
3242
|
+
text: z.string().trim().min(1).max(60).regex(/^[^<>\u0000-\u001f\u007f]*$/),
|
|
3243
|
+
point,
|
|
3244
|
+
angle: z.number().min(-90).max(90)
|
|
3245
|
+
}).readonly();
|
|
3246
|
+
const drawingFrameSchema = z.strictObject({
|
|
3247
|
+
radiusMeters: z.union([z.literal(FRAMINGS[0].radiusMeters), z.literal(FRAMINGS[1].radiusMeters)]),
|
|
3248
|
+
viewBox: z.tuple([
|
|
3249
|
+
z.literal(0),
|
|
3250
|
+
z.literal(0),
|
|
3251
|
+
z.literal(800),
|
|
3252
|
+
z.literal(800)
|
|
3253
|
+
]).readonly(),
|
|
3254
|
+
backgroundClass: z.literal("map-water"),
|
|
3255
|
+
paths: z.array(drawingPathSchema).max(1800).readonly(),
|
|
3256
|
+
labels: z.array(label).max(12).readonly(),
|
|
3257
|
+
pin: z.strictObject({
|
|
3258
|
+
className: z.literal("map-pin"),
|
|
3259
|
+
point: z.tuple([z.literal(400), z.literal(400)]).readonly()
|
|
3260
|
+
}).readonly()
|
|
3261
|
+
}).superRefine((value, ctx) => {
|
|
3262
|
+
const count = value.paths.reduce((sum, path) => sum + (path.kind === "line" ? path.points.length : path.rings.reduce((n, ring) => n + ring.length, 0)), 0);
|
|
3263
|
+
if (count > 5e4) ctx.addIssue({
|
|
3264
|
+
code: "custom",
|
|
3265
|
+
message: "Drawing exceeds its vertex budget."
|
|
3266
|
+
});
|
|
3267
|
+
}).readonly();
|
|
3268
|
+
const mapDrawingSchema = z.strictObject({
|
|
3269
|
+
schemaVersion: z.literal(DRAWING_SCHEMA_VERSION),
|
|
3270
|
+
generatorVersion: z.literal(DRAWING_GENERATOR_VERSION),
|
|
3271
|
+
buildId: z.string().regex(/^\d{8}$/),
|
|
3272
|
+
tileSchemaVersion: z.string().regex(/^4\.\d+\.\d+$/),
|
|
3273
|
+
frames: z.strictObject({
|
|
3274
|
+
neighbourhood: drawingFrameSchema,
|
|
3275
|
+
wider: drawingFrameSchema
|
|
3276
|
+
}).readonly()
|
|
3277
|
+
}).refine((value) => value.frames.neighbourhood.radiusMeters === FRAMINGS[0].radiusMeters && value.frames.wider.radiusMeters === FRAMINGS[1].radiusMeters).refine((value) => new TextEncoder().encode(JSON.stringify(value)).byteLength <= MAX_DRAWING_BYTES, { error: "Drawing exceeds its byte budget." }).readonly();
|
|
3278
|
+
|
|
3116
3279
|
//#endregion
|
|
3117
3280
|
//#region ../core/src/string.ts
|
|
3118
3281
|
/**
|
|
@@ -3200,6 +3363,7 @@ const locationValueSchema = z.strictObject({
|
|
|
3200
3363
|
pin: locationPinSchema.nullable().default(null),
|
|
3201
3364
|
doorNote: z.string().trim().max(1e3).nullish().transform(trimmedStringOrNull),
|
|
3202
3365
|
addressMatch: locationAddressMatchSchema.nullable().default(null),
|
|
3366
|
+
addressCheckSettled: z.literal(true).optional(),
|
|
3203
3367
|
ownerConfirmedPinAndAddress: z.boolean().default(false)
|
|
3204
3368
|
}).superRefine((value, context) => {
|
|
3205
3369
|
if (value.address === null && value.pin === null) {
|
|
@@ -3216,6 +3380,13 @@ const locationValueSchema = z.strictObject({
|
|
|
3216
3380
|
message: "Address evidence requires an address."
|
|
3217
3381
|
});
|
|
3218
3382
|
}
|
|
3383
|
+
if (value.address === null && value.addressCheckSettled) {
|
|
3384
|
+
context.addIssue({
|
|
3385
|
+
code: "custom",
|
|
3386
|
+
path: ["addressCheckSettled"],
|
|
3387
|
+
message: "A settled address check requires an address."
|
|
3388
|
+
});
|
|
3389
|
+
}
|
|
3219
3390
|
if (value.ownerConfirmedPinAndAddress && (value.address === null || value.pin === null)) {
|
|
3220
3391
|
context.addIssue({
|
|
3221
3392
|
code: "custom",
|
|
@@ -3232,7 +3403,8 @@ function parseLocationValue(input) {
|
|
|
3232
3403
|
issues: parsed.error.issues
|
|
3233
3404
|
});
|
|
3234
3405
|
}
|
|
3235
|
-
|
|
3406
|
+
/** One display formatter: a known matching site country is redundant. */
|
|
3407
|
+
function formatLocationAddress(address, siteCountry) {
|
|
3236
3408
|
if (address === null) return null;
|
|
3237
3409
|
const parts = [
|
|
3238
3410
|
address.building,
|
|
@@ -3242,15 +3414,32 @@ function formatLocationAddress(address) {
|
|
|
3242
3414
|
address.locality,
|
|
3243
3415
|
address.region,
|
|
3244
3416
|
address.postalCode,
|
|
3245
|
-
address.country
|
|
3417
|
+
sameLocationCountry(address.country, siteCountry) ? null : address.country
|
|
3246
3418
|
];
|
|
3247
3419
|
return parts.filter((part) => part !== null).join(", ") || null;
|
|
3248
3420
|
}
|
|
3421
|
+
const regionNames = new Intl.DisplayNames(["en"], { type: "region" });
|
|
3422
|
+
const countryIdentity = (country) => {
|
|
3423
|
+
const text = country.trim();
|
|
3424
|
+
const code = text.toUpperCase() === "UK" ? "GB" : text.toUpperCase();
|
|
3425
|
+
return (code.length === 2 && /^[A-Z]{2}$/.test(code) ? regionNames.of(code) ?? text : text).toLowerCase();
|
|
3426
|
+
};
|
|
3427
|
+
function sameLocationCountry(country, siteCountry) {
|
|
3428
|
+
return country !== null && Boolean(siteCountry?.trim()) && countryIdentity(country) === countryIdentity(siteCountry ?? "");
|
|
3429
|
+
}
|
|
3430
|
+
const publicLocationSchema = z.strictObject({
|
|
3431
|
+
drawing: z.lazy(() => mapDrawingSchema).optional().catch(undefined),
|
|
3432
|
+
address: locationAddressSchema.nullable(),
|
|
3433
|
+
displayAddress: z.string().nullable().default(null),
|
|
3434
|
+
pin: locationPinSchema.nullable(),
|
|
3435
|
+
doorNote: z.string().max(1e3).nullable(),
|
|
3436
|
+
attribution: z.array(locationAddressMatchSchema.unwrap().shape.attribution.unwrap().element).max(4).readonly()
|
|
3437
|
+
}).readonly();
|
|
3249
3438
|
/** Permission belongs to the resource/source owner before this projection. */
|
|
3250
|
-
function projectPublicLocation(location) {
|
|
3439
|
+
function projectPublicLocation(location, siteCountry) {
|
|
3251
3440
|
return {
|
|
3252
3441
|
address: location.address,
|
|
3253
|
-
displayAddress: formatLocationAddress(location.address),
|
|
3442
|
+
displayAddress: formatLocationAddress(location.address, siteCountry),
|
|
3254
3443
|
pin: location.pin,
|
|
3255
3444
|
doorNote: location.doorNote,
|
|
3256
3445
|
attribution: location.addressMatch?.attribution ?? []
|
|
@@ -3274,6 +3463,73 @@ function toLocationCoordinates(value) {
|
|
|
3274
3463
|
lng: value.lng
|
|
3275
3464
|
};
|
|
3276
3465
|
}
|
|
3466
|
+
/** Decide permission before loading or projecting the selected source. */
|
|
3467
|
+
function decideLocationSourcePermission(source, businessAddressVisibility) {
|
|
3468
|
+
if (source === "business_address" && businessAddressVisibility === undefined) return { kind: "unresolved" };
|
|
3469
|
+
return source === "business_address" && businessAddressVisibility === "hidden" ? { kind: "hidden" } : {
|
|
3470
|
+
kind: "permitted",
|
|
3471
|
+
source
|
|
3472
|
+
};
|
|
3473
|
+
}
|
|
3474
|
+
function resolvePermittedLocation(permission, location, siteCountry) {
|
|
3475
|
+
if (permission.kind !== "permitted") return permission;
|
|
3476
|
+
return location === null ? {
|
|
3477
|
+
kind: "missing",
|
|
3478
|
+
source: permission.source
|
|
3479
|
+
} : {
|
|
3480
|
+
kind: "visible",
|
|
3481
|
+
source: permission.source,
|
|
3482
|
+
location: projectPublicLocation(location, siteCountry)
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
//#endregion
|
|
3487
|
+
//#region ../core/src/locationLinks.ts
|
|
3488
|
+
/** Both outbound actions use the arrival pin, never match evidence. */
|
|
3489
|
+
function locationMapLinks(input) {
|
|
3490
|
+
const target = input.location != null ? `${input.location.lat},${input.location.lng}` : normalizeLocationAddressText(input.addressText);
|
|
3491
|
+
if (target === null) return {
|
|
3492
|
+
directions: null,
|
|
3493
|
+
googleMaps: null
|
|
3494
|
+
};
|
|
3495
|
+
const directions = new URL("https://www.google.com/maps/dir/");
|
|
3496
|
+
directions.searchParams.set("api", "1");
|
|
3497
|
+
directions.searchParams.set("destination", target);
|
|
3498
|
+
const search = new URL("https://www.google.com/maps/search/");
|
|
3499
|
+
search.searchParams.set("api", "1");
|
|
3500
|
+
search.searchParams.set("query", target);
|
|
3501
|
+
return {
|
|
3502
|
+
directions: directions.toString(),
|
|
3503
|
+
googleMaps: search.toString()
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
//#endregion
|
|
3508
|
+
//#region ../core/src/stable-json.ts
|
|
3509
|
+
function stableJsonStringify(value) {
|
|
3510
|
+
return JSON.stringify(sortStableJsonValue(value));
|
|
3511
|
+
}
|
|
3512
|
+
function sortStableJsonValue(value) {
|
|
3513
|
+
if (value === null) return null;
|
|
3514
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
3515
|
+
if (typeof value === "number") {
|
|
3516
|
+
if (!Number.isFinite(value)) {
|
|
3517
|
+
throw new TypeError("Stable JSON only supports finite numbers");
|
|
3518
|
+
}
|
|
3519
|
+
return value;
|
|
3520
|
+
}
|
|
3521
|
+
if (Array.isArray(value)) {
|
|
3522
|
+
return value.map(sortStableJsonValue);
|
|
3523
|
+
}
|
|
3524
|
+
if (typeof value === "object") {
|
|
3525
|
+
const prototype = Object.getPrototypeOf(value);
|
|
3526
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
3527
|
+
throw new TypeError("Stable JSON only supports plain objects");
|
|
3528
|
+
}
|
|
3529
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sortStableJsonValue(child)]));
|
|
3530
|
+
}
|
|
3531
|
+
throw new TypeError(`Stable JSON does not support ${typeof value}`);
|
|
3532
|
+
}
|
|
3277
3533
|
|
|
3278
3534
|
//#endregion
|
|
3279
3535
|
//#region ../core/src/location-relationship.ts
|
|
@@ -3293,15 +3549,15 @@ function decideLocationRelationship(location) {
|
|
|
3293
3549
|
if (location === null) return { kind: "empty" };
|
|
3294
3550
|
if (location.pin === null) return { kind: "address_only" };
|
|
3295
3551
|
if (location.address === null) return { kind: "pin_only" };
|
|
3296
|
-
if (location.addressMatch === null
|
|
3552
|
+
if (location.addressMatch === null) {
|
|
3297
3553
|
return { kind: "unchecked" };
|
|
3298
3554
|
}
|
|
3299
|
-
const { point, precision } = location.addressMatch;
|
|
3300
|
-
const distanceMetres = locationDistanceMetres(point, location.pin);
|
|
3301
|
-
const thresholdMetres = LOCATION_MATCH_THRESHOLDS_METRES[precision];
|
|
3555
|
+
const { point: point$1, precision } = location.addressMatch;
|
|
3556
|
+
const distanceMetres = locationDistanceMetres(point$1, location.pin);
|
|
3557
|
+
const thresholdMetres = LOCATION_MATCH_THRESHOLDS_METRES[precision === "unknown" ? "building" : precision];
|
|
3302
3558
|
const beyondThreshold = distanceMetres > thresholdMetres * (1 + 8 * Number.EPSILON);
|
|
3303
3559
|
return {
|
|
3304
|
-
kind: beyondThreshold ? "far" : precision === "building" ? "near" : "approximate",
|
|
3560
|
+
kind: beyondThreshold ? "far" : precision === "building" || precision === "unknown" ? "near" : "approximate",
|
|
3305
3561
|
distanceMetres,
|
|
3306
3562
|
thresholdMetres,
|
|
3307
3563
|
precision
|
|
@@ -3315,6 +3571,7 @@ const spatialAddressParts = [
|
|
|
3315
3571
|
"country"
|
|
3316
3572
|
];
|
|
3317
3573
|
const comparisonText = (text) => (text ?? "").trim().replace(/\s+/g, " ").toLowerCase();
|
|
3574
|
+
/** Address evidence identity excludes refinements such as unit, floor and region. */
|
|
3318
3575
|
function sameMeaningfulAddress(a, b) {
|
|
3319
3576
|
if (a === null || b === null) return a === b;
|
|
3320
3577
|
return spatialAddressParts.every((part) => comparisonText(a[part]) === comparisonText(b[part]));
|
|
@@ -3322,12 +3579,21 @@ function sameMeaningfulAddress(a, b) {
|
|
|
3322
3579
|
function samePin(a, b) {
|
|
3323
3580
|
return a === null || b === null ? a === b : a.lat === b.lat && a.lng === b.lng;
|
|
3324
3581
|
}
|
|
3582
|
+
/** Spatial identity ignores unit/floor/region/note edits; picker reply tags share this rule. */
|
|
3583
|
+
function sameLocationSpatialPair(a, b) {
|
|
3584
|
+
return a === null || b === null ? a === b : sameMeaningfulAddress(a.address, b.address) && samePin(a.pin, b.pin);
|
|
3585
|
+
}
|
|
3325
3586
|
/** Edits cannot carry forward old confirmation or address evidence by accident. */
|
|
3326
3587
|
function editLocationValue(previous, details) {
|
|
3327
3588
|
if (details.address === null && details.pin === null) return ok(null);
|
|
3589
|
+
const authored = {
|
|
3590
|
+
address: details.address,
|
|
3591
|
+
pin: details.pin,
|
|
3592
|
+
doorNote: details.doorNote
|
|
3593
|
+
};
|
|
3328
3594
|
if (previous === null) {
|
|
3329
3595
|
return parseLocationValue({
|
|
3330
|
-
...
|
|
3596
|
+
...authored,
|
|
3331
3597
|
addressMatch: null,
|
|
3332
3598
|
ownerConfirmedPinAndAddress: false
|
|
3333
3599
|
});
|
|
@@ -3335,8 +3601,9 @@ function editLocationValue(previous, details) {
|
|
|
3335
3601
|
const sameAddress = sameMeaningfulAddress(previous.address, details.address);
|
|
3336
3602
|
const samePair = sameAddress && samePin(previous.pin, details.pin);
|
|
3337
3603
|
return parseLocationValue({
|
|
3338
|
-
...
|
|
3604
|
+
...authored,
|
|
3339
3605
|
addressMatch: sameAddress ? previous.addressMatch : null,
|
|
3606
|
+
...sameAddress && previous.addressCheckSettled ? { addressCheckSettled: true } : {},
|
|
3340
3607
|
ownerConfirmedPinAndAddress: samePair && previous.ownerConfirmedPinAndAddress
|
|
3341
3608
|
});
|
|
3342
3609
|
}
|
|
@@ -3365,6 +3632,21 @@ function decideLocationSave(location, activity) {
|
|
|
3365
3632
|
location
|
|
3366
3633
|
};
|
|
3367
3634
|
}
|
|
3635
|
+
/** Opening lookup evidence may change provider facts only, never the owner's saved place. */
|
|
3636
|
+
function planLocationOpeningEvidence(previous, evidence) {
|
|
3637
|
+
const parsed = parseLocationValue(evidence);
|
|
3638
|
+
if (previous === null || !parsed.ok || parsed.value === null) return err({ kind: "invalid_location" });
|
|
3639
|
+
const next = parsed.value;
|
|
3640
|
+
const authored = (value) => ({
|
|
3641
|
+
address: value.address,
|
|
3642
|
+
pin: value.pin,
|
|
3643
|
+
doorNote: value.doorNote,
|
|
3644
|
+
ownerConfirmedPinAndAddress: value.ownerConfirmedPinAndAddress
|
|
3645
|
+
});
|
|
3646
|
+
if (stableJsonStringify(authored(previous)) !== stableJsonStringify(authored(next)) || next.addressMatch === null && next.addressCheckSettled !== true) return err({ kind: "invalid_location" });
|
|
3647
|
+
if (stableJsonStringify(previous) === stableJsonStringify(next)) return ok(next);
|
|
3648
|
+
return previous.addressMatch !== null || previous.addressCheckSettled === true ? err({ kind: "invalid_location" }) : ok(next);
|
|
3649
|
+
}
|
|
3368
3650
|
|
|
3369
3651
|
//#endregion
|
|
3370
3652
|
//#region ../core/src/location-migration.ts
|
|
@@ -3475,17 +3757,25 @@ function mapVenueAddress(address) {
|
|
|
3475
3757
|
};
|
|
3476
3758
|
return Object.values(structured).some((part) => displayComparison(part) !== "") ? structured : { street: firstText(address.formatted, address.summary) };
|
|
3477
3759
|
}
|
|
3478
|
-
|
|
3760
|
+
const displayTokens = (text) => (text ?? "").toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
3761
|
+
/** Only redundant trailing country text is disposable; ordered address words must all survive. */
|
|
3762
|
+
function comparableDisplay(text, countries) {
|
|
3763
|
+
const tokens = displayTokens(text);
|
|
3764
|
+
const suffixIndex = tokens.findIndex((_token, index) => countries.some((country) => sameLocationCountry(tokens.slice(index).join(" "), country)));
|
|
3765
|
+
return (suffixIndex < 0 ? tokens : tokens.slice(0, suffixIndex)).join(" ");
|
|
3766
|
+
}
|
|
3767
|
+
function checkDisplayText(result, text, siteCountry) {
|
|
3479
3768
|
if (!result.ok) return result;
|
|
3480
3769
|
const original = text?.trim() ?? "";
|
|
3481
3770
|
const derived = formatLocationAddress(result.value?.address ?? null);
|
|
3482
|
-
|
|
3771
|
+
const countries = [result.value?.address?.country, siteCountry];
|
|
3772
|
+
return original.length > 0 && comparableDisplay(original, countries) !== comparableDisplay(derived, countries) ? err({
|
|
3483
3773
|
kind: "display_address_conflict",
|
|
3484
3774
|
original,
|
|
3485
3775
|
derived
|
|
3486
3776
|
}) : result;
|
|
3487
3777
|
}
|
|
3488
|
-
function convertBusiness(value) {
|
|
3778
|
+
function convertBusiness(value, siteCountry) {
|
|
3489
3779
|
if (value == null) return ok(null);
|
|
3490
3780
|
const parsed = businessAddressSchema.safeParse(value);
|
|
3491
3781
|
if (!parsed.success) return err({
|
|
@@ -3500,7 +3790,7 @@ function convertBusiness(value) {
|
|
|
3500
3790
|
postalCode: parsed.data.postalCode,
|
|
3501
3791
|
country: parsed.data.countryCode
|
|
3502
3792
|
}, parsed.data);
|
|
3503
|
-
return checkDisplayText(result, parsed.data.displayAddress);
|
|
3793
|
+
return checkDisplayText(result, parsed.data.displayAddress, siteCountry);
|
|
3504
3794
|
}
|
|
3505
3795
|
function convertCustom(value) {
|
|
3506
3796
|
if (value == null) return ok(null);
|
|
@@ -3514,7 +3804,7 @@ function convertCustom(value) {
|
|
|
3514
3804
|
function convertLegacyLocation(input) {
|
|
3515
3805
|
switch (input.kind) {
|
|
3516
3806
|
case "venue": return convertVenue(input);
|
|
3517
|
-
case "business": return convertBusiness(input.value);
|
|
3807
|
+
case "business": return convertBusiness(input.value, input.siteCountry);
|
|
3518
3808
|
case "custom": return convertCustom(input.value);
|
|
3519
3809
|
}
|
|
3520
3810
|
}
|
|
@@ -3526,7 +3816,7 @@ function normalizeCountryCode(value) {
|
|
|
3526
3816
|
return trimmed !== null ? trimmed.toUpperCase() : null;
|
|
3527
3817
|
}
|
|
3528
3818
|
function normalizeCoordinate(value) {
|
|
3529
|
-
return typeof value === "number"
|
|
3819
|
+
return typeof value === "number" ? value : null;
|
|
3530
3820
|
}
|
|
3531
3821
|
function isLatitude(value) {
|
|
3532
3822
|
return value >= -90 && value <= 90;
|
|
@@ -3534,7 +3824,7 @@ function isLatitude(value) {
|
|
|
3534
3824
|
function isLongitude(value) {
|
|
3535
3825
|
return value >= -180 && value <= 180;
|
|
3536
3826
|
}
|
|
3537
|
-
function buildBusinessAddressDisplayText(input) {
|
|
3827
|
+
function buildBusinessAddressDisplayText(input, siteCountry) {
|
|
3538
3828
|
return formatLocationAddress({
|
|
3539
3829
|
building: null,
|
|
3540
3830
|
street: trimmedStringOrNull(input.addressLine1),
|
|
@@ -3544,7 +3834,7 @@ function buildBusinessAddressDisplayText(input) {
|
|
|
3544
3834
|
region: trimmedStringOrNull(input.region),
|
|
3545
3835
|
postalCode: trimmedStringOrNull(input.postalCode),
|
|
3546
3836
|
country: normalizeCountryCode(input.countryCode)
|
|
3547
|
-
});
|
|
3837
|
+
}, siteCountry);
|
|
3548
3838
|
}
|
|
3549
3839
|
function validateBusinessAddressDraft(input) {
|
|
3550
3840
|
const addressLine1 = trimmedStringOrNull(input.addressLine1);
|
|
@@ -3556,16 +3846,13 @@ function validateBusinessAddressDraft(input) {
|
|
|
3556
3846
|
const displayAddress = trimmedStringOrNull(input.displayAddress) ?? buildBusinessAddressDisplayText(input);
|
|
3557
3847
|
const lat = normalizeCoordinate(input.lat);
|
|
3558
3848
|
const lng = normalizeCoordinate(input.lng);
|
|
3559
|
-
const errors = [
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
if (lng === null) errors.push("missing_longitude");
|
|
3567
|
-
else if (!isLongitude(lng)) errors.push("invalid_longitude");
|
|
3568
|
-
if (errors.length > 0 || addressLine1 === null || locality === null || countryCode === null || displayAddress === null || lat === null || lng === null) {
|
|
3849
|
+
const errors = [...missingPostalFields({
|
|
3850
|
+
addressLine1,
|
|
3851
|
+
locality,
|
|
3852
|
+
countryCode,
|
|
3853
|
+
displayAddress
|
|
3854
|
+
}), ...businessPinErrors(lat, lng)];
|
|
3855
|
+
if (errors.length > 0 || addressLine1 === null || locality === null || countryCode === null || displayAddress === null) {
|
|
3569
3856
|
return {
|
|
3570
3857
|
success: false,
|
|
3571
3858
|
errors
|
|
@@ -3598,6 +3885,95 @@ function businessAddressToAddressedLocation(address) {
|
|
|
3598
3885
|
lng: address.lng
|
|
3599
3886
|
};
|
|
3600
3887
|
}
|
|
3888
|
+
/** Temporary postal/editor response adapter. Canonical storage never uses this shape. */
|
|
3889
|
+
function businessAddressFromLocation(location, siteCountry) {
|
|
3890
|
+
if (location === null || location.address === null) return null;
|
|
3891
|
+
const address = location.address;
|
|
3892
|
+
const displayAddress = formatLocationAddress(address, siteCountry);
|
|
3893
|
+
if (displayAddress === null) return null;
|
|
3894
|
+
const pin = location.pin ?? {
|
|
3895
|
+
lat: null,
|
|
3896
|
+
lng: null
|
|
3897
|
+
};
|
|
3898
|
+
return {
|
|
3899
|
+
addressLine1: [address.building, address.street].filter((part) => part !== null).join(", "),
|
|
3900
|
+
addressLine2: [address.unit, address.floor].filter((part) => part !== null).join(", ") || null,
|
|
3901
|
+
locality: address.locality ?? "",
|
|
3902
|
+
region: address.region,
|
|
3903
|
+
postalCode: address.postalCode,
|
|
3904
|
+
countryCode: address.country ?? "",
|
|
3905
|
+
displayAddress,
|
|
3906
|
+
lat: pin.lat,
|
|
3907
|
+
lng: pin.lng
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
/** Legacy editor inputs are explicit write adapters, never fallback storage readers. */
|
|
3911
|
+
function planBusinessLocationWrite(previous, input) {
|
|
3912
|
+
if ("locationValue" in input && input.expectedLocationValue !== undefined) {
|
|
3913
|
+
if (stableJsonStringify(previous) !== stableJsonStringify(input.expectedLocationValue)) return err({ kind: "invalid_location" });
|
|
3914
|
+
return planLocationOpeningEvidence(previous, input.locationValue);
|
|
3915
|
+
}
|
|
3916
|
+
const planned = "locationValue" in input ? parseLocationValue(input.locationValue) : planLegacyBusinessLocation(previous, input.businessAddress);
|
|
3917
|
+
if (!planned.ok) return err({ kind: "locationValue" in input ? "invalid_location" : "invalid_legacy_location" });
|
|
3918
|
+
const decision = decideLocationSave(planned.value, "idle");
|
|
3919
|
+
return decision.kind === "blocked" ? err({ kind: "unconfirmed_mismatch" }) : ok(decision.location);
|
|
3920
|
+
}
|
|
3921
|
+
function planLegacyBusinessLocation(previous, address) {
|
|
3922
|
+
if (address === null) return ok(null);
|
|
3923
|
+
if (previous === null) return convertLegacyBusinessEdit(previous, address);
|
|
3924
|
+
const projection = businessAddressFromLocation(previous);
|
|
3925
|
+
return projection !== null && sameBusinessPostalProjection(projection, address) ? editBusinessPin(previous, address) : convertLegacyBusinessEdit(previous, address);
|
|
3926
|
+
}
|
|
3927
|
+
function editBusinessPin(previous, address) {
|
|
3928
|
+
const pin = locationPinSchema.nullable().safeParse(address.lat === null && address.lng === null ? null : {
|
|
3929
|
+
lat: address.lat,
|
|
3930
|
+
lng: address.lng
|
|
3931
|
+
});
|
|
3932
|
+
return pin.success ? editLocationValue(previous, {
|
|
3933
|
+
address: previous.address,
|
|
3934
|
+
pin: pin.data,
|
|
3935
|
+
doorNote: previous.doorNote
|
|
3936
|
+
}) : err({ kind: "invalid_location" });
|
|
3937
|
+
}
|
|
3938
|
+
function convertLegacyBusinessEdit(previous, address) {
|
|
3939
|
+
const converted = convertLegacyLocation({
|
|
3940
|
+
kind: "business",
|
|
3941
|
+
value: address
|
|
3942
|
+
});
|
|
3943
|
+
if (!converted.ok || converted.value === null) return converted;
|
|
3944
|
+
return editLocationValue(previous, {
|
|
3945
|
+
address: converted.value.address,
|
|
3946
|
+
pin: converted.value.pin,
|
|
3947
|
+
doorNote: previous?.doorNote ?? null
|
|
3948
|
+
});
|
|
3949
|
+
}
|
|
3950
|
+
function sameBusinessPostalProjection(a, b) {
|
|
3951
|
+
const fields = [
|
|
3952
|
+
"addressLine1",
|
|
3953
|
+
"addressLine2",
|
|
3954
|
+
"locality",
|
|
3955
|
+
"region",
|
|
3956
|
+
"postalCode",
|
|
3957
|
+
"countryCode",
|
|
3958
|
+
"displayAddress"
|
|
3959
|
+
];
|
|
3960
|
+
return fields.every((field) => (a[field] ?? "").trim().replace(/\s+/g, " ").toLowerCase() === (b[field] ?? "").trim().replace(/\s+/g, " ").toLowerCase());
|
|
3961
|
+
}
|
|
3962
|
+
function missingPostalFields(fields) {
|
|
3963
|
+
const required = [
|
|
3964
|
+
[fields.addressLine1, "missing_address_line_1"],
|
|
3965
|
+
[fields.locality, "missing_locality"],
|
|
3966
|
+
[fields.countryCode, "missing_country_code"],
|
|
3967
|
+
[fields.displayAddress, "missing_display_address"]
|
|
3968
|
+
];
|
|
3969
|
+
return required.flatMap(([value, code]) => value === null ? [code] : []);
|
|
3970
|
+
}
|
|
3971
|
+
function businessPinErrors(lat, lng) {
|
|
3972
|
+
if (lat === null && lng === null) return [];
|
|
3973
|
+
const latitude = lat === null ? ["missing_latitude"] : !isLatitude(lat) ? ["invalid_latitude"] : [];
|
|
3974
|
+
const longitude = lng === null ? ["missing_longitude"] : !isLongitude(lng) ? ["invalid_longitude"] : [];
|
|
3975
|
+
return [...latitude, ...longitude];
|
|
3976
|
+
}
|
|
3601
3977
|
|
|
3602
3978
|
//#endregion
|
|
3603
3979
|
//#region ../core/src/try-async.ts
|
|
@@ -3718,16 +4094,8 @@ function syncStateOf(sync) {
|
|
|
3718
4094
|
const EDIT_LEASE_TTL_SECONDS = 60;
|
|
3719
4095
|
/** One-use navigation transport for the same previous-client acquire contract. */
|
|
3720
4096
|
const EDIT_LEASE_HANDOVER_QUERY_PARAM = "edit_lease_handover";
|
|
3721
|
-
/** Frozen at editor mount; reacquiring a lease never advances
|
|
3722
|
-
const
|
|
3723
|
-
function parsePageDocumentSessionCheckpoint(value) {
|
|
3724
|
-
if (value === "legacy") return { kind: "legacy" };
|
|
3725
|
-
const parsed = parsePageDocumentCheckpointId(value);
|
|
3726
|
-
return parsed.ok ? {
|
|
3727
|
-
kind: "document",
|
|
3728
|
-
checkpointId: parsed.value
|
|
3729
|
-
} : undefined;
|
|
3730
|
-
}
|
|
4097
|
+
/** Frozen at editor mount; reacquiring a lease never advances the server-issued generation. */
|
|
4098
|
+
const EDITOR_SESSION_GENERATION_HEADER = "X-Riverbank-Editor-Session-Generation";
|
|
3731
4099
|
function parseEditLeaseScope(input) {
|
|
3732
4100
|
switch (input.nodeType) {
|
|
3733
4101
|
case "page": {
|
|
@@ -4702,12 +5070,12 @@ function parsePublicSocialLinks(value) {
|
|
|
4702
5070
|
});
|
|
4703
5071
|
continue;
|
|
4704
5072
|
}
|
|
4705
|
-
const label = trimmedOptionalString(rawLink.label);
|
|
4706
|
-
if (rawLink.kind === "custom" && label !== null && label !== undefined) {
|
|
5073
|
+
const label$1 = trimmedOptionalString(rawLink.label);
|
|
5074
|
+
if (rawLink.kind === "custom" && label$1 !== null && label$1 !== undefined) {
|
|
4707
5075
|
links.push({
|
|
4708
5076
|
id: id.value,
|
|
4709
5077
|
kind: "custom",
|
|
4710
|
-
label,
|
|
5078
|
+
label: label$1,
|
|
4711
5079
|
url: url.value
|
|
4712
5080
|
});
|
|
4713
5081
|
continue;
|
|
@@ -5892,10 +6260,10 @@ function parseFooterEngagementSection(value) {
|
|
|
5892
6260
|
});
|
|
5893
6261
|
}
|
|
5894
6262
|
function upsertFooterEngagementSection(composition, section) {
|
|
5895
|
-
const hasEngagement = composition.sections.some((candidate) => candidate.kind === "engagement");
|
|
6263
|
+
const hasEngagement = composition.sections.some((candidate$1) => candidate$1.kind === "engagement");
|
|
5896
6264
|
return validateFooterComposition({
|
|
5897
6265
|
...composition,
|
|
5898
|
-
sections: hasEngagement ? composition.sections.map((candidate) => candidate.kind === "engagement" ? section : candidate) : [...composition.sections, section]
|
|
6266
|
+
sections: hasEngagement ? composition.sections.map((candidate$1) => candidate$1.kind === "engagement" ? section : candidate$1) : [...composition.sections, section]
|
|
5899
6267
|
});
|
|
5900
6268
|
}
|
|
5901
6269
|
function removeFooterEngagementSection(composition) {
|
|
@@ -6161,6 +6529,36 @@ function planMailingListSetupSwitch(input) {
|
|
|
6161
6529
|
affectedPlacementCount: input.placementModes.length
|
|
6162
6530
|
};
|
|
6163
6531
|
}
|
|
6532
|
+
const preparedSetupWrite = Symbol("prepared-mailing-list-setup-write");
|
|
6533
|
+
/** The current setup and real placement census decide the only permitted write shape. */
|
|
6534
|
+
function prepareMailingListSetupWrite(input, current, placementModes) {
|
|
6535
|
+
const revision = current.tag === "not_configured" ? null : current.revision;
|
|
6536
|
+
if (revision !== input.expectedRevision || current.siteId !== input.siteId) return { kind: "revision-conflict" };
|
|
6537
|
+
const switchPlan = planMailingListSetupSwitch({
|
|
6538
|
+
previousScope: current.tag === "configured" ? mailingListProviderScopeFromConfigured(current.provider) : null,
|
|
6539
|
+
nextScope: mailingListProviderScopeFromConfigured(input.provider),
|
|
6540
|
+
placementModes,
|
|
6541
|
+
defaultChanged: current.tag !== "configured" || !sameMailingListProviderDefault(current.provider, input.provider)
|
|
6542
|
+
});
|
|
6543
|
+
const plan = {
|
|
6544
|
+
...input,
|
|
6545
|
+
expectedRevision: revision ?? 0,
|
|
6546
|
+
kind: switchPlan.tag === "replace_scope" ? "reset" : "update",
|
|
6547
|
+
[preparedSetupWrite]: true
|
|
6548
|
+
};
|
|
6549
|
+
Object.defineProperty(plan, preparedSetupWrite, { enumerable: false });
|
|
6550
|
+
return {
|
|
6551
|
+
kind: "prepared",
|
|
6552
|
+
plan
|
|
6553
|
+
};
|
|
6554
|
+
}
|
|
6555
|
+
function sameMailingListProviderDefault(previous, next) {
|
|
6556
|
+
switch (previous.provider) {
|
|
6557
|
+
case "native": return next.provider === "native" && sameNewsletterAudience(previous.defaultAudience, next.defaultAudience);
|
|
6558
|
+
case "brevo": return next.provider === "brevo" && previous.defaultListId === next.defaultListId;
|
|
6559
|
+
case "mailchimp": return next.provider === "mailchimp" && previous.defaultAudienceId === next.defaultAudienceId;
|
|
6560
|
+
}
|
|
6561
|
+
}
|
|
6164
6562
|
function mailingListProviderScopeFromConfigured(provider) {
|
|
6165
6563
|
switch (provider.provider) {
|
|
6166
6564
|
case "native": return { provider: "native" };
|
|
@@ -6501,10 +6899,14 @@ function assessCustomTargeting(catalog, targeting) {
|
|
|
6501
6899
|
}
|
|
6502
6900
|
}
|
|
6503
6901
|
}
|
|
6902
|
+
/** Shared choice availability decision for both runtime catalogues and captured document facts. */
|
|
6903
|
+
function missingMailingListTargetingChoiceIds(targeting, choices) {
|
|
6904
|
+
const available = new Set(choices.map((choice) => choice.id));
|
|
6905
|
+
return mailingListTargetingChoiceIds(targeting).filter((id) => !available.has(id));
|
|
6906
|
+
}
|
|
6504
6907
|
function assessMatchedChoices(matched, choices) {
|
|
6505
6908
|
const selected = new Set(mailingListTargetingChoiceIds(matched.targeting));
|
|
6506
|
-
const
|
|
6507
|
-
const missingIds = [...selected].filter((id) => !available.has(id));
|
|
6909
|
+
const missingIds = missingMailingListTargetingChoiceIds(matched.targeting, choices);
|
|
6508
6910
|
const value = {
|
|
6509
6911
|
mode: "custom",
|
|
6510
6912
|
...matched,
|
|
@@ -6956,9 +7358,9 @@ function selectSubmissionPreviewFields(schemaFields, submissions) {
|
|
|
6956
7358
|
if (id === null) return null;
|
|
6957
7359
|
const type = (field.type ?? "text").toString().toLowerCase();
|
|
6958
7360
|
if (shouldHideInPreview(type)) return null;
|
|
6959
|
-
const label = trimmedStringOrNull(field.label?.toString()) ?? humanizeSubmissionFieldKey(id);
|
|
7361
|
+
const label$1 = trimmedStringOrNull(field.label?.toString()) ?? humanizeSubmissionFieldKey(id);
|
|
6960
7362
|
const normalizedId = id.toLowerCase();
|
|
6961
|
-
const normalizedLabel = label.toLowerCase();
|
|
7363
|
+
const normalizedLabel = label$1.toLowerCase();
|
|
6962
7364
|
let score = 0;
|
|
6963
7365
|
if (PRIORITY_IDS.has(normalizedId)) score += 500;
|
|
6964
7366
|
if (normalizedId.includes("name") || normalizedLabel.includes("name")) score += 550;
|
|
@@ -6981,7 +7383,7 @@ function selectSubmissionPreviewFields(schemaFields, submissions) {
|
|
|
6981
7383
|
}
|
|
6982
7384
|
return {
|
|
6983
7385
|
id,
|
|
6984
|
-
label,
|
|
7386
|
+
label: label$1,
|
|
6985
7387
|
type,
|
|
6986
7388
|
score,
|
|
6987
7389
|
index
|
|
@@ -7514,33 +7916,6 @@ function generateUuidV4(cryptoSource = globalThis.crypto) {
|
|
|
7514
7916
|
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10).join("")}`;
|
|
7515
7917
|
}
|
|
7516
7918
|
|
|
7517
|
-
//#endregion
|
|
7518
|
-
//#region ../core/src/stable-json.ts
|
|
7519
|
-
function stableJsonStringify(value) {
|
|
7520
|
-
return JSON.stringify(sortStableJsonValue(value));
|
|
7521
|
-
}
|
|
7522
|
-
function sortStableJsonValue(value) {
|
|
7523
|
-
if (value === null) return null;
|
|
7524
|
-
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
7525
|
-
if (typeof value === "number") {
|
|
7526
|
-
if (!Number.isFinite(value)) {
|
|
7527
|
-
throw new TypeError("Stable JSON only supports finite numbers");
|
|
7528
|
-
}
|
|
7529
|
-
return value;
|
|
7530
|
-
}
|
|
7531
|
-
if (Array.isArray(value)) {
|
|
7532
|
-
return value.map(sortStableJsonValue);
|
|
7533
|
-
}
|
|
7534
|
-
if (typeof value === "object") {
|
|
7535
|
-
const prototype = Object.getPrototypeOf(value);
|
|
7536
|
-
if (prototype !== Object.prototype && prototype !== null) {
|
|
7537
|
-
throw new TypeError("Stable JSON only supports plain objects");
|
|
7538
|
-
}
|
|
7539
|
-
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sortStableJsonValue(child)]));
|
|
7540
|
-
}
|
|
7541
|
-
throw new TypeError(`Stable JSON does not support ${typeof value}`);
|
|
7542
|
-
}
|
|
7543
|
-
|
|
7544
7919
|
//#endregion
|
|
7545
7920
|
//#region ../core/src/secret.ts
|
|
7546
7921
|
/**
|
|
@@ -7943,14 +8318,14 @@ const BOOKING_DEFAULTS_BY_BEHAVIOUR = {
|
|
|
7943
8318
|
memberships: "off"
|
|
7944
8319
|
}
|
|
7945
8320
|
};
|
|
7946
|
-
function verticalBookingPreset(id, verticalId, canonicalBehaviour, label, pluralLabel, description, availability$1 = "onboarding") {
|
|
8321
|
+
function verticalBookingPreset(id, verticalId, canonicalBehaviour, label$1, pluralLabel, description, availability$1 = "onboarding") {
|
|
7947
8322
|
return {
|
|
7948
8323
|
id,
|
|
7949
8324
|
verticalId,
|
|
7950
8325
|
canonicalBehaviour,
|
|
7951
8326
|
engine: BOOKING_ENGINE_BY_BEHAVIOUR[canonicalBehaviour],
|
|
7952
8327
|
availability: availability$1,
|
|
7953
|
-
label,
|
|
8328
|
+
label: label$1,
|
|
7954
8329
|
pluralLabel,
|
|
7955
8330
|
description
|
|
7956
8331
|
};
|
|
@@ -8826,6 +9201,616 @@ function resolvePublicSiteVisibility(input) {
|
|
|
8826
9201
|
return "live";
|
|
8827
9202
|
}
|
|
8828
9203
|
|
|
9204
|
+
//#endregion
|
|
9205
|
+
//#region ../core/src/map-drawing/geometry.ts
|
|
9206
|
+
const VIEW_BOUNDS = {
|
|
9207
|
+
left: 0,
|
|
9208
|
+
top: 0,
|
|
9209
|
+
right: 800,
|
|
9210
|
+
bottom: 800
|
|
9211
|
+
};
|
|
9212
|
+
function samePoint(a, b) {
|
|
9213
|
+
return a[0] === b[0] && a[1] === b[1];
|
|
9214
|
+
}
|
|
9215
|
+
function distance(a, b) {
|
|
9216
|
+
return Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
9217
|
+
}
|
|
9218
|
+
function roundCoordinate(value) {
|
|
9219
|
+
const rounded = Math.round(value * 10) / 10;
|
|
9220
|
+
return rounded === 0 ? 0 : rounded;
|
|
9221
|
+
}
|
|
9222
|
+
function roundPoint(point$1) {
|
|
9223
|
+
return [roundCoordinate(point$1[0]), roundCoordinate(point$1[1])];
|
|
9224
|
+
}
|
|
9225
|
+
/** Liang–Barsky: one bounded segment, no fabricated connecting line across the viewport. */
|
|
9226
|
+
function clipSegment(a, b, bounds) {
|
|
9227
|
+
const dx = b[0] - a[0], dy = b[1] - a[1];
|
|
9228
|
+
const edges = [
|
|
9229
|
+
[-dx, a[0] - bounds.left],
|
|
9230
|
+
[dx, bounds.right - a[0]],
|
|
9231
|
+
[-dy, a[1] - bounds.top],
|
|
9232
|
+
[dy, bounds.bottom - a[1]]
|
|
9233
|
+
];
|
|
9234
|
+
const interval = edges.reduce((range, [p, q]) => {
|
|
9235
|
+
if (range === null || p === 0 && q < 0) return null;
|
|
9236
|
+
if (p === 0) return range;
|
|
9237
|
+
const ratio = q / p;
|
|
9238
|
+
const next = p < 0 ? [Math.max(range[0], ratio), range[1]] : [range[0], Math.min(range[1], ratio)];
|
|
9239
|
+
return next[0] > next[1] ? null : next;
|
|
9240
|
+
}, [0, 1]);
|
|
9241
|
+
return interval === null ? null : [[a[0] + interval[0] * dx, a[1] + interval[0] * dy], [a[0] + interval[1] * dx, a[1] + interval[1] * dy]];
|
|
9242
|
+
}
|
|
9243
|
+
function segments(points, closed = false) {
|
|
9244
|
+
return points.flatMap((start, index) => {
|
|
9245
|
+
const end = points[index + 1] ?? (closed ? points[0] : undefined);
|
|
9246
|
+
return end === undefined ? [] : [{
|
|
9247
|
+
start,
|
|
9248
|
+
end
|
|
9249
|
+
}];
|
|
9250
|
+
});
|
|
9251
|
+
}
|
|
9252
|
+
function clipLine(points, bounds) {
|
|
9253
|
+
return segments(points).reduce((paths$1, { start, end }) => {
|
|
9254
|
+
const segment = clipSegment(start, end, bounds);
|
|
9255
|
+
if (segment === null || samePoint(segment[0], segment[1])) return paths$1;
|
|
9256
|
+
const previous = paths$1[paths$1.length - 1];
|
|
9257
|
+
const last = previous?.[previous.length - 1];
|
|
9258
|
+
return previous !== undefined && last !== undefined && samePoint(last, segment[0]) ? [...paths$1.slice(0, -1), [...previous, segment[1]]] : [...paths$1, segment];
|
|
9259
|
+
}, []);
|
|
9260
|
+
}
|
|
9261
|
+
function polygonEdges(bounds) {
|
|
9262
|
+
const vertical = (x) => (a, b) => [x, a[1] + (b[1] - a[1]) * (x - a[0]) / (b[0] - a[0])];
|
|
9263
|
+
const horizontal = (y) => (a, b) => [a[0] + (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]), y];
|
|
9264
|
+
return [
|
|
9265
|
+
{
|
|
9266
|
+
inside: (p) => p[0] >= bounds.left,
|
|
9267
|
+
cross: vertical(bounds.left)
|
|
9268
|
+
},
|
|
9269
|
+
{
|
|
9270
|
+
inside: (p) => p[0] <= bounds.right,
|
|
9271
|
+
cross: vertical(bounds.right)
|
|
9272
|
+
},
|
|
9273
|
+
{
|
|
9274
|
+
inside: (p) => p[1] >= bounds.top,
|
|
9275
|
+
cross: horizontal(bounds.top)
|
|
9276
|
+
},
|
|
9277
|
+
{
|
|
9278
|
+
inside: (p) => p[1] <= bounds.bottom,
|
|
9279
|
+
cross: horizontal(bounds.bottom)
|
|
9280
|
+
}
|
|
9281
|
+
];
|
|
9282
|
+
}
|
|
9283
|
+
function clipPolygonEdge(points, edge) {
|
|
9284
|
+
return segments(points, true).flatMap(({ start, end }) => {
|
|
9285
|
+
const wasInside = edge.inside(start), isInside = edge.inside(end);
|
|
9286
|
+
if (wasInside && isInside) return [end];
|
|
9287
|
+
if (wasInside) return [edge.cross(start, end)];
|
|
9288
|
+
return isInside ? [edge.cross(start, end), end] : [];
|
|
9289
|
+
});
|
|
9290
|
+
}
|
|
9291
|
+
function clipPolygon(points, bounds) {
|
|
9292
|
+
const first = points[0], last = points[points.length - 1];
|
|
9293
|
+
const open = first !== undefined && last !== undefined && samePoint(first, last) ? points.slice(0, -1) : points;
|
|
9294
|
+
return polygonEdges(bounds).reduce((ring, edge) => clipPolygonEdge(ring, edge), open);
|
|
9295
|
+
}
|
|
9296
|
+
function deviation(point$1, start, end) {
|
|
9297
|
+
const dx = end[0] - start[0], dy = end[1] - start[1];
|
|
9298
|
+
const fraction = Math.max(0, Math.min(1, ((point$1[0] - start[0]) * dx + (point$1[1] - start[1]) * dy) / (dx * dx + dy * dy || 1)));
|
|
9299
|
+
return distance(point$1, [start[0] + fraction * dx, start[1] + fraction * dy]);
|
|
9300
|
+
}
|
|
9301
|
+
function simplifyChunk(points) {
|
|
9302
|
+
const start = points[0], end = points[points.length - 1];
|
|
9303
|
+
if (start === undefined || end === undefined || points.length <= 2) return points;
|
|
9304
|
+
const furthest = points.slice(1, -1).reduce((best, point$1, index) => {
|
|
9305
|
+
const error = deviation(point$1, start, end);
|
|
9306
|
+
return error > best.error ? {
|
|
9307
|
+
error,
|
|
9308
|
+
index: index + 1
|
|
9309
|
+
} : best;
|
|
9310
|
+
}, {
|
|
9311
|
+
error: .8,
|
|
9312
|
+
index: -1
|
|
9313
|
+
});
|
|
9314
|
+
return furthest.index < 0 ? [start, end] : [...simplifyChunk(points.slice(0, furthest.index + 1)).slice(0, -1), ...simplifyChunk(points.slice(furthest.index))];
|
|
9315
|
+
}
|
|
9316
|
+
/** Douglas–Peucker in overlapping 256-point chunks bounds recursion/work on hostile zigzags. */
|
|
9317
|
+
function simplify(points, closed) {
|
|
9318
|
+
const rounded = points.map(roundPoint);
|
|
9319
|
+
const open = rounded.filter((point$1, index) => rounded[index - 1] === undefined || !samePoint(point$1, rounded[index - 1] ?? point$1));
|
|
9320
|
+
const first = open[0], last = open[open.length - 1];
|
|
9321
|
+
if (first === undefined || last === undefined) return [];
|
|
9322
|
+
const input = closed && !samePoint(first, last) ? [...open, first] : open;
|
|
9323
|
+
const kept = Array.from({ length: Math.ceil((input.length - 1) / 255) }, (_, index) => simplifyChunk(input.slice(index * 255, index * 255 + 256))).flatMap((chunk, index) => index === 0 ? chunk : chunk.slice(1));
|
|
9324
|
+
const start = kept[0], end = kept[kept.length - 1];
|
|
9325
|
+
return closed && start !== undefined && end !== undefined && samePoint(start, end) ? kept.slice(0, -1) : kept;
|
|
9326
|
+
}
|
|
9327
|
+
function expandBounds(bounds, by) {
|
|
9328
|
+
return {
|
|
9329
|
+
left: bounds.left - by,
|
|
9330
|
+
top: bounds.top - by,
|
|
9331
|
+
right: bounds.right + by,
|
|
9332
|
+
bottom: bounds.bottom + by
|
|
9333
|
+
};
|
|
9334
|
+
}
|
|
9335
|
+
function polygonArea(points) {
|
|
9336
|
+
return Math.abs(segments(points, true).reduce((area, { start, end }) => area + start[0] * end[1] - end[0] * start[1], 0)) / 2;
|
|
9337
|
+
}
|
|
9338
|
+
|
|
9339
|
+
//#endregion
|
|
9340
|
+
//#region ../core/src/map-drawing/projection.ts
|
|
9341
|
+
const EARTH_CIRCUMFERENCE = 2 * Math.PI * 6378137;
|
|
9342
|
+
const MERCATOR_LIMIT = 85.0511287798066;
|
|
9343
|
+
function createProjection(pin, radius) {
|
|
9344
|
+
const latitude = pin.lat * Math.PI / 180;
|
|
9345
|
+
const center = [(pin.lng + 180) / 360, (1 - Math.asinh(Math.tan(latitude)) / Math.PI) / 2];
|
|
9346
|
+
const span = 2 * radius / (EARTH_CIRCUMFERENCE * Math.cos(latitude));
|
|
9347
|
+
return {
|
|
9348
|
+
center,
|
|
9349
|
+
span,
|
|
9350
|
+
project: (tile$1, point$1) => {
|
|
9351
|
+
const x = (tile$1.x + point$1[0] / tile$1.extent) / 2 ** tile$1.z;
|
|
9352
|
+
const y = (tile$1.y + point$1[1] / tile$1.extent) / 2 ** tile$1.z;
|
|
9353
|
+
const wrapped = (x - center[0] + 1.5) % 1 - .5;
|
|
9354
|
+
return [400 + wrapped / span * 800, 400 + (y - center[1]) / span * 800];
|
|
9355
|
+
}
|
|
9356
|
+
};
|
|
9357
|
+
}
|
|
9358
|
+
function tileBounds(tile$1, projection) {
|
|
9359
|
+
const a = projection.project(tile$1, [0, 0]), b = projection.project(tile$1, [tile$1.extent, tile$1.extent]);
|
|
9360
|
+
return {
|
|
9361
|
+
left: a[0],
|
|
9362
|
+
top: a[1],
|
|
9363
|
+
right: b[0],
|
|
9364
|
+
bottom: b[1]
|
|
9365
|
+
};
|
|
9366
|
+
}
|
|
9367
|
+
function supportsProjection(pin, projection) {
|
|
9368
|
+
return Math.abs(pin.lat) <= MERCATOR_LIMIT && projection.center[1] - projection.span / 2 >= 0 && projection.center[1] + projection.span / 2 <= 1;
|
|
9369
|
+
}
|
|
9370
|
+
|
|
9371
|
+
//#endregion
|
|
9372
|
+
//#region ../core/src/map-drawing/features.ts
|
|
9373
|
+
const compareKey = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
9374
|
+
const greenKinds = [
|
|
9375
|
+
"park",
|
|
9376
|
+
"forest",
|
|
9377
|
+
"wood",
|
|
9378
|
+
"grass",
|
|
9379
|
+
"meadow",
|
|
9380
|
+
"garden",
|
|
9381
|
+
"nature_reserve",
|
|
9382
|
+
"protected_area",
|
|
9383
|
+
"recreation_ground",
|
|
9384
|
+
"village_green"
|
|
9385
|
+
];
|
|
9386
|
+
function polygonRole(feature$1) {
|
|
9387
|
+
if (feature$1.layer === "earth") return "map-land";
|
|
9388
|
+
if (feature$1.layer === "water") return feature$1.kind === "swimming_pool" ? null : "map-water";
|
|
9389
|
+
if (feature$1.layer === "buildings") return "map-building";
|
|
9390
|
+
return feature$1.layer === "landuse" && greenKinds.includes(feature$1.kind) ? "map-park" : null;
|
|
9391
|
+
}
|
|
9392
|
+
function roadRole(kind) {
|
|
9393
|
+
if (kind === "highway" || kind === "major_road") return "major";
|
|
9394
|
+
if (kind === "minor_road") return "minor";
|
|
9395
|
+
return kind === "path" ? "path" : null;
|
|
9396
|
+
}
|
|
9397
|
+
function intersection(a, b) {
|
|
9398
|
+
const bounds = {
|
|
9399
|
+
left: Math.max(a.left, b.left),
|
|
9400
|
+
top: Math.max(a.top, b.top),
|
|
9401
|
+
right: Math.min(a.right, b.right),
|
|
9402
|
+
bottom: Math.min(a.bottom, b.bottom)
|
|
9403
|
+
};
|
|
9404
|
+
return bounds.left >= bounds.right || bounds.top >= bounds.bottom ? null : bounds;
|
|
9405
|
+
}
|
|
9406
|
+
function canonicalRing(points) {
|
|
9407
|
+
const head = points[0];
|
|
9408
|
+
if (head === undefined) return [];
|
|
9409
|
+
const first = points.reduce((best, point$1, index) => point$1[0] < best.point[0] || point$1[0] === best.point[0] && point$1[1] < best.point[1] ? {
|
|
9410
|
+
point: point$1,
|
|
9411
|
+
index
|
|
9412
|
+
} : best, {
|
|
9413
|
+
point: head,
|
|
9414
|
+
index: 0
|
|
9415
|
+
});
|
|
9416
|
+
const forward = [...points.slice(first.index), ...points.slice(0, first.index)];
|
|
9417
|
+
const backward = [first.point, ...forward.slice(1).reverse()];
|
|
9418
|
+
return compareKey(JSON.stringify(forward), JSON.stringify(backward)) <= 0 ? forward : backward;
|
|
9419
|
+
}
|
|
9420
|
+
function canonicalLine(points) {
|
|
9421
|
+
const reversed = points.slice().reverse();
|
|
9422
|
+
return compareKey(JSON.stringify(points), JSON.stringify(reversed)) <= 0 ? points : reversed;
|
|
9423
|
+
}
|
|
9424
|
+
const CENTRE = [400, 400];
|
|
9425
|
+
function pinDistance(geometry) {
|
|
9426
|
+
if (geometry.kind === "road") return segments(geometry.points).reduce((nearest, { start, end }) => Math.min(nearest, deviation(CENTRE, start, end)), Infinity);
|
|
9427
|
+
return geometry.rings.reduce((best, ring) => ring.reduce((nearest, point$1) => Math.min(nearest, distance(point$1, CENTRE)), best), Infinity);
|
|
9428
|
+
}
|
|
9429
|
+
function candidate(geometry, rank) {
|
|
9430
|
+
const key = JSON.stringify(geometry.kind === "polygon" ? geometry : {
|
|
9431
|
+
kind: "road",
|
|
9432
|
+
role: geometry.role,
|
|
9433
|
+
points: geometry.points
|
|
9434
|
+
});
|
|
9435
|
+
return {
|
|
9436
|
+
geometry,
|
|
9437
|
+
rank,
|
|
9438
|
+
key,
|
|
9439
|
+
distance: pinDistance(geometry)
|
|
9440
|
+
};
|
|
9441
|
+
}
|
|
9442
|
+
/**
|
|
9443
|
+
* Polygons are clipped a fraction past their tile edge so neighbouring tiles' fills overlap; clipped exactly,
|
|
9444
|
+
* anti-aliasing leaves a hairline of background along every tile seam. Fills must be opaque for this to hold.
|
|
9445
|
+
*/
|
|
9446
|
+
const SEAM_OVERLAP = .5;
|
|
9447
|
+
function projectFeature(tile$1, feature$1, projection) {
|
|
9448
|
+
const bounds = intersection(expandBounds(tileBounds(tile$1, projection), feature$1.geometry === "polygon" ? SEAM_OVERLAP : 0), VIEW_BOUNDS);
|
|
9449
|
+
if (bounds === null) return [];
|
|
9450
|
+
const paths$1 = feature$1.paths.map((path) => path.map((point$1) => projection.project(tile$1, point$1)));
|
|
9451
|
+
if (feature$1.geometry === "line") {
|
|
9452
|
+
const role = roadRole(feature$1.kind);
|
|
9453
|
+
return role ? paths$1.flatMap((path) => clipLine(path, bounds)).map((path) => canonicalLine(simplify(canonicalLine(path), false))).filter((path) => path.length >= 2 && segments(path).reduce((sum, { start, end }) => sum + distance(start, end), 0) >= 1).map((points) => candidate({
|
|
9454
|
+
kind: "road",
|
|
9455
|
+
role,
|
|
9456
|
+
points,
|
|
9457
|
+
name: feature$1.name
|
|
9458
|
+
}, feature$1.sortRank)) : [];
|
|
9459
|
+
}
|
|
9460
|
+
const className = polygonRole(feature$1);
|
|
9461
|
+
if (!className) return [];
|
|
9462
|
+
const rings = paths$1.map((path) => simplify(canonicalRing(clipPolygon(path, bounds)), true)).filter((ring) => ring.length >= 3 && polygonArea(ring) >= .3).map(canonicalRing).slice().sort((a, b) => compareKey(JSON.stringify(a), JSON.stringify(b)));
|
|
9463
|
+
return rings.length ? [candidate({
|
|
9464
|
+
kind: "polygon",
|
|
9465
|
+
fillRule: "evenodd",
|
|
9466
|
+
className,
|
|
9467
|
+
rings
|
|
9468
|
+
}, feature$1.sortRank)] : [];
|
|
9469
|
+
}
|
|
9470
|
+
function deduplicate(candidates) {
|
|
9471
|
+
return candidates.slice().sort((a, b) => compareKey(a.key, b.key) || a.rank - b.rank || compareKey(JSON.stringify(a.geometry), JSON.stringify(b.geometry))).filter((item, index, sorted) => item.key !== sorted[index - 1]?.key);
|
|
9472
|
+
}
|
|
9473
|
+
|
|
9474
|
+
//#endregion
|
|
9475
|
+
//#region ../core/src/map-drawing/labels.ts
|
|
9476
|
+
/**
|
|
9477
|
+
* Collision budget for a ~12.5-unit label in an ordinary sans-serif, with room for a heavier site font.
|
|
9478
|
+
* Renderers can set label type smaller than this, never larger.
|
|
9479
|
+
*/
|
|
9480
|
+
const CHARACTER_WIDTH = 7;
|
|
9481
|
+
const HALF_HEIGHT = 8;
|
|
9482
|
+
const PADDING = 5;
|
|
9483
|
+
/** How far a street may bend away from the straight line its label is written along. */
|
|
9484
|
+
const MAX_BEND = 4.5;
|
|
9485
|
+
/** Preferred places along a street for its label, as fractions of its visible length. */
|
|
9486
|
+
const POSITIONS = [
|
|
9487
|
+
.5,
|
|
9488
|
+
.4,
|
|
9489
|
+
.6,
|
|
9490
|
+
.3,
|
|
9491
|
+
.7,
|
|
9492
|
+
.2,
|
|
9493
|
+
.8,
|
|
9494
|
+
.1,
|
|
9495
|
+
.9
|
|
9496
|
+
];
|
|
9497
|
+
/** Street pieces whose ends are this close are one street (OSM ways and tile clipping both split streets). */
|
|
9498
|
+
const JOIN_DISTANCE = 1;
|
|
9499
|
+
/**
|
|
9500
|
+
* Every label lies wholly inside the central 400 × 300 units. Renderers always show at least that much (a phone
|
|
9501
|
+
* at 0.85 px per unit or more, a 1200 × 500 band at 1.5), so a crop never cuts a street name in half.
|
|
9502
|
+
*/
|
|
9503
|
+
const LABEL_WINDOW = {
|
|
9504
|
+
left: 200,
|
|
9505
|
+
right: 600,
|
|
9506
|
+
top: 250,
|
|
9507
|
+
bottom: 550
|
|
9508
|
+
};
|
|
9509
|
+
/** The pin is a teardrop 26 units wide and 38 tall with its tip on the point; labels keep clear of it. */
|
|
9510
|
+
const PIN_KEEP_OUT = {
|
|
9511
|
+
left: 382,
|
|
9512
|
+
right: 418,
|
|
9513
|
+
top: 358,
|
|
9514
|
+
bottom: 406
|
|
9515
|
+
};
|
|
9516
|
+
const LABELLED_ROLES = ["major", "minor"];
|
|
9517
|
+
const textLength = (text) => Array.from(text).length * CHARACTER_WIDTH;
|
|
9518
|
+
/** The label's rotated rectangle, which collision checks treat as solid. */
|
|
9519
|
+
function corners(label$1) {
|
|
9520
|
+
const radians = label$1.angle * Math.PI / 180, cos = Math.cos(radians), sin = Math.sin(radians);
|
|
9521
|
+
const half = textLength(label$1.text) / 2 + PADDING;
|
|
9522
|
+
const at = (x, y) => [label$1.point[0] + x * cos - y * sin, label$1.point[1] + x * sin + y * cos];
|
|
9523
|
+
return [
|
|
9524
|
+
at(-half, -HALF_HEIGHT),
|
|
9525
|
+
at(half, -HALF_HEIGHT),
|
|
9526
|
+
at(half, HALF_HEIGHT),
|
|
9527
|
+
at(-half, HALF_HEIGHT)
|
|
9528
|
+
];
|
|
9529
|
+
}
|
|
9530
|
+
const boxCorners = (box) => [
|
|
9531
|
+
[box.left, box.top],
|
|
9532
|
+
[box.right, box.top],
|
|
9533
|
+
[box.right, box.bottom],
|
|
9534
|
+
[box.left, box.bottom]
|
|
9535
|
+
];
|
|
9536
|
+
/** The label's axis-aligned extent once rotated. */
|
|
9537
|
+
function labelBounds(label$1) {
|
|
9538
|
+
const points = corners(label$1), xs = points.map((point$1) => point$1[0]), ys = points.map((point$1) => point$1[1]);
|
|
9539
|
+
return {
|
|
9540
|
+
left: Math.min(...xs),
|
|
9541
|
+
right: Math.max(...xs),
|
|
9542
|
+
top: Math.min(...ys),
|
|
9543
|
+
bottom: Math.max(...ys)
|
|
9544
|
+
};
|
|
9545
|
+
}
|
|
9546
|
+
function overlaps(a, b) {
|
|
9547
|
+
return a.left <= b.right && a.right >= b.left && a.top <= b.bottom && a.bottom >= b.top;
|
|
9548
|
+
}
|
|
9549
|
+
/** Separating-axis test for two rectangles; diagonal labels would block far too much as bounding boxes. */
|
|
9550
|
+
function rectanglesMeet(a, b) {
|
|
9551
|
+
const axes = [a, b].flatMap((box) => [[box[1][0] - box[0][0], box[1][1] - box[0][1]], [box[3][0] - box[0][0], box[3][1] - box[0][1]]]);
|
|
9552
|
+
return axes.every(([x, y]) => {
|
|
9553
|
+
const project = (box) => box.map((point$1) => point$1[0] * x + point$1[1] * y);
|
|
9554
|
+
const first = project(a), second = project(b);
|
|
9555
|
+
return Math.max(...first) >= Math.min(...second) && Math.max(...second) >= Math.min(...first);
|
|
9556
|
+
});
|
|
9557
|
+
}
|
|
9558
|
+
function labelsCollide(a, b) {
|
|
9559
|
+
return rectanglesMeet(corners(a), corners(b));
|
|
9560
|
+
}
|
|
9561
|
+
function labelCoversPin(label$1) {
|
|
9562
|
+
return rectanglesMeet(corners(label$1), boxCorners(PIN_KEEP_OUT));
|
|
9563
|
+
}
|
|
9564
|
+
const inside = (inner, outer) => inner.left >= outer.left && inner.right <= outer.right && inner.top >= outer.top && inner.bottom <= outer.bottom;
|
|
9565
|
+
function pointAlong(points, at) {
|
|
9566
|
+
return segments(points).reduce((state, { start, end }) => {
|
|
9567
|
+
if (state.point !== null) return state;
|
|
9568
|
+
const size = distance(start, end);
|
|
9569
|
+
if (size === 0 || state.remaining > size) return {
|
|
9570
|
+
remaining: state.remaining - size,
|
|
9571
|
+
point: null
|
|
9572
|
+
};
|
|
9573
|
+
const fraction = state.remaining / size;
|
|
9574
|
+
return {
|
|
9575
|
+
remaining: 0,
|
|
9576
|
+
point: [start[0] + (end[0] - start[0]) * fraction, start[1] + (end[1] - start[1]) * fraction]
|
|
9577
|
+
};
|
|
9578
|
+
}, {
|
|
9579
|
+
remaining: at,
|
|
9580
|
+
point: null
|
|
9581
|
+
}).point;
|
|
9582
|
+
}
|
|
9583
|
+
/** The run of street a label of `size` would cover around `centre`, with the vertices inside it. */
|
|
9584
|
+
function stretch(points, centre, size) {
|
|
9585
|
+
const start = pointAlong(points, centre - size / 2), end = pointAlong(points, centre + size / 2), middle = pointAlong(points, centre);
|
|
9586
|
+
if (start === null || end === null || middle === null) return null;
|
|
9587
|
+
const offsets = points.reduce((sums, point$1, index) => [...sums, index === 0 ? 0 : (sums[index - 1] ?? 0) + distance(points[index - 1] ?? point$1, point$1)], []);
|
|
9588
|
+
return {
|
|
9589
|
+
start,
|
|
9590
|
+
end,
|
|
9591
|
+
middle,
|
|
9592
|
+
vertices: points.filter((_, index) => Math.abs((offsets[index] ?? 0) - centre) < size / 2)
|
|
9593
|
+
};
|
|
9594
|
+
}
|
|
9595
|
+
function uprightAngle(start, end) {
|
|
9596
|
+
const degrees = Math.atan2(end[1] - start[1], end[0] - start[0]) * 180 / Math.PI;
|
|
9597
|
+
const upright = degrees > 90 ? degrees - 180 : degrees <= -90 ? degrees + 180 : degrees;
|
|
9598
|
+
return Math.round(upright * 10) / 10 || 0;
|
|
9599
|
+
}
|
|
9600
|
+
/** Joins `line` onto `chain` when one end of each meets, keeping the chain's direction where it can. */
|
|
9601
|
+
function join(chain, line$1) {
|
|
9602
|
+
const meets = (a, b) => a !== undefined && b !== undefined && distance(a, b) <= JOIN_DISTANCE;
|
|
9603
|
+
const reversed = line$1.slice().reverse();
|
|
9604
|
+
if (meets(chain[chain.length - 1], line$1[0])) return [...chain, ...line$1.slice(1)];
|
|
9605
|
+
if (meets(chain[chain.length - 1], reversed[0])) return [...chain, ...reversed.slice(1)];
|
|
9606
|
+
if (meets(line$1[line$1.length - 1], chain[0])) return [...line$1, ...chain.slice(1)];
|
|
9607
|
+
return meets(reversed[reversed.length - 1], chain[0]) ? [...reversed, ...chain.slice(1)] : null;
|
|
9608
|
+
}
|
|
9609
|
+
function stitchPass(streets) {
|
|
9610
|
+
return streets.reduce((chains, street) => {
|
|
9611
|
+
const index = chains.findIndex((chain$1) => chain$1.name === street.name && chain$1.major === street.major && join(chain$1.points, street.points) !== null);
|
|
9612
|
+
const chain = chains[index];
|
|
9613
|
+
const joined = chain === undefined ? null : join(chain.points, street.points);
|
|
9614
|
+
return chain === undefined || joined === null ? [...chains, street] : chains.map((item, at) => at === index ? {
|
|
9615
|
+
...item,
|
|
9616
|
+
distance: Math.min(item.distance, street.distance),
|
|
9617
|
+
points: joined
|
|
9618
|
+
} : item);
|
|
9619
|
+
}, []);
|
|
9620
|
+
}
|
|
9621
|
+
/** Whole named streets from their pieces, so a name is written along the street rather than one short way of it. */
|
|
9622
|
+
function stitch(streets) {
|
|
9623
|
+
const next = stitchPass(streets);
|
|
9624
|
+
return next.length === streets.length ? next : stitch(next);
|
|
9625
|
+
}
|
|
9626
|
+
function namedStreets(roads) {
|
|
9627
|
+
return roads.slice().sort((a, b) => compareKey(a.key, b.key)).flatMap(({ geometry, distance: distance$1 }) => geometry.kind === "road" && geometry.name !== null && LABELLED_ROLES.includes(geometry.role) ? [{
|
|
9628
|
+
name: geometry.name,
|
|
9629
|
+
major: geometry.role === "major",
|
|
9630
|
+
distance: distance$1,
|
|
9631
|
+
points: geometry.points
|
|
9632
|
+
}] : []);
|
|
9633
|
+
}
|
|
9634
|
+
/** Label options for one street, best first: only where the street runs straight enough to write along. */
|
|
9635
|
+
function streetLabels({ name: text, points }) {
|
|
9636
|
+
const size = textLength(text);
|
|
9637
|
+
const length = segments(points).reduce((sum, { start, end }) => sum + distance(start, end), 0);
|
|
9638
|
+
if (length < size) return [];
|
|
9639
|
+
return POSITIONS.flatMap((position) => {
|
|
9640
|
+
const centre = Math.min(Math.max(length * position, size / 2), length - size / 2);
|
|
9641
|
+
const run = stretch(points, centre, size);
|
|
9642
|
+
if (run === null || run.vertices.some((vertex) => deviation(vertex, run.start, run.end) > MAX_BEND)) return [];
|
|
9643
|
+
return [{
|
|
9644
|
+
className: "map-label",
|
|
9645
|
+
haloClassName: "map-label-halo",
|
|
9646
|
+
text,
|
|
9647
|
+
point: roundPoint(run.middle),
|
|
9648
|
+
angle: uprightAngle(run.start, run.end)
|
|
9649
|
+
}];
|
|
9650
|
+
});
|
|
9651
|
+
}
|
|
9652
|
+
function fits(label$1, placed) {
|
|
9653
|
+
return inside(labelBounds(label$1), LABEL_WINDOW) && !labelCoversPin(label$1) && placed.every((other) => other.text !== label$1.text && !labelsCollide(label$1, other));
|
|
9654
|
+
}
|
|
9655
|
+
/** Nearest streets first, as the framing weighs them: the ones around the door are the names a visitor needs. */
|
|
9656
|
+
function placeLabels(roads, framing) {
|
|
9657
|
+
const { maxLabels, minorLabelDistance } = framing;
|
|
9658
|
+
const weighted = (street) => street.major ? street.distance : street.distance * minorLabelDistance;
|
|
9659
|
+
return stitch(namedStreets(roads)).slice().sort((a, b) => weighted(a) - weighted(b) || compareKey(a.name, b.name)).reduce((placed, street) => {
|
|
9660
|
+
if (placed.length >= maxLabels) return placed;
|
|
9661
|
+
const label$1 = streetLabels(street).find((option) => fits(option, placed));
|
|
9662
|
+
return label$1 === undefined ? placed : [...placed, label$1];
|
|
9663
|
+
}, []);
|
|
9664
|
+
}
|
|
9665
|
+
|
|
9666
|
+
//#endregion
|
|
9667
|
+
//#region ../core/src/map-drawing/coverage.ts
|
|
9668
|
+
/** The bounded mixed-zoom coverage decision shared by extraction and completeness validation. */
|
|
9669
|
+
function drawingTileCoverage(pin) {
|
|
9670
|
+
const contexts = FRAMINGS.map((framing) => ({
|
|
9671
|
+
framing,
|
|
9672
|
+
projection: createProjection(pin, framing.radiusMeters)
|
|
9673
|
+
}));
|
|
9674
|
+
if (contexts.some(({ projection }) => !supportsProjection(pin, projection))) return err("unsupported-latitude");
|
|
9675
|
+
const bounds = contexts.map(({ framing, projection }) => {
|
|
9676
|
+
const n = 2 ** framing.zoom;
|
|
9677
|
+
const left = Math.floor((projection.center[0] - projection.span / 2) * n), right = Math.floor((projection.center[0] + projection.span / 2) * n);
|
|
9678
|
+
const top = Math.floor((projection.center[1] - projection.span / 2) * n), bottom = Math.floor((projection.center[1] + projection.span / 2) * n);
|
|
9679
|
+
return {
|
|
9680
|
+
z: framing.zoom,
|
|
9681
|
+
n,
|
|
9682
|
+
left,
|
|
9683
|
+
top,
|
|
9684
|
+
bottom,
|
|
9685
|
+
width: right - left + 1,
|
|
9686
|
+
count: (right - left + 1) * (bottom - top + 1)
|
|
9687
|
+
};
|
|
9688
|
+
});
|
|
9689
|
+
if (bounds.some((value) => !Number.isFinite(value.count) || value.count < 1 || value.top < 0 || value.bottom >= value.n) || bounds.reduce((sum, value) => sum + value.count, 0) > 96) return err("geometry-limit");
|
|
9690
|
+
return ok(bounds.flatMap(({ z: z$1, n, left, top, width, count }) => Array.from({ length: count }, (_, index) => ({
|
|
9691
|
+
z: z$1,
|
|
9692
|
+
x: ((left + index % width) % n + n) % n,
|
|
9693
|
+
y: top + Math.floor(index / width)
|
|
9694
|
+
}))));
|
|
9695
|
+
}
|
|
9696
|
+
|
|
9697
|
+
//#endregion
|
|
9698
|
+
//#region ../core/src/map-drawing/generate.ts
|
|
9699
|
+
const ROLE_ORDER = [
|
|
9700
|
+
"major",
|
|
9701
|
+
"minor",
|
|
9702
|
+
"path"
|
|
9703
|
+
];
|
|
9704
|
+
const MAX_BUILDINGS = 1e3;
|
|
9705
|
+
const MAX_ROADS = 300;
|
|
9706
|
+
/** What this framing draws: its road roles, buildings or not, and no green patches too small to read. */
|
|
9707
|
+
function belongsIn(framing, item) {
|
|
9708
|
+
const geometry = item.geometry;
|
|
9709
|
+
if (geometry.kind === "road") return framing.roads.some((role) => role === geometry.role) && (geometry.role !== "path" || item.distance <= framing.maxPathDistance);
|
|
9710
|
+
if (geometry.className === "map-building") return framing.buildings;
|
|
9711
|
+
return geometry.className !== "map-park" || geometry.rings.some((ring) => polygonArea(ring) >= framing.minParkArea);
|
|
9712
|
+
}
|
|
9713
|
+
function selectFeatures(framing, candidates) {
|
|
9714
|
+
const ordered = candidates.filter((item) => belongsIn(framing, item)).sort((a, b) => a.distance - b.distance || a.rank - b.rank || compareKey(a.key, b.key));
|
|
9715
|
+
const buildings = ordered.filter((item) => item.geometry.kind === "polygon" && item.geometry.className === "map-building").slice(0, MAX_BUILDINGS);
|
|
9716
|
+
const roleOrder = (item) => item.geometry.kind === "road" ? ROLE_ORDER.indexOf(item.geometry.role) : 0;
|
|
9717
|
+
const roads = ordered.filter((item) => item.geometry.kind === "road").sort((a, b) => roleOrder(a) - roleOrder(b) || a.distance - b.distance || compareKey(a.key, b.key)).slice(0, MAX_ROADS);
|
|
9718
|
+
const ground = ordered.filter((item) => item.geometry.kind === "polygon" && item.geometry.className !== "map-building");
|
|
9719
|
+
return [
|
|
9720
|
+
...ground,
|
|
9721
|
+
...buildings,
|
|
9722
|
+
...roads
|
|
9723
|
+
];
|
|
9724
|
+
}
|
|
9725
|
+
/** Water over green, so a wood or park running to the bank never hides the river. */
|
|
9726
|
+
const POLYGON_ORDER = [
|
|
9727
|
+
"map-land",
|
|
9728
|
+
"map-park",
|
|
9729
|
+
"map-water",
|
|
9730
|
+
"map-building"
|
|
9731
|
+
];
|
|
9732
|
+
function paintingOrder(candidate$1) {
|
|
9733
|
+
const geometry = candidate$1.geometry;
|
|
9734
|
+
return geometry.kind === "road" ? POLYGON_ORDER.length + ROLE_ORDER.length - ROLE_ORDER.indexOf(geometry.role) : POLYGON_ORDER.indexOf(geometry.className);
|
|
9735
|
+
}
|
|
9736
|
+
const casingClass = {
|
|
9737
|
+
major: "map-road-major-casing",
|
|
9738
|
+
minor: "map-road-minor-casing"
|
|
9739
|
+
};
|
|
9740
|
+
const strokeClass = {
|
|
9741
|
+
major: "map-road-major",
|
|
9742
|
+
minor: "map-road-minor",
|
|
9743
|
+
path: "map-road-path"
|
|
9744
|
+
};
|
|
9745
|
+
/** Casings under every stroke; paths are a single thin line with no casing. */
|
|
9746
|
+
function roadPaths(roads) {
|
|
9747
|
+
const lines = roads.flatMap((item) => item.geometry.kind === "road" ? [item.geometry] : []);
|
|
9748
|
+
const casings = lines.flatMap(({ role, points }) => role === "path" ? [] : [{
|
|
9749
|
+
kind: "line",
|
|
9750
|
+
className: casingClass[role],
|
|
9751
|
+
points
|
|
9752
|
+
}]);
|
|
9753
|
+
return [...casings, ...lines.map(({ role, points }) => ({
|
|
9754
|
+
kind: "line",
|
|
9755
|
+
className: strokeClass[role],
|
|
9756
|
+
points
|
|
9757
|
+
}))];
|
|
9758
|
+
}
|
|
9759
|
+
function generateFrame(input, framing, projection) {
|
|
9760
|
+
const candidates = input.tiles.filter((tile$1) => tile$1.z === framing.zoom).flatMap((tile$1) => tile$1.features.flatMap((feature$1) => projectFeature(tile$1, feature$1, projection)));
|
|
9761
|
+
const selected = selectFeatures(framing, deduplicate(candidates)).slice().sort((a, b) => paintingOrder(a) - paintingOrder(b) || a.rank - b.rank || compareKey(a.key, b.key));
|
|
9762
|
+
const polygons = selected.flatMap((item) => item.geometry.kind === "polygon" ? [item.geometry] : []);
|
|
9763
|
+
const roads = selected.filter((item) => item.geometry.kind === "road");
|
|
9764
|
+
return {
|
|
9765
|
+
radiusMeters: framing.radiusMeters,
|
|
9766
|
+
viewBox: [
|
|
9767
|
+
0,
|
|
9768
|
+
0,
|
|
9769
|
+
800,
|
|
9770
|
+
800
|
|
9771
|
+
],
|
|
9772
|
+
backgroundClass: "map-water",
|
|
9773
|
+
paths: [...polygons, ...roadPaths(roads)],
|
|
9774
|
+
labels: placeLabels(roads, framing),
|
|
9775
|
+
pin: {
|
|
9776
|
+
className: "map-pin",
|
|
9777
|
+
point: [400, 400]
|
|
9778
|
+
}
|
|
9779
|
+
};
|
|
9780
|
+
}
|
|
9781
|
+
/** Pure: parse decoded input once, decide both framings, return only closed numeric geometry. */
|
|
9782
|
+
function generateMapDrawing(raw) {
|
|
9783
|
+
const parsed = mapDrawingInputSchema.safeParse(raw);
|
|
9784
|
+
if (!parsed.success) return err("invalid-input");
|
|
9785
|
+
const input = parsed.data;
|
|
9786
|
+
const coverage = drawingTileCoverage(input.pin);
|
|
9787
|
+
if (!coverage.ok) return err(coverage.error);
|
|
9788
|
+
const neighbourhood = {
|
|
9789
|
+
framing: FRAMINGS[0],
|
|
9790
|
+
projection: createProjection(input.pin, FRAMINGS[0].radiusMeters)
|
|
9791
|
+
};
|
|
9792
|
+
const wider = {
|
|
9793
|
+
framing: FRAMINGS[1],
|
|
9794
|
+
projection: createProjection(input.pin, FRAMINGS[1].radiusMeters)
|
|
9795
|
+
};
|
|
9796
|
+
const keys = input.tiles.map((tile$1) => `${tile$1.z}/${tile$1.x}/${tile$1.y}`);
|
|
9797
|
+
const present = new Set(keys);
|
|
9798
|
+
if (present.size !== keys.length) return err("invalid-input");
|
|
9799
|
+
if (coverage.value.some((tile$1) => !present.has(`${tile$1.z}/${tile$1.x}/${tile$1.y}`))) return err("missing-tiles");
|
|
9800
|
+
const drawing = mapDrawingSchema.safeParse({
|
|
9801
|
+
schemaVersion: DRAWING_SCHEMA_VERSION,
|
|
9802
|
+
generatorVersion: DRAWING_GENERATOR_VERSION,
|
|
9803
|
+
buildId: input.buildId,
|
|
9804
|
+
tileSchemaVersion: input.tileSchemaVersion,
|
|
9805
|
+
frames: {
|
|
9806
|
+
neighbourhood: generateFrame(input, neighbourhood.framing, neighbourhood.projection),
|
|
9807
|
+
wider: generateFrame(input, wider.framing, wider.projection)
|
|
9808
|
+
}
|
|
9809
|
+
});
|
|
9810
|
+
if (!drawing.success) return err("geometry-limit");
|
|
9811
|
+
return ok(drawing.data);
|
|
9812
|
+
}
|
|
9813
|
+
|
|
8829
9814
|
//#endregion
|
|
8830
9815
|
//#region ../core/src/authored-values.ts
|
|
8831
9816
|
/** Canonical authored equality: empty storage values and absent optional members agree. */
|
|
@@ -8855,4 +9840,468 @@ function isPlainRecord(value) {
|
|
|
8855
9840
|
}
|
|
8856
9841
|
|
|
8857
9842
|
//#endregion
|
|
8858
|
-
|
|
9843
|
+
//#region ../core/src/journal-receipt.ts
|
|
9844
|
+
/** Owners pass captured preimages and committed results; callers never fetch a replacement inverse. */
|
|
9845
|
+
function journalCommitReceipt(target, inverse, revisions) {
|
|
9846
|
+
return {
|
|
9847
|
+
...target,
|
|
9848
|
+
...inverse,
|
|
9849
|
+
...revisions
|
|
9850
|
+
};
|
|
9851
|
+
}
|
|
9852
|
+
function journalReplayValue(inverse, direction) {
|
|
9853
|
+
return direction === "undo" ? inverse.before : inverse.after;
|
|
9854
|
+
}
|
|
9855
|
+
/** Composition keeps the first inverse only when every committed link belongs to the same target. */
|
|
9856
|
+
function composeJournalCommitReceipts(receipts, targetKey) {
|
|
9857
|
+
const first = receipts[0], last = receipts[receipts.length - 1];
|
|
9858
|
+
if (first === undefined || last === undefined) return null;
|
|
9859
|
+
const continuous = receipts.every((receipt, index) => {
|
|
9860
|
+
const previous = receipts[index - 1];
|
|
9861
|
+
return previous === undefined || targetKey(previous) === targetKey(receipt) && previous.revision === receipt.expectedRevision && authoredValuesEqual(previous.after, receipt.before);
|
|
9862
|
+
});
|
|
9863
|
+
return continuous ? journalCommitReceipt(last, {
|
|
9864
|
+
before: first.before,
|
|
9865
|
+
after: last.after
|
|
9866
|
+
}, {
|
|
9867
|
+
expectedRevision: first.expectedRevision,
|
|
9868
|
+
revision: last.revision
|
|
9869
|
+
}) : null;
|
|
9870
|
+
}
|
|
9871
|
+
|
|
9872
|
+
//#endregion
|
|
9873
|
+
//#region ../core/src/committed-write-proof.ts
|
|
9874
|
+
/** Positive atomic storage evidence survives a failed response projection. */
|
|
9875
|
+
const committedWriteProofSchema = z.object({
|
|
9876
|
+
operationId: z.string().uuid(),
|
|
9877
|
+
committedAt: z.string(),
|
|
9878
|
+
resultingRevision: z.number().int().nonnegative()
|
|
9879
|
+
});
|
|
9880
|
+
|
|
9881
|
+
//#endregion
|
|
9882
|
+
//#region ../core/src/site-deployment-mode.ts
|
|
9883
|
+
/** Only Riverbank-owned themes supply persisted styling facts. */
|
|
9884
|
+
function usesPersistedRiverbankTheme(mode) {
|
|
9885
|
+
return mode !== "headless_full";
|
|
9886
|
+
}
|
|
9887
|
+
|
|
9888
|
+
//#endregion
|
|
9889
|
+
//#region ../core/src/email-vocabulary.ts
|
|
9890
|
+
/** Shared email identities. No renderer, dashboard or private email-package dependency. */
|
|
9891
|
+
const EMAIL_DEFINITIONS = {
|
|
9892
|
+
event_confirmation: {
|
|
9893
|
+
wording: "editable",
|
|
9894
|
+
perEvent: true,
|
|
9895
|
+
templateStorage: true
|
|
9896
|
+
},
|
|
9897
|
+
event_reminder: {
|
|
9898
|
+
wording: "editable",
|
|
9899
|
+
perEvent: true,
|
|
9900
|
+
templateStorage: true
|
|
9901
|
+
},
|
|
9902
|
+
event_feedback_request: {
|
|
9903
|
+
wording: "editable",
|
|
9904
|
+
perEvent: false,
|
|
9905
|
+
templateStorage: true
|
|
9906
|
+
},
|
|
9907
|
+
event_cancellation_customer: {
|
|
9908
|
+
wording: "editable",
|
|
9909
|
+
perEvent: false,
|
|
9910
|
+
templateStorage: true
|
|
9911
|
+
},
|
|
9912
|
+
event_cancellation_admin: {
|
|
9913
|
+
wording: "editable",
|
|
9914
|
+
perEvent: false,
|
|
9915
|
+
templateStorage: true
|
|
9916
|
+
},
|
|
9917
|
+
event_refund_customer: {
|
|
9918
|
+
wording: "editable",
|
|
9919
|
+
perEvent: false,
|
|
9920
|
+
templateStorage: true
|
|
9921
|
+
},
|
|
9922
|
+
event_refund_admin: {
|
|
9923
|
+
wording: "editable",
|
|
9924
|
+
perEvent: false,
|
|
9925
|
+
templateStorage: true
|
|
9926
|
+
},
|
|
9927
|
+
event_transfer_customer: {
|
|
9928
|
+
wording: "editable",
|
|
9929
|
+
perEvent: false,
|
|
9930
|
+
templateStorage: true
|
|
9931
|
+
},
|
|
9932
|
+
event_transfer_admin: {
|
|
9933
|
+
wording: "editable",
|
|
9934
|
+
perEvent: false,
|
|
9935
|
+
templateStorage: true
|
|
9936
|
+
},
|
|
9937
|
+
event_booking_rescheduled_customer: {
|
|
9938
|
+
wording: "editable",
|
|
9939
|
+
perEvent: false,
|
|
9940
|
+
templateStorage: true
|
|
9941
|
+
},
|
|
9942
|
+
event_booking_moved_admin: {
|
|
9943
|
+
wording: "editable",
|
|
9944
|
+
perEvent: false,
|
|
9945
|
+
templateStorage: true
|
|
9946
|
+
},
|
|
9947
|
+
pass_purchase_confirmation: {
|
|
9948
|
+
wording: "editable",
|
|
9949
|
+
perEvent: false,
|
|
9950
|
+
templateStorage: true
|
|
9951
|
+
},
|
|
9952
|
+
membership_purchase_confirmation: {
|
|
9953
|
+
wording: "editable",
|
|
9954
|
+
perEvent: false,
|
|
9955
|
+
templateStorage: true
|
|
9956
|
+
},
|
|
9957
|
+
membership_cancel_at_period_end: {
|
|
9958
|
+
wording: "editable",
|
|
9959
|
+
perEvent: false,
|
|
9960
|
+
templateStorage: true
|
|
9961
|
+
},
|
|
9962
|
+
membership_payment_refund: {
|
|
9963
|
+
wording: "editable",
|
|
9964
|
+
perEvent: false,
|
|
9965
|
+
templateStorage: true
|
|
9966
|
+
},
|
|
9967
|
+
appointment_confirmation: {
|
|
9968
|
+
wording: "editable",
|
|
9969
|
+
perEvent: false,
|
|
9970
|
+
templateStorage: true
|
|
9971
|
+
},
|
|
9972
|
+
appointment_reminder: {
|
|
9973
|
+
wording: "editable",
|
|
9974
|
+
perEvent: false,
|
|
9975
|
+
templateStorage: true
|
|
9976
|
+
},
|
|
9977
|
+
appointment_follow_up: {
|
|
9978
|
+
wording: "editable",
|
|
9979
|
+
perEvent: false,
|
|
9980
|
+
templateStorage: true
|
|
9981
|
+
},
|
|
9982
|
+
site_credit_adjustment: {
|
|
9983
|
+
wording: "editable",
|
|
9984
|
+
perEvent: false,
|
|
9985
|
+
templateStorage: true
|
|
9986
|
+
},
|
|
9987
|
+
site_credit_refund_conversion: {
|
|
9988
|
+
wording: "editable",
|
|
9989
|
+
perEvent: false,
|
|
9990
|
+
templateStorage: true
|
|
9991
|
+
},
|
|
9992
|
+
order_refund: {
|
|
9993
|
+
wording: "editable",
|
|
9994
|
+
perEvent: false,
|
|
9995
|
+
templateStorage: true
|
|
9996
|
+
},
|
|
9997
|
+
pass_refund: {
|
|
9998
|
+
wording: "editable",
|
|
9999
|
+
perEvent: false,
|
|
10000
|
+
templateStorage: true
|
|
10001
|
+
},
|
|
10002
|
+
event_series_registration_confirmation: {
|
|
10003
|
+
wording: "editable",
|
|
10004
|
+
perEvent: false,
|
|
10005
|
+
templateStorage: true
|
|
10006
|
+
},
|
|
10007
|
+
event_series_registration_cancellation_customer: {
|
|
10008
|
+
wording: "editable",
|
|
10009
|
+
perEvent: false,
|
|
10010
|
+
templateStorage: true
|
|
10011
|
+
},
|
|
10012
|
+
event_series_registration_cancellation_admin: {
|
|
10013
|
+
wording: "editable",
|
|
10014
|
+
perEvent: false,
|
|
10015
|
+
templateStorage: true
|
|
10016
|
+
},
|
|
10017
|
+
event_series_registration_refund_customer: {
|
|
10018
|
+
wording: "editable",
|
|
10019
|
+
perEvent: false,
|
|
10020
|
+
templateStorage: true
|
|
10021
|
+
},
|
|
10022
|
+
event_series_registration_refund_admin: {
|
|
10023
|
+
wording: "editable",
|
|
10024
|
+
perEvent: false,
|
|
10025
|
+
templateStorage: true
|
|
10026
|
+
},
|
|
10027
|
+
event_update: {
|
|
10028
|
+
wording: "editable",
|
|
10029
|
+
perEvent: false,
|
|
10030
|
+
templateStorage: true
|
|
10031
|
+
},
|
|
10032
|
+
event_cancelled_by_organizer: {
|
|
10033
|
+
wording: "editable",
|
|
10034
|
+
perEvent: true,
|
|
10035
|
+
templateStorage: true
|
|
10036
|
+
},
|
|
10037
|
+
digital_product_fulfillment: {
|
|
10038
|
+
wording: "editable",
|
|
10039
|
+
perEvent: false,
|
|
10040
|
+
templateStorage: true
|
|
10041
|
+
},
|
|
10042
|
+
order_confirmation: {
|
|
10043
|
+
wording: "editable",
|
|
10044
|
+
perEvent: false,
|
|
10045
|
+
templateStorage: true
|
|
10046
|
+
},
|
|
10047
|
+
order_shipment: {
|
|
10048
|
+
wording: "editable",
|
|
10049
|
+
perEvent: false,
|
|
10050
|
+
templateStorage: true
|
|
10051
|
+
},
|
|
10052
|
+
order_cancellation: {
|
|
10053
|
+
wording: "editable",
|
|
10054
|
+
perEvent: false,
|
|
10055
|
+
templateStorage: true
|
|
10056
|
+
},
|
|
10057
|
+
order_payment_reminder_customer: {
|
|
10058
|
+
wording: "editable",
|
|
10059
|
+
perEvent: false,
|
|
10060
|
+
templateStorage: true
|
|
10061
|
+
},
|
|
10062
|
+
order_balance_due_customer: {
|
|
10063
|
+
wording: "editable",
|
|
10064
|
+
perEvent: false,
|
|
10065
|
+
templateStorage: true
|
|
10066
|
+
},
|
|
10067
|
+
order_balance_overdue_customer: {
|
|
10068
|
+
wording: "editable",
|
|
10069
|
+
perEvent: false,
|
|
10070
|
+
templateStorage: true
|
|
10071
|
+
},
|
|
10072
|
+
order_balance_overdue_admin: {
|
|
10073
|
+
wording: "editable",
|
|
10074
|
+
perEvent: false,
|
|
10075
|
+
templateStorage: true
|
|
10076
|
+
},
|
|
10077
|
+
form_submitter_auto_reply: {
|
|
10078
|
+
wording: "editable",
|
|
10079
|
+
perEvent: false,
|
|
10080
|
+
templateStorage: true
|
|
10081
|
+
},
|
|
10082
|
+
appointment_request_received: {
|
|
10083
|
+
wording: "editable",
|
|
10084
|
+
perEvent: false,
|
|
10085
|
+
templateStorage: true
|
|
10086
|
+
},
|
|
10087
|
+
appointment_request_declined: {
|
|
10088
|
+
wording: "editable",
|
|
10089
|
+
perEvent: false,
|
|
10090
|
+
templateStorage: true
|
|
10091
|
+
},
|
|
10092
|
+
booking_questions_reminder: {
|
|
10093
|
+
wording: "editable",
|
|
10094
|
+
perEvent: false,
|
|
10095
|
+
templateStorage: true
|
|
10096
|
+
},
|
|
10097
|
+
bundle_booking_confirmation: {
|
|
10098
|
+
wording: "shared",
|
|
10099
|
+
sharesWordingWith: "event_confirmation",
|
|
10100
|
+
perEvent: false,
|
|
10101
|
+
templateStorage: true
|
|
10102
|
+
},
|
|
10103
|
+
appointment_rescheduled_customer: {
|
|
10104
|
+
wording: "editable",
|
|
10105
|
+
perEvent: false,
|
|
10106
|
+
templateStorage: true
|
|
10107
|
+
},
|
|
10108
|
+
appointment_rescheduled_team: {
|
|
10109
|
+
wording: "fixed",
|
|
10110
|
+
perEvent: false,
|
|
10111
|
+
templateStorage: false
|
|
10112
|
+
},
|
|
10113
|
+
event_venue_changed: {
|
|
10114
|
+
wording: "editable",
|
|
10115
|
+
perEvent: false,
|
|
10116
|
+
templateStorage: true
|
|
10117
|
+
},
|
|
10118
|
+
course_session_added: {
|
|
10119
|
+
wording: "editable",
|
|
10120
|
+
perEvent: false,
|
|
10121
|
+
templateStorage: true
|
|
10122
|
+
},
|
|
10123
|
+
appointment_cancellation: {
|
|
10124
|
+
wording: "editable",
|
|
10125
|
+
perEvent: false,
|
|
10126
|
+
templateStorage: true
|
|
10127
|
+
},
|
|
10128
|
+
course_session_dropped: {
|
|
10129
|
+
wording: "editable",
|
|
10130
|
+
perEvent: false,
|
|
10131
|
+
templateStorage: true
|
|
10132
|
+
},
|
|
10133
|
+
event_series_waitlist_joined: {
|
|
10134
|
+
wording: "editable",
|
|
10135
|
+
perEvent: false,
|
|
10136
|
+
templateStorage: true
|
|
10137
|
+
},
|
|
10138
|
+
event_waitlist_join_confirmation: {
|
|
10139
|
+
wording: "editable",
|
|
10140
|
+
perEvent: false,
|
|
10141
|
+
templateStorage: true
|
|
10142
|
+
},
|
|
10143
|
+
waitlist_claim: {
|
|
10144
|
+
wording: "editable",
|
|
10145
|
+
perEvent: false,
|
|
10146
|
+
templateStorage: true
|
|
10147
|
+
},
|
|
10148
|
+
gift_card_delivery: {
|
|
10149
|
+
wording: "editable",
|
|
10150
|
+
perEvent: false,
|
|
10151
|
+
templateStorage: true
|
|
10152
|
+
},
|
|
10153
|
+
voucher_delivery: {
|
|
10154
|
+
wording: "editable",
|
|
10155
|
+
perEvent: false,
|
|
10156
|
+
templateStorage: true
|
|
10157
|
+
},
|
|
10158
|
+
event_team_new_booking: {
|
|
10159
|
+
wording: "fixed",
|
|
10160
|
+
perEvent: false,
|
|
10161
|
+
templateStorage: false
|
|
10162
|
+
},
|
|
10163
|
+
event_team_cancellation: {
|
|
10164
|
+
wording: "fixed",
|
|
10165
|
+
perEvent: false,
|
|
10166
|
+
templateStorage: false
|
|
10167
|
+
},
|
|
10168
|
+
event_team_transfer: {
|
|
10169
|
+
wording: "fixed",
|
|
10170
|
+
perEvent: false,
|
|
10171
|
+
templateStorage: false
|
|
10172
|
+
},
|
|
10173
|
+
course_team_new_booking: {
|
|
10174
|
+
wording: "fixed",
|
|
10175
|
+
perEvent: false,
|
|
10176
|
+
templateStorage: false
|
|
10177
|
+
},
|
|
10178
|
+
course_team_cancellation: {
|
|
10179
|
+
wording: "fixed",
|
|
10180
|
+
perEvent: false,
|
|
10181
|
+
templateStorage: false
|
|
10182
|
+
},
|
|
10183
|
+
appointment_team_new_booking: {
|
|
10184
|
+
wording: "fixed",
|
|
10185
|
+
perEvent: false,
|
|
10186
|
+
templateStorage: false
|
|
10187
|
+
},
|
|
10188
|
+
appointment_team_new_request: {
|
|
10189
|
+
wording: "fixed",
|
|
10190
|
+
perEvent: false,
|
|
10191
|
+
templateStorage: false
|
|
10192
|
+
},
|
|
10193
|
+
appointment_team_cancellation: {
|
|
10194
|
+
wording: "fixed",
|
|
10195
|
+
perEvent: false,
|
|
10196
|
+
templateStorage: false
|
|
10197
|
+
},
|
|
10198
|
+
appointment_team_staff_change: {
|
|
10199
|
+
wording: "fixed",
|
|
10200
|
+
perEvent: false,
|
|
10201
|
+
templateStorage: false
|
|
10202
|
+
},
|
|
10203
|
+
appointment_staff_daily_digest: {
|
|
10204
|
+
wording: "fixed",
|
|
10205
|
+
perEvent: false,
|
|
10206
|
+
templateStorage: false
|
|
10207
|
+
},
|
|
10208
|
+
form_owner_notification: {
|
|
10209
|
+
wording: "fixed",
|
|
10210
|
+
perEvent: false,
|
|
10211
|
+
templateStorage: false
|
|
10212
|
+
},
|
|
10213
|
+
support_ticket_notification: {
|
|
10214
|
+
wording: "fixed",
|
|
10215
|
+
perEvent: false,
|
|
10216
|
+
templateStorage: false
|
|
10217
|
+
},
|
|
10218
|
+
support_ticket_customer_reply: {
|
|
10219
|
+
wording: "fixed",
|
|
10220
|
+
perEvent: false,
|
|
10221
|
+
templateStorage: false
|
|
10222
|
+
},
|
|
10223
|
+
support_ticket_reply: {
|
|
10224
|
+
wording: "fixed",
|
|
10225
|
+
perEvent: false,
|
|
10226
|
+
templateStorage: false
|
|
10227
|
+
},
|
|
10228
|
+
auth_confirmation: {
|
|
10229
|
+
wording: "fixed",
|
|
10230
|
+
perEvent: false,
|
|
10231
|
+
templateStorage: false
|
|
10232
|
+
},
|
|
10233
|
+
password_reset: {
|
|
10234
|
+
wording: "fixed",
|
|
10235
|
+
perEvent: false,
|
|
10236
|
+
templateStorage: false
|
|
10237
|
+
},
|
|
10238
|
+
portal_magic_link: {
|
|
10239
|
+
wording: "fixed",
|
|
10240
|
+
perEvent: false,
|
|
10241
|
+
templateStorage: true
|
|
10242
|
+
},
|
|
10243
|
+
site_invitation: {
|
|
10244
|
+
wording: "fixed",
|
|
10245
|
+
perEvent: false,
|
|
10246
|
+
templateStorage: false
|
|
10247
|
+
},
|
|
10248
|
+
demo_claim_preparation_failed: {
|
|
10249
|
+
wording: "fixed",
|
|
10250
|
+
perEvent: false,
|
|
10251
|
+
templateStorage: false
|
|
10252
|
+
},
|
|
10253
|
+
billing_payment_issue: {
|
|
10254
|
+
wording: "fixed",
|
|
10255
|
+
perEvent: false,
|
|
10256
|
+
templateStorage: false
|
|
10257
|
+
},
|
|
10258
|
+
order_instalment_payment_failed: {
|
|
10259
|
+
wording: "fixed",
|
|
10260
|
+
perEvent: false,
|
|
10261
|
+
templateStorage: true
|
|
10262
|
+
},
|
|
10263
|
+
macadamia_contact_inquiry: {
|
|
10264
|
+
wording: "fixed",
|
|
10265
|
+
perEvent: false,
|
|
10266
|
+
templateStorage: false
|
|
10267
|
+
},
|
|
10268
|
+
newsletter_confirmation: {
|
|
10269
|
+
wording: "fixed",
|
|
10270
|
+
perEvent: false,
|
|
10271
|
+
templateStorage: true
|
|
10272
|
+
},
|
|
10273
|
+
newsletter_campaign_test_send: {
|
|
10274
|
+
wording: "written_each_time",
|
|
10275
|
+
perEvent: false,
|
|
10276
|
+
templateStorage: false
|
|
10277
|
+
},
|
|
10278
|
+
newsletter_campaign_delivery: {
|
|
10279
|
+
wording: "written_each_time",
|
|
10280
|
+
perEvent: false,
|
|
10281
|
+
templateStorage: false
|
|
10282
|
+
},
|
|
10283
|
+
site_email_template_test_send: {
|
|
10284
|
+
wording: "written_each_time",
|
|
10285
|
+
perEvent: false,
|
|
10286
|
+
templateStorage: false
|
|
10287
|
+
},
|
|
10288
|
+
participant_message: {
|
|
10289
|
+
wording: "written_each_time",
|
|
10290
|
+
perEvent: false,
|
|
10291
|
+
templateStorage: false
|
|
10292
|
+
}
|
|
10293
|
+
};
|
|
10294
|
+
const EMAIL_KEYS = Object.keys(EMAIL_DEFINITIONS);
|
|
10295
|
+
const EMAIL_TEMPLATE_TYPES = EMAIL_KEYS.filter((key) => EMAIL_DEFINITIONS[key].wording === "editable");
|
|
10296
|
+
const EVENT_EMAIL_TEMPLATE_TYPES = EMAIL_TEMPLATE_TYPES.filter((key) => EMAIL_DEFINITIONS[key].perEvent);
|
|
10297
|
+
/** Includes upcoming wording conversions; storage admission doesn't expose an editor. */
|
|
10298
|
+
const EMAIL_TEMPLATE_STORAGE_KEYS = EMAIL_KEYS.filter((key) => EMAIL_DEFINITIONS[key].templateStorage);
|
|
10299
|
+
function isEmailKey(value) {
|
|
10300
|
+
return Object.prototype.hasOwnProperty.call(EMAIL_DEFINITIONS, value);
|
|
10301
|
+
}
|
|
10302
|
+
function isEventEmailTemplateType(value) {
|
|
10303
|
+
return EVENT_EMAIL_TEMPLATE_TYPES.some((key) => key === value);
|
|
10304
|
+
}
|
|
10305
|
+
|
|
10306
|
+
//#endregion
|
|
10307
|
+
export { mapDrawingSchema as $, ok as $n, isNonNegativeWholeCents as $t, assessMailingListTargeting as A, unsafeAsEntitlementId as An, addIntervalPreservingMonthAnchor as At, parsePublicContactDetails as B, unsafeAsPositiveSeconds as Bn, parseEventPageSurfaceSubject as Bt, exposeSecret as C, unsafeAsAppointmentResourceId as Cn, toScheduledBookingDateTime as Ct, parseMailingListContentVersionId as D, unsafeAsDeclaredMimeType as Dn, isLiveOrderInstalmentPlanStatus as Dt, parseMailingListBlockId as E, unsafeAsCheckoutLineId as En, validateBookingPaymentOptionKinds as Et, parseMailingListPlacementTargeting as F, unsafeAsMediaAssetId as Fn, omitZeroAmountFutureRows as Ft, locationMapLinks as G, unsafeAsSiteId as Gn, publicEventPricingDisplay as Gt, assertAllowedPublicUrl as H, unsafeAsProductId as Hn, formatEventPricing as Ht, DEFAULT_MAILING_LIST_TARGETING as I, unsafeAsOccurrenceId as In, splitAmountWithRemainderInFirst as It, parseLocationValue as J, unsafeAsStorageBucketName as Jn, resolveExactOrFreePriceDisplay as Jt, locationValueSchema as K, unsafeAsSiteScopedStoragePath as Kn, publicEventPricingOptions as Kt, planMailingListChoicePresentation as L, unsafeAsOriginalFilename as Ln, resolveFlexibleBalanceDueAt as Lt, parseMailingListProviderScope as M, unsafeAsEventOfferingId as Mn, getBookingInstalmentDepositRefundability as Mt, parseFooterCompositionV1 as N, unsafeAsEventSeriesId as Nn, getBookingInstalmentPlanIntervalCount as Nt, parseMailingListSignedSourceToken as O, unsafeAsDemoCapability as On, isSettleableFlexibleBalanceContractStatus as Ot, footerNavigationLayouts as P, unsafeAsFileChecksumSha256 as Pn, getBookingInstalmentProtectedDepositAmountCents as Pt, isPresent as Q, err as Qn, mapReadonlyNonEmptyArray as Qt, isCanonicalRichTextDoc as R, unsafeAsPassId as Rn, resolveFlexibleBalanceInitialPaymentCents as Rt, createSecret as S, parseUserId as Sn, toBookingTimeZone as St, slugify as T, unsafeAsBookingSessionId as Tn, parseBookingTimeZone as Tt, decideLocationSave as U, unsafeAsSafeDownloadFilename as Un, isPublicEventPricingOptionDisplayable as Ut, parseWeeklyOpeningHours as V, unsafeAsPricingOptionId as Vn, formatBookingPriceDisplay as Vt, stableJsonStringify as W, unsafeAsSiteFontId as Wn, publicEventPricingCurrency as Wt, publicLocationSchema as X, unsafeAsUploadSessionId as Xn, fromArray as Xt, projectPublicLocation as Y, unsafeAsStoragePath as Yn, resolvePricingOptionsPriceDisplay as Yt, toLocationCoordinates as Z, unsafeAsUserId as Zn, mapNonEmptyArray as Zt, CANCELLED_SESSION_STATUS as _, parseMembershipId as _n, parseBookingLocalDate as _t, NAVIGATION_LOCATIONS as a, unsafeAsPositiveCents as an, formatTime as at, resolveWholeSeriesBookingPhase as b, parseSiteId as bn, resolveBookingBusinessTimeZone as bt, parseVerticalId as c, assertNever as cn, resolveBookingDisplayContext as ct, DEFAULT_EVENT_TYPE_PRESETS as d, parseCustomerId as dn, formatLocalScheduleDate as dt, parseNonNegativeCents as en, selectCategoryView as et, EVENT_PRESET_VALUES as f, parseDatedOfferingCapacityHoldAuditRef as fn, formatLocalScheduleTime as ft, publicReadFreshnessSeconds as g, parseMediaAssetId as gn, localScheduleTimePattern as gt, WHOLE_SERIES_EVENT_PRESET_VALUES as h, parseEventSeriesId as hn, getLocalScheduleTime as ht, EVENT_PRESET_VOCABULARY as i, unsafeAsNonNegativeCents as in, formatDateTimeRange as it, DEFAULT_MAILING_LIST_SUCCESS_MESSAGE as j, unsafeAsEventInstanceId as jn, clampConfiguredAmount as jt, parseMailingListRuntimeContext as k, unsafeAsDetectedMimeType as kn, isSettleableOrderInstalmentPlanStatus as kt, parseVerticalPresetId as l, formatDurationUnitLabel as ln, buildBookingUtcDateWindow as lt, SURFACE_EVENT_PRESET_VALUES as m, parseEditorSessionGeneration as mn, getLocalScheduleDate as mt, authoredValuesEqual as n, toCents as nn, formatDateRange as nt, areConfiguredBookingVerticalConfigsEqual as o, formatCurrencyAmount as on, formatTimeRange as ot, ORDINARY_EVENT_PRESET_VALUES as p, parseDatedOfferingCapacityHoldId as pn, getBookingLocalDateKey as pt, normalizeLocationAddressText as q, unsafeAsStaffMemberId as qn, publicEventPricingType as qt, resolveEventCatalogue as r, toNonNegativeCents as rn, formatDateTime as rt, canonicalizeConfiguredBookingVerticalConfig as s, parseCurrencyCode as sn, inferSiteDateLocale as st, committedWriteProofSchema as t, parsePositiveCents as tn, formatDate as tt, resolveSiteVerticalBookingConfig as u, parseApiKeyId as un, compareLocalScheduleDates as ut, nextLiveRunSessionStartMs as v, parsePageId as vn, parseLocalScheduleTime as vt, parseManagementKey as w, unsafeAsBookingRequirementId as wn, toUtcIsoDateTimeString as wt, resolveWholeSeriesRunLifecycle as x, parseSiteTermsVersionId as xn, resolveBookingTimeZoneId as xt, orderedLiveRunSessions as y, parseRetiredCourseId as yn, parseUtcIsoDateTimeString as yt, formatOpeningHoursRows as z, unsafeAsPositiveByteCount as zn, deriveTotalTicketQuantity as zt };
|