@jant/core 0.3.50 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-C7CtIQM-.js → app-B9XQDSoB.js} +112 -70
- package/dist/{app-CIx9SSOi.js → app-CHW6VVQt.js} +1 -1
- package/dist/client/.vite/manifest.json +1 -1
- package/dist/client/_assets/{client-auth-Ce5WEAVS.js → client-auth-DFDajqqT.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/node.js +2 -2
- package/package.json +1 -1
- package/src/client/compose-bridge.ts +5 -0
- package/src/client/palette-search-trigger.ts +35 -0
- package/src/client-auth.ts +1 -0
- package/src/lib/__tests__/hosted-domain.test.ts +1 -1
- package/src/lib/hosted-domain-check.ts +21 -80
- package/src/lib/hosted-domain.ts +1 -1
- package/src/routes/api/internal/__tests__/sites.test.ts +168 -0
- package/src/routes/api/internal/sites.ts +63 -10
- package/src/routes/hosted/__tests__/domain-check.test.ts +30 -19
- package/src/routes/hosted/domain-check.ts +9 -14
- package/src/services/site-admin.ts +62 -1
|
@@ -3418,9 +3418,9 @@ function normalizeThemeColorForMeta(color) {
|
|
|
3418
3418
|
* internal paths (e.g. `/_assets/client-HASH.js`) embedded by the Worker build
|
|
3419
3419
|
* from the Vite client manifest. Used only in production (IS_VITE_DEV=false).
|
|
3420
3420
|
*/ var IS_VITE_DEV = typeof __JANT_DEV__ !== "undefined" && __JANT_DEV__ === true;
|
|
3421
|
-
var CORE_VERSION = "0.
|
|
3421
|
+
var CORE_VERSION = "0.4.0-aa2a43928b7884a5";
|
|
3422
3422
|
var CLIENT_JS_FILE = "/_assets/client-dSfWfMe9.js";
|
|
3423
|
-
var CLIENT_AUTH_JS_FILE = "/_assets/client-auth-
|
|
3423
|
+
var CLIENT_AUTH_JS_FILE = "/_assets/client-auth-DFDajqqT.js";
|
|
3424
3424
|
var CLIENT_CSS_FILE = "/_assets/client-BoUn7xBo.css";
|
|
3425
3425
|
var CLIENT_CJK_CSS_FILE = "/_assets/client-cjk-B7Z0snDu.css";
|
|
3426
3426
|
var CLIENT_CJK_TC_CSS_FILE = "/_assets/client-cjk-tc-BesJYrb2.css";
|
|
@@ -3739,7 +3739,7 @@ var IconSprite = () => {
|
|
|
3739
3739
|
const cjkSerifFont = appConfig?.cjkSerifFont ?? "off";
|
|
3740
3740
|
const cjkStylesheetPath = cjkSerifFont === "zh-Hans" ? IS_VITE_DEV ? assetPath("/src/style-cjk.css") : toPublicAssetPath(CLIENT_CJK_CSS_FILE, assetBasePath) : cjkSerifFont === "zh-Hant" ? IS_VITE_DEV ? assetPath("/src/style-cjk-tc.css") : toPublicAssetPath(CLIENT_CJK_TC_CSS_FILE, assetBasePath) : cjkSerifFont === "ja" ? IS_VITE_DEV ? assetPath("/src/style-cjk-jp.css") : toPublicAssetPath(CLIENT_CJK_JP_CSS_FILE, assetBasePath) : cjkSerifFont === "ko" ? IS_VITE_DEV ? assetPath("/src/style-cjk-kr.css") : toPublicAssetPath(CLIENT_CJK_KR_CSS_FILE, assetBasePath) : null;
|
|
3741
3741
|
const clientScriptPath = IS_VITE_DEV ? resolvedClientBundle === "full" ? assetPath("/src/client-auth.ts") : assetPath("/src/client.ts") : toPublicAssetPath(resolvedClientBundle === "full" ? CLIENT_AUTH_JS_FILE : CLIENT_JS_FILE, assetBasePath);
|
|
3742
|
-
const faviconAssetVersion = resolvedFaviconVersion || "0.
|
|
3742
|
+
const faviconAssetVersion = resolvedFaviconVersion || "0.4.0-aa2a43928b7884a5";
|
|
3743
3743
|
const resolvedFaviconHref = faviconHref ?? (faviconAssetVersion ? toPublicPath(`/favicon.ico?v=${faviconAssetVersion}`, sitePathPrefix) : toPublicPath("/favicon.ico", sitePathPrefix));
|
|
3744
3744
|
const resolvedAppleTouchHref = appleTouchHref ?? (faviconAssetVersion ? toPublicPath(`/apple-touch-icon.png?v=${faviconAssetVersion}`, sitePathPrefix) : toPublicPath("/apple-touch-icon.png", sitePathPrefix));
|
|
3745
3745
|
const socialImageHref = resolvedSocialImagePath && (isFullUrl(resolvedSocialImagePath) || resolvedSocialImagePath.startsWith("//") ? resolvedSocialImagePath : toAbsoluteSiteUrl(resolvedSocialImagePath, appConfig?.siteUrl || "", sitePathPrefix));
|
|
@@ -6397,83 +6397,44 @@ hostedSsoRoutes.get("/__sso", async (c) => {
|
|
|
6397
6397
|
}
|
|
6398
6398
|
});
|
|
6399
6399
|
//#endregion
|
|
6400
|
-
//#region src/lib/crypto.ts
|
|
6401
|
-
/**
|
|
6402
|
-
* Compare two byte arrays using a constant-time loop.
|
|
6403
|
-
*
|
|
6404
|
-
* This avoids relying on runtime-specific crypto helpers so the same behavior
|
|
6405
|
-
* works in Node.js and Workers.
|
|
6406
|
-
*
|
|
6407
|
-
* @param a - First byte array
|
|
6408
|
-
* @param b - Second byte array
|
|
6409
|
-
* @returns `true` when the inputs are byte-for-byte identical
|
|
6410
|
-
*
|
|
6411
|
-
* @example
|
|
6412
|
-
* ```ts
|
|
6413
|
-
* const isEqual = timingSafeEqualBytes(
|
|
6414
|
-
* new TextEncoder().encode("abc"),
|
|
6415
|
-
* new TextEncoder().encode("abc"),
|
|
6416
|
-
* );
|
|
6417
|
-
* ```
|
|
6418
|
-
*/ function timingSafeEqualBytes(a, b) {
|
|
6419
|
-
if (a.byteLength !== b.byteLength) return false;
|
|
6420
|
-
let mismatch = 0;
|
|
6421
|
-
for (let index = 0; index < a.byteLength; index += 1) mismatch |= (a[index] ?? 0) ^ (b[index] ?? 0);
|
|
6422
|
-
return mismatch === 0;
|
|
6423
|
-
}
|
|
6424
|
-
/**
|
|
6425
|
-
* Compare two strings using a constant-time byte comparison.
|
|
6426
|
-
*
|
|
6427
|
-
* @param a - First string
|
|
6428
|
-
* @param b - Second string
|
|
6429
|
-
* @returns `true` when the UTF-8 byte sequences are identical
|
|
6430
|
-
*
|
|
6431
|
-
* @example
|
|
6432
|
-
* ```ts
|
|
6433
|
-
* const isEqual = timingSafeEqualText("token-a", "token-b");
|
|
6434
|
-
* ```
|
|
6435
|
-
*/ function timingSafeEqualText(a, b) {
|
|
6436
|
-
const encoder = new TextEncoder();
|
|
6437
|
-
return timingSafeEqualBytes(encoder.encode(a), encoder.encode(b));
|
|
6438
|
-
}
|
|
6439
|
-
//#endregion
|
|
6440
6400
|
//#region src/lib/hosted-domain-check.ts
|
|
6401
|
+
var VERIFICATION_HMAC_VERSION = "v1";
|
|
6441
6402
|
var textEncoder$3 = new TextEncoder();
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
6403
|
+
function bytesToHex$1(bytes) {
|
|
6404
|
+
let out = "";
|
|
6405
|
+
for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
|
|
6406
|
+
return out;
|
|
6447
6407
|
}
|
|
6448
|
-
|
|
6408
|
+
/**
|
|
6409
|
+
* Compute the plaintext HMAC token returned from
|
|
6410
|
+
* `/.well-known/jant-verification`.
|
|
6411
|
+
*
|
|
6412
|
+
* The control plane sends a nonce in the query string; the site replies with
|
|
6413
|
+
* `jant-verification=<hex>` where `<hex>` is `HMAC-SHA256(secret, payload)`
|
|
6414
|
+
* over `payload = "v1:" + host + ":" + nonce`. The shared secret is
|
|
6415
|
+
* `HOSTED_CONTROL_PLANE_DOMAIN_CHECK_SECRET`.
|
|
6416
|
+
*/ async function computeHostedVerificationToken(secret, host, nonce) {
|
|
6449
6417
|
const key = await crypto.subtle.importKey("raw", textEncoder$3.encode(secret), {
|
|
6450
6418
|
name: "HMAC",
|
|
6451
6419
|
hash: "SHA-256"
|
|
6452
6420
|
}, false, ["sign"]);
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
async function signHostedDomainCheckToken(secret, claims) {
|
|
6456
|
-
const payload = toBase64Url$1(textEncoder$3.encode(JSON.stringify(claims)));
|
|
6457
|
-
return `${payload}.${toBase64Url$1(await createHmacSignature$1(secret, payload))}`;
|
|
6421
|
+
const payload = `${VERIFICATION_HMAC_VERSION}:${host.trim().toLowerCase()}:${nonce}`;
|
|
6422
|
+
return bytesToHex$1(new Uint8Array(await crypto.subtle.sign("HMAC", key, textEncoder$3.encode(payload))));
|
|
6458
6423
|
}
|
|
6459
6424
|
//#endregion
|
|
6460
6425
|
//#region src/routes/hosted/domain-check.ts
|
|
6461
6426
|
var hostedDomainCheckRoutes = new Hono();
|
|
6462
|
-
hostedDomainCheckRoutes.get("/.well-known/jant-
|
|
6427
|
+
hostedDomainCheckRoutes.get("/.well-known/jant-verification", async (c) => {
|
|
6463
6428
|
const secret = getHostedControlPlaneDomainCheckSecret(c.env);
|
|
6464
|
-
if (!secret) throw new NotFoundError("Hosted domain
|
|
6465
|
-
if (!c.var.currentSiteDomain) throw new NotFoundError("Hosted domain
|
|
6429
|
+
if (!secret) throw new NotFoundError("Hosted domain verification endpoint");
|
|
6430
|
+
if (!c.var.currentSiteDomain) throw new NotFoundError("Hosted domain verification endpoint");
|
|
6466
6431
|
const nonce = c.req.query("nonce")?.trim();
|
|
6467
|
-
if (!nonce) return c.
|
|
6468
|
-
const token = await
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
iss: "jant-core",
|
|
6474
|
-
nonce
|
|
6475
|
-
});
|
|
6476
|
-
return c.json({ token }, 200, { "Cache-Control": "no-store" });
|
|
6432
|
+
if (!nonce) return c.text("Missing nonce.", 400);
|
|
6433
|
+
const token = await computeHostedVerificationToken(secret, c.var.currentSiteDomain.host.trim().toLowerCase(), nonce);
|
|
6434
|
+
return c.text(`jant-verification=${token}\n`, 200, {
|
|
6435
|
+
"Cache-Control": "no-store",
|
|
6436
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
6437
|
+
});
|
|
6477
6438
|
});
|
|
6478
6439
|
//#endregion
|
|
6479
6440
|
//#region src/lib/collection-paths.ts
|
|
@@ -24752,8 +24713,10 @@ internalApiTokensRoutes.post("/purge", requireInternalAdminApi(), async (c) => {
|
|
|
24752
24713
|
});
|
|
24753
24714
|
//#endregion
|
|
24754
24715
|
//#region src/routes/api/internal/sites.ts
|
|
24716
|
+
var ManagedSiteKeySchema = z.string().trim().toLowerCase().min(3).max(40).regex(/^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])?$/, "Site key must use lowercase letters, numbers, or hyphens.");
|
|
24717
|
+
var SiteKeyAvailabilityQuerySchema = z.object({ key: ManagedSiteKeySchema });
|
|
24755
24718
|
var CreateManagedSiteSchema = z.object({
|
|
24756
|
-
key:
|
|
24719
|
+
key: ManagedSiteKeySchema,
|
|
24757
24720
|
primaryHost: z.string().trim().toLowerCase().min(1).max(255).regex(/^(?=.{1,255}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/, "Primary host must be a valid hostname."),
|
|
24758
24721
|
siteName: z.string().trim().min(1).max(120),
|
|
24759
24722
|
siteLanguage: z.string().trim().max(35).optional(),
|
|
@@ -24778,6 +24741,12 @@ internalSitesRoutes.post("/", requireInternalAdminApi(), async (c) => {
|
|
|
24778
24741
|
status: result.site.status
|
|
24779
24742
|
}, 201);
|
|
24780
24743
|
});
|
|
24744
|
+
internalSitesRoutes.get("/availability", requireInternalAdminApi(), async (c) => {
|
|
24745
|
+
assertHostBasedMode(c.env);
|
|
24746
|
+
const query = parseValidated(SiteKeyAvailabilityQuerySchema, { key: c.req.query("key") ?? "" });
|
|
24747
|
+
const result = await c.var.services.siteAdmin.isManagedSiteKeyAvailable(query.key);
|
|
24748
|
+
return c.json(result);
|
|
24749
|
+
});
|
|
24781
24750
|
internalSitesRoutes.delete("/:siteId", requireInternalAdminApi(), async (c) => {
|
|
24782
24751
|
assertHostBasedMode(c.env);
|
|
24783
24752
|
await c.var.services.siteAdmin.deleteManagedSite(c.req.param("siteId"), { storage: c.var.storage });
|
|
@@ -24847,6 +24816,18 @@ internalSitesRoutes.post("/:siteId/domains/:domainId/primary", requireInternalAd
|
|
|
24847
24816
|
redirectToPrimary: domain.redirectToPrimary
|
|
24848
24817
|
})) });
|
|
24849
24818
|
});
|
|
24819
|
+
var ManagedSiteDomainRedirectSchema = z.object({ redirectToPrimary: z.boolean() });
|
|
24820
|
+
internalSitesRoutes.post("/:siteId/domains/:domainId/redirect", requireInternalAdminApi(), async (c) => {
|
|
24821
|
+
assertHostBasedMode(c.env);
|
|
24822
|
+
const body = parseValidated(ManagedSiteDomainRedirectSchema, await c.req.json());
|
|
24823
|
+
const domains = await c.var.services.siteAdmin.setManagedSiteDomainRedirect(c.req.param("siteId"), c.req.param("domainId"), body.redirectToPrimary);
|
|
24824
|
+
return c.json({ domains: domains.map((domain) => ({
|
|
24825
|
+
host: domain.host,
|
|
24826
|
+
id: domain.id,
|
|
24827
|
+
kind: domain.kind,
|
|
24828
|
+
redirectToPrimary: domain.redirectToPrimary
|
|
24829
|
+
})) });
|
|
24830
|
+
});
|
|
24850
24831
|
internalSitesRoutes.delete("/:siteId/domains/:domainId", requireInternalAdminApi(), async (c) => {
|
|
24851
24832
|
assertHostBasedMode(c.env);
|
|
24852
24833
|
const domains = await c.var.services.siteAdmin.deleteManagedSiteDomain(c.req.param("siteId"), c.req.param("domainId"));
|
|
@@ -26616,7 +26597,7 @@ var cors = (options) => {
|
|
|
26616
26597
|
//#endregion
|
|
26617
26598
|
//#region src/lib/hosted-domain.ts
|
|
26618
26599
|
var CANONICAL_REDIRECT_BYPASS_PREFIXES = [
|
|
26619
|
-
"/.well-known/jant-
|
|
26600
|
+
"/.well-known/jant-verification",
|
|
26620
26601
|
"/__dev",
|
|
26621
26602
|
"/__sso",
|
|
26622
26603
|
"/api/",
|
|
@@ -26963,6 +26944,46 @@ function createD1RateLimiter(db, schema, now = () => Math.floor(Date.now() / 1e3
|
|
|
26963
26944
|
} };
|
|
26964
26945
|
}
|
|
26965
26946
|
//#endregion
|
|
26947
|
+
//#region src/lib/crypto.ts
|
|
26948
|
+
/**
|
|
26949
|
+
* Compare two byte arrays using a constant-time loop.
|
|
26950
|
+
*
|
|
26951
|
+
* This avoids relying on runtime-specific crypto helpers so the same behavior
|
|
26952
|
+
* works in Node.js and Workers.
|
|
26953
|
+
*
|
|
26954
|
+
* @param a - First byte array
|
|
26955
|
+
* @param b - Second byte array
|
|
26956
|
+
* @returns `true` when the inputs are byte-for-byte identical
|
|
26957
|
+
*
|
|
26958
|
+
* @example
|
|
26959
|
+
* ```ts
|
|
26960
|
+
* const isEqual = timingSafeEqualBytes(
|
|
26961
|
+
* new TextEncoder().encode("abc"),
|
|
26962
|
+
* new TextEncoder().encode("abc"),
|
|
26963
|
+
* );
|
|
26964
|
+
* ```
|
|
26965
|
+
*/ function timingSafeEqualBytes(a, b) {
|
|
26966
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
26967
|
+
let mismatch = 0;
|
|
26968
|
+
for (let index = 0; index < a.byteLength; index += 1) mismatch |= (a[index] ?? 0) ^ (b[index] ?? 0);
|
|
26969
|
+
return mismatch === 0;
|
|
26970
|
+
}
|
|
26971
|
+
/**
|
|
26972
|
+
* Compare two strings using a constant-time byte comparison.
|
|
26973
|
+
*
|
|
26974
|
+
* @param a - First string
|
|
26975
|
+
* @param b - Second string
|
|
26976
|
+
* @returns `true` when the UTF-8 byte sequences are identical
|
|
26977
|
+
*
|
|
26978
|
+
* @example
|
|
26979
|
+
* ```ts
|
|
26980
|
+
* const isEqual = timingSafeEqualText("token-a", "token-b");
|
|
26981
|
+
* ```
|
|
26982
|
+
*/ function timingSafeEqualText(a, b) {
|
|
26983
|
+
const encoder = new TextEncoder();
|
|
26984
|
+
return timingSafeEqualBytes(encoder.encode(a), encoder.encode(b));
|
|
26985
|
+
}
|
|
26986
|
+
//#endregion
|
|
26966
26987
|
//#region src/lib/hosted-sso.ts
|
|
26967
26988
|
var textEncoder = new TextEncoder();
|
|
26968
26989
|
var textDecoder = new TextDecoder();
|
|
@@ -30755,6 +30776,14 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
|
|
|
30755
30776
|
if (supportsDrizzleTransaction(db, databaseDialect)) return db.transaction(async (tx) => createWithDatabase(tx, input));
|
|
30756
30777
|
return createWithDatabase(db, input);
|
|
30757
30778
|
},
|
|
30779
|
+
async isManagedSiteKeyAvailable(key) {
|
|
30780
|
+
assertManagedSiteOperationsEnabled();
|
|
30781
|
+
const normalizedKey = key.trim();
|
|
30782
|
+
return {
|
|
30783
|
+
available: !(await db.select({ id: sites.id }).from(sites).where(eq(sites.key, normalizedKey)).limit(1))[0],
|
|
30784
|
+
key: normalizedKey
|
|
30785
|
+
};
|
|
30786
|
+
},
|
|
30758
30787
|
async getManagedSiteMediaUsage(siteId) {
|
|
30759
30788
|
assertManagedSiteOperationsEnabled();
|
|
30760
30789
|
return getManagedSiteMediaUsage(siteId);
|
|
@@ -30877,7 +30906,7 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
|
|
|
30877
30906
|
const timestamp = now();
|
|
30878
30907
|
if (input.makePrimary) await targetDb.update(siteDomains).set({
|
|
30879
30908
|
kind: "alias",
|
|
30880
|
-
redirectToPrimary:
|
|
30909
|
+
redirectToPrimary: false,
|
|
30881
30910
|
updatedAt: timestamp
|
|
30882
30911
|
}).where(eq(siteDomains.siteId, normalizedSiteId));
|
|
30883
30912
|
await targetDb.insert(siteDomains).values({
|
|
@@ -30892,6 +30921,19 @@ function createSiteAdminService(db, databaseSchema = sqliteSchemaBundle, databas
|
|
|
30892
30921
|
});
|
|
30893
30922
|
});
|
|
30894
30923
|
},
|
|
30924
|
+
async setManagedSiteDomainRedirect(siteId, domainId, redirectToPrimary) {
|
|
30925
|
+
assertManagedSiteOperationsEnabled();
|
|
30926
|
+
return mutateSiteDomains(siteId, async (targetDb, normalizedSiteId) => {
|
|
30927
|
+
await requireSite(targetDb, normalizedSiteId);
|
|
30928
|
+
const normalizedDomainId = domainId.trim();
|
|
30929
|
+
if (!(await targetDb.select({ id: siteDomains.id }).from(siteDomains).where(sql`${siteDomains.id} = ${normalizedDomainId} AND ${siteDomains.siteId} = ${normalizedSiteId}`).limit(1))[0]) throw new NotFoundError("Site domain");
|
|
30930
|
+
const timestamp = now();
|
|
30931
|
+
await targetDb.update(siteDomains).set({
|
|
30932
|
+
redirectToPrimary,
|
|
30933
|
+
updatedAt: timestamp
|
|
30934
|
+
}).where(eq(siteDomains.id, normalizedDomainId));
|
|
30935
|
+
});
|
|
30936
|
+
},
|
|
30895
30937
|
async setManagedSitePrimaryDomain(siteId, domainId) {
|
|
30896
30938
|
assertManagedSiteOperationsEnabled();
|
|
30897
30939
|
return mutateSiteDomains(siteId, async (targetDb, normalizedSiteId) => {
|
|
@@ -2163,7 +2163,7 @@ The @mediabunny/aac-encoder extension package provides support for encoding AAC.
|
|
|
2163
2163
|
The @mediabunny/ac3 extension package provides support for encoding and decoding AC-3/E-AC-3.`),t.includes(`flac`)&&e.push(`
|
|
2164
2164
|
The @mediabunny/flac-encoder extension package provides support for encoding FLAC.`)}else e.push(`
|
|
2165
2165
|
Check the discardedTracks field for more info.`)}return e}async execute(){if(!this.isValid)throw Error(`Cannot execute this conversion because its output configuration is invalid. Make sure to always check the isValid field before executing a conversion.
|
|
2166
|
-
`+this._getInvalidityExplanation().join(``));if(this._executed)throw Error(`Conversion cannot be executed twice.`);if(this._executed=!0,this.onProgress){let e=this.utilizedTracks.map(e=>e.computeDuration()),t=Math.max(0,...await Promise.all(e));this._computeProgress=!0,this._totalDuration=Math.min(t-this._startTimestamp,this._endTimestamp-this._startTimestamp);for(let e of this.utilizedTracks)this._maxTimestamps.set(e.id,0);this.onProgress?.(0)}await this.output.start(),this._start();try{await Promise.all(this._trackPromises)}catch(e){throw this._canceled||this.cancel(),e}if(this._canceled)throw new VL;await this.output.finalize(),this._computeProgress&&this.onProgress?.(1)}async cancel(){if(!(this.output.state===`finalizing`||this.output.state===`finalized`)){if(this._canceled){console.warn(`Conversion already canceled.`);return}this._canceled=!0,await this.output.cancel()}}async _processVideoTrack(e,t){let n=e.codec;if(!n){this.discardedTracks.push({track:e,reason:`unknown_source_codec`});return}let r,i=LO(e.rotation+(t.rotate??0)),a=i,o=this.output.format.supportsVideoRotationMetadata&&(t.allowRotationMetadata??!0),[s,c]=i%180==0?[e.squarePixelWidth,e.squarePixelHeight]:[e.squarePixelHeight,e.squarePixelWidth],l=t.crop;l&&Zj(l,s,c);let[u,d]=l?[l.width,l.height]:[s,c],f=u,p=d,m=f/p,h=e=>Math.ceil(e/2)*2;t.width!==void 0&&t.height===void 0?(f=h(t.width),p=h(Math.round(f/m))):t.width===void 0&&t.height!==void 0?(p=h(t.height),f=h(Math.round(p*m))):t.width!==void 0&&t.height!==void 0&&(f=h(t.width),p=h(t.height));let g=await e.getFirstTimestamp(),_=this.output.format.getSupportedVideoCodecs(),v=!!t.forceTranscode||g<this._startTimestamp||!!t.frameRate||t.keyFrameInterval!==void 0||t.process!==void 0||t.bitrate!==void 0||!_.includes(n)||t.codec&&t.codec!==n||f!==u||p!==d||i!==0&&!o||!!l,y=t.alpha??`discard`;if(v){if(!await e.canDecode()){this.discardedTracks.push({track:e,reason:`undecodable_source_codec`});return}t.codec&&(_=_.filter(e=>e===t.codec));let n=t.bitrate??_L,s=await xL(_,{width:t.process&&t.processedWidth?t.processedWidth:f,height:t.process&&t.processedHeight?t.processedHeight:p,bitrate:n});if(!s){this.discardedTracks.push({track:e,reason:`no_encodable_target_codec`});return}let c={codec:s,bitrate:n,keyFrameInterval:t.keyFrameInterval,sizeChangeBehavior:t.fit??`passThrough`,alpha:y,hardwareAcceleration:t.hardwareAcceleration},m=new DL(c);r=m;let h=f!==u||p!==d||i!==0&&(!o||t.process!==void 0)||!!l||e.squarePixelWidth!==e.codedWidth||e.squarePixelHeight!==e.codedHeight;if(!h){let t=new FL({format:new cL,target:new eL}),n=new DL(c);t.addVideoTrack(n),await t.start();let r=await new yM(e).getSample(g);if(r)try{await n.add(r),r.close(),await t.finalize()}catch(e){console.info(`Error when probing encoder support. Falling back to rerender path.`,e),h=!0,t.cancel()}else await t.cancel()}h?this._trackPromises.push((async()=>{await this._started;let n=new bM(e,{width:f,height:p,fit:t.fit??`fill`,rotation:i,crop:t.crop,poolSize:1,alpha:y===`keep`}).canvases(this._startTimestamp,this._endTimestamp),r=t.frameRate;a=0;let o=null,s=null,c=null,l=async n=>{U(o),U(r!==void 0);let i=Math.round((n-s)*r);for(let n=1;n<i;n++){let i=new Jj(o,{timestamp:s+n/r,duration:1/r});await this._registerVideoSample(e,t,m,i),i.close()}};for await(let{canvas:i,timestamp:a,duration:u}of n){if(this._canceled)return;let n=Math.max(a-this._startTimestamp,0);if(c=n+u,r!==void 0){let e=Math.floor(n*r)/r;if(o!==null)if(e<=s){o=i,s=e;continue}else await l(e);n=e}let d=new Jj(i,{timestamp:n,duration:r===void 0?u:1/r});await this._registerVideoSample(e,t,m,d),d.close(),r!==void 0&&(o=i,s=n)}o&&(U(c!==null),U(r!==void 0),await l(Math.floor(c*r)/r)),m.close(),this._synchronizer.closeTrack(e.id)})()):this._trackPromises.push((async()=>{await this._started;let n=new yM(e),r=t.frameRate,i=null,a=null,o=null,s=async n=>{U(i),U(r!==void 0);let o=Math.round((n-a)*r);for(let n=1;n<o;n++)i.setTimestamp(a+n/r),i.setDuration(1/r),await this._registerVideoSample(e,t,m,i);i.close()};for await(let c of n.samples(this._startTimestamp,this._endTimestamp)){if(this._canceled){c.close(),i?.close();return}let n=Math.max(c.timestamp-this._startTimestamp,0);if(o=n+c.duration,r!==void 0){let e=Math.floor(n*r)/r;if(i!==null)if(e<=a){i.close(),i=c,a=e;continue}else await s(e);n=e,c.setDuration(1/r)}c.setTimestamp(n),await this._registerVideoSample(e,t,m,c),r===void 0?c.close():(i=c,a=n)}i&&(U(o!==null),U(r!==void 0),await s(Math.floor(o*r)/r)),m.close(),this._synchronizer.closeTrack(e.id)})())}else{let t=new wL(n);r=t,this._trackPromises.push((async()=>{await this._started;let n=new pM(e),r={decoderConfig:await e.getDecoderConfig()??void 0},i=Number.isFinite(this._endTimestamp)?await n.getPacket(this._endTimestamp,{metadataOnly:!0})??void 0:void 0;for await(let a of n.packets(void 0,i,{verifyKeyPackets:!0})){if(this._canceled)return;let n=a.clone({timestamp:a.timestamp-this._startTimestamp,sideData:y===`discard`?{}:a.sideData});U(n.timestamp>=0),this._reportProgress(e.id,n.timestamp),await t.add(n,r),this._synchronizer.shouldWait(e.id,n.timestamp)&&await this._synchronizer.wait(n.timestamp)}t.close(),this._synchronizer.closeTrack(e.id)})())}this.output.addVideoTrack(r,{frameRate:t.frameRate,languageCode:Ck(e.languageCode)?e.languageCode:void 0,name:e.name??void 0,disposition:e.disposition,rotation:a}),this._addedCounts.video++,this._totalTrackCount++,this.utilizedTracks.push(e)}async _registerVideoSample(e,t,n,r){if(this._canceled)return;this._reportProgress(e.id,r.timestamp);let i;if(!t.process)i=[r];else{let e=t.process(r);e instanceof Promise&&(e=await e),Array.isArray(e)||(e=e===null?[]:[e]),i=e.map(e=>e instanceof Jj?e:typeof VideoFrame<`u`&&e instanceof VideoFrame?new Jj(e):new Jj(e,{timestamp:r.timestamp,duration:r.duration}))}for(let t of i){if(this._canceled)break;await n.add(t),this._synchronizer.shouldWait(e.id,t.timestamp)&&await this._synchronizer.wait(t.timestamp)}for(let e of i)e!==r&&e.close()}async _processAudioTrack(e,t){let n=e.codec;if(!n){this.discardedTracks.push({track:e,reason:`unknown_source_codec`});return}let r,i=e.numberOfChannels,a=e.sampleRate,o=await e.getFirstTimestamp(),s=t.numberOfChannels??i,c=t.sampleRate??a,l=s!==i||c!==a||o<this._startTimestamp||o>this._startTimestamp&&!this.output.format.supportsTimestampedMediaData,u=this.output.format.getSupportedAudioCodecs();if(!t.forceTranscode&&!t.bitrate&&!l&&u.includes(n)&&(!t.codec||t.codec===n)&&!t.process){let t=new kL(n);r=t,this._trackPromises.push((async()=>{await this._started;let n=new pM(e),r={decoderConfig:await e.getDecoderConfig()??void 0},i=Number.isFinite(this._endTimestamp)?await n.getPacket(this._endTimestamp,{metadataOnly:!0})??void 0:void 0;for await(let a of n.packets(void 0,i)){if(this._canceled)return;let n=a.clone({timestamp:a.timestamp-this._startTimestamp});U(n.timestamp>=0),this._reportProgress(e.id,n.timestamp),await t.add(n,r),this._synchronizer.shouldWait(e.id,n.timestamp)&&await this._synchronizer.wait(n.timestamp)}t.close(),this._synchronizer.closeTrack(e.id)})())}else{if(!await e.canDecode()){this.discardedTracks.push({track:e,reason:`undecodable_source_codec`});return}let n=null;t.codec&&(u=u.filter(e=>e===t.codec));let i=t.bitrate??_L,a=await bL(u,{numberOfChannels:t.process&&t.processedNumberOfChannels?t.processedNumberOfChannels:s,sampleRate:t.process&&t.processedSampleRate?t.processedSampleRate:c,bitrate:i});if(!a.some(e=>tA.includes(e))&&u.some(e=>tA.includes(e))&&(s!==RL||c!==zL)){let e=(await bL(u,{numberOfChannels:RL,sampleRate:zL,bitrate:i})).find(e=>tA.includes(e));e&&(l=!0,n=e,s=RL,c=zL)}else n=a[0]??null;if(n===null){this.discardedTracks.push({track:e,reason:`no_encodable_target_codec`});return}if(l)r=this._resampleAudio(e,t,n,s,c,i);else{let a=new jL({codec:n,bitrate:i});r=a,this._trackPromises.push((async()=>{await this._started;let n=new CM(e);for await(let r of n.samples(void 0,this._endTimestamp)){if(this._canceled){r.close();return}r.setTimestamp(r.timestamp-this._startTimestamp),await this._registerAudioSample(e,t,a,r),r.close()}a.close(),this._synchronizer.closeTrack(e.id)})())}}this.output.addAudioTrack(r,{languageCode:Ck(e.languageCode)?e.languageCode:void 0,name:e.name??void 0,disposition:e.disposition}),this._addedCounts.audio++,this._totalTrackCount++,this.utilizedTracks.push(e)}async _registerAudioSample(e,t,n,r){if(this._canceled)return;this._reportProgress(e.id,r.timestamp);let i;if(!t.process)i=[r];else{let e=t.process(r);if(e instanceof Promise&&(e=await e),Array.isArray(e)||(e=e===null?[]:[e]),!e.every(e=>e instanceof rM))throw TypeError(`The audio process function must return an AudioSample, null, or an array of AudioSamples.`);i=e}for(let t of i){if(this._canceled)break;await n.add(t),this._synchronizer.shouldWait(e.id,t.timestamp)&&await this._synchronizer.wait(t.timestamp)}for(let e of i)e!==r&&e.close()}_resampleAudio(e,t,n,r,i,a){let o=new jL({codec:n,bitrate:a});return this._trackPromises.push((async()=>{await this._started;let n=new WL({targetNumberOfChannels:r,targetSampleRate:i,startTime:this._startTimestamp,endTime:this._endTimestamp,onSample:async n=>{await this._registerAudioSample(e,t,o,n),n.close()}}),a=new CM(e).samples(this._startTimestamp,this._endTimestamp);for await(let e of a){if(this._canceled){e.close();return}await n.add(e),e.close()}await n.finalize(),o.close(),this._synchronizer.closeTrack(e.id)})()),o}_reportProgress(e,t){if(!this._computeProgress)return;U(this._totalDuration!==null),this._maxTimestamps.set(e,Math.max(t,this._maxTimestamps.get(e)));let n=_k(Math.min(...this._maxTimestamps.values())/this._totalDuration,0,1);n!==this._lastProgress&&(this._lastProgress=n,this.onProgress?.(n))}},VL=class extends Error{constructor(e=`Conversion has been canceled.`){super(e),this.name=`ConversionCanceledError`}},HL=5,UL=class{constructor(){this.maxTimestamps=new Map,this.resolvers=[]}computeMinAndMaybeResolve(){let e=1/0;for(let[,t]of this.maxTimestamps)e=Math.min(e,t);for(let t=0;t<this.resolvers.length;t++){let n=this.resolvers[t];n.timestamp-e<HL&&(n.resolve(),this.resolvers.splice(t,1),t--)}return e}shouldWait(e,t){return this.maxTimestamps.set(e,Math.max(t,this.maxTimestamps.get(e)??-1/0)),t-this.computeMinAndMaybeResolve()>=HL}wait(e){let{promise:t,resolve:n}=ok();return this.resolvers.push({timestamp:e,resolve:n}),t}closeTrack(e){this.maxTimestamps.delete(e),this.computeMinAndMaybeResolve()}},WL=class{constructor(e){this.sourceSampleRate=null,this.sourceNumberOfChannels=null,this.targetSampleRate=e.targetSampleRate,this.targetNumberOfChannels=e.targetNumberOfChannels,this.startTime=e.startTime,this.endTime=e.endTime,this.onSample=e.onSample,this.bufferSizeInFrames=Math.floor(this.targetSampleRate*5),this.bufferSizeInSamples=this.bufferSizeInFrames*this.targetNumberOfChannels,this.outputBuffer=new Float32Array(this.bufferSizeInSamples),this.bufferStartFrame=0,this.maxWrittenFrame=-1}doChannelMixerSetup(){U(this.sourceNumberOfChannels!==null);let e=this.sourceNumberOfChannels,t=this.targetNumberOfChannels;e===1&&t===2?this.channelMixer=(t,n)=>t[n*e]:e===1&&t===4?this.channelMixer=(t,n,r)=>t[n*e]*+(r<2):e===1&&t===6?this.channelMixer=(t,n,r)=>t[n*e]*+(r===2):e===2&&t===1?this.channelMixer=(t,n)=>{let r=n*e;return .5*(t[r]+t[r+1])}:e===2&&t===4||e===2&&t===6?this.channelMixer=(t,n,r)=>t[n*e+r]*+(r<2):e===4&&t===1?this.channelMixer=(t,n)=>{let r=n*e;return .25*(t[r]+t[r+1]+t[r+2]+t[r+3])}:e===4&&t===2?this.channelMixer=(t,n,r)=>{let i=n*e;return .5*(t[i+r]+t[i+r+2])}:e===4&&t===6?this.channelMixer=(t,n,r)=>{let i=n*e;return r<2?t[i+r]:r===2||r===3?0:t[i+r-2]}:e===6&&t===1?this.channelMixer=(t,n)=>{let r=n*e;return Math.SQRT1_2*(t[r]+t[r+1])+t[r+2]+.5*(t[r+4]+t[r+5])}:e===6&&t===2?this.channelMixer=(t,n,r)=>{let i=n*e;return t[i+r]+Math.SQRT1_2*(t[i+2]+t[i+r+4])}:e===6&&t===4?this.channelMixer=(t,n,r)=>{let i=n*e;return r<2?t[i+r]+Math.SQRT1_2*t[i+2]:t[i+r+2]}:this.channelMixer=(t,n,r)=>r<e?t[n*e+r]:0}ensureTempBufferSize(e){let t=this.tempSourceBuffer.length;for(;t<e;)t*=2;if(t!==this.tempSourceBuffer.length){let e=new Float32Array(t);e.set(this.tempSourceBuffer),this.tempSourceBuffer=e}}async add(e){this.sourceSampleRate===null&&(this.sourceSampleRate=e.sampleRate,this.sourceNumberOfChannels=e.numberOfChannels,this.tempSourceBuffer=new Float32Array(this.sourceSampleRate*this.sourceNumberOfChannels),this.doChannelMixerSetup());let t=e.numberOfFrames*e.numberOfChannels;this.ensureTempBufferSize(t);let n=e.allocationSize({planeIndex:0,format:`f32`}),r=new Float32Array(this.tempSourceBuffer.buffer,0,n/4);e.copyTo(r,{planeIndex:0,format:`f32`});let i=e.timestamp-this.startTime,a=e.numberOfFrames/this.sourceSampleRate,o=Math.min(i+a,this.endTime-this.startTime),s=Math.floor(i*this.targetSampleRate),c=Math.ceil(o*this.targetSampleRate);for(let t=s;t<c;t++){if(t<this.bufferStartFrame)continue;for(;t>=this.bufferStartFrame+this.bufferSizeInFrames;)await this.finalizeCurrentBuffer(),this.bufferStartFrame+=this.bufferSizeInFrames;let n=t-this.bufferStartFrame;U(n<this.bufferSizeInFrames);let a=(t/this.targetSampleRate-i)*this.sourceSampleRate,o=Math.floor(a),s=Math.ceil(a),c=a-o;for(let t=0;t<this.targetNumberOfChannels;t++){let i=0,a=0;o>=0&&o<e.numberOfFrames&&(i=this.channelMixer(r,o,t)),s>=0&&s<e.numberOfFrames&&(a=this.channelMixer(r,s,t));let l=i+c*(a-i),u=n*this.targetNumberOfChannels+t;this.outputBuffer[u]+=l}this.maxWrittenFrame=Math.max(this.maxWrittenFrame,n)}}async finalizeCurrentBuffer(){if(this.maxWrittenFrame<0)return;let e=(this.maxWrittenFrame+1)*this.targetNumberOfChannels,t=new Float32Array(e);t.set(this.outputBuffer.subarray(0,e));let n=this.bufferStartFrame/this.targetSampleRate,r=new rM({format:`f32`,sampleRate:this.targetSampleRate,numberOfChannels:this.targetNumberOfChannels,timestamp:n,data:t});await this.onSample(r),this.outputBuffer.fill(0),this.maxWrittenFrame=-1}finalize(){return this.finalizeCurrentBuffer()}};function GL(){return typeof AudioEncoder<`u`}async function KL(e,t){let n=new MP({source:new OP(e),formats:EP}),r=new $I,i=new FL({format:new cL({fastStart:`in-memory`}),target:r});try{let a=await BL.init({input:n,output:i,video:{discard:!0},audio:{codec:`aac`,bitrate:_L}});t&&(a.onProgress=t),await a.execute();let o=r.buffer;if(!o)throw Error(`Audio processing produced no output`);let s=e.name.replace(/\.[^.]+$/,``);return{file:new File([o],`${s}.m4a`,{type:`audio/mp4`})}}finally{n.dispose()}}var qL={isSupported:GL,processToFile:KL},JL=1920,YL=1080,XL=640,ZL=32;function QL(){return typeof VideoEncoder<`u`}async function $L(e){let t=new MP({source:new OP(e),formats:EP});try{let e=await t.getPrimaryVideoTrack();if(!e)return{};let n=e.displayWidth,r=e.displayHeight,i=e.rotation,a=await t.computeDuration(),o=s(a),c=Math.min(a*.1,3),l=await new bM(e).getCanvas(c);if(!l)return{sourceWidth:n,sourceHeight:r,rotation:i,durationSeconds:o};let u=l.canvas,d=u.width,f=u.height,p=Math.min(XL/d,1),m=Math.round(d*p),h=Math.round(f*p),g=document.createElement(`canvas`);g.width=m,g.height=h;let _=g.getContext(`2d`);if(!_)return{sourceWidth:n,sourceHeight:r};_.drawImage(u,0,0,m,h);let v=await new Promise(e=>{g.toBlob(t=>e(t??void 0),`image/webp`,.8)}),y=Math.min(ZL/d,ZL/f,1),b=Math.max(Math.round(d*y),1),x=Math.max(Math.round(f*y),1),S=document.createElement(`canvas`);S.width=b,S.height=x;let C=S.getContext(`2d`);return C?(C.drawImage(u,0,0,b,x),{poster:v,blurhash:AC(C.getImageData(0,0,b,x).data,b,x,4,3),sourceWidth:n,sourceHeight:r,rotation:i,durationSeconds:o}):{poster:v,sourceWidth:n,sourceHeight:r}}catch{return{}}finally{t.dispose()}}async function eR(e,t){let{poster:n,blurhash:r,sourceWidth:i,sourceHeight:a,rotation:o,durationSeconds:s}=await $L(e),c=i||JL,l=a||YL;if(i&&a){let e=Math.max(i,a),t=Math.min(i,a),n=Math.min(JL/e,YL/t,1);c=Math.round(i*n),l=Math.round(a*n)}c+=c%2,l+=l%2;let u=new MP({source:new OP(e),formats:EP}),d=new $I,f=new FL({format:new cL({fastStart:`in-memory`}),target:d});try{let i=await BL.init({input:u,output:f,video:{codec:`avc`,width:c,height:l,fit:`contain`,bitrate:_L},audio:{codec:`aac`}});t&&(i.onProgress=t),await i.execute();let a=d.buffer;if(!a)throw Error(`Video processing produced no output`);let p=e.name.replace(/\.[^.]+$/,``),m=new File([a],`${p}.mp4`,{type:`video/mp4`}),h=await iR(m),g=Math.abs(h.width-c)<=2&&Math.abs(h.height-l)<=2;return o&&!g&&(rR(a),m=new File([a],`${p}.mp4`,{type:`video/mp4`}),h=await iR(m)),{file:m,width:h.width,height:h.height,durationSeconds:s,poster:n,blurhash:r}}finally{u.dispose()}}var tR=[65536,0,0,0,65536,0,0,0,1073741824];function nR(e,t,n,r){let i=t;for(;i+8<=n;){let t=e.getUint32(i),a=String.fromCharCode(e.getUint8(i+4),e.getUint8(i+5),e.getUint8(i+6),e.getUint8(i+7));if(t===0&&(t=n-i),t<8||i+t>n)break;r(i,t,a),(a===`moov`||a===`trak`||a===`mdia`||a===`edts`)&&nR(e,i+8,i+t,r),i+=t}}function rR(e){let t=new DataView(e);nR(t,0,e.byteLength,(n,r,i)=>{if(i!==`tkhd`)return;let a=n+8,o=a+(t.getUint8(a)===0?40:52);if(o+36>e.byteLength)return;let s=!0;for(let e=0;e<9;e++)if(t.getInt32(o+e*4)!==tR[e]){s=!1;break}if(!s)for(let e=0;e<9;e++)t.setInt32(o+e*4,tR[e])})}function iR(e){return new Promise((t,n)=>{let r=URL.createObjectURL(e),i=document.createElement(`video`);i.preload=`metadata`,i.onloadedmetadata=()=>{URL.revokeObjectURL(r),t({width:i.videoWidth,height:i.videoHeight})},i.onerror=()=>{URL.revokeObjectURL(r),n(Error(`Failed to probe transcoded video dimensions`))},i.src=r})}var aR={isSupported:QL,processToFile:eR};function oR(){return document.querySelector(`jant-compose-dialog`)}function sR(){return document.querySelector(`[data-page='collection'][data-collection-id]`)?.dataset.collectionId||void 0}function cR(e=document){if(e===document){let e=document.querySelector(`[data-page='post'] article[data-post]:hover`);if(e)return e}return e.querySelector(`[data-post-current] article[data-post]`)||e.querySelector(`[data-post-view] article[data-post]`)||(e===document?document.querySelector(`article[data-post]:hover`):null)}function lR(e){let t=e.closest(`[data-post-view]`)?.dataset.postViewId;if(t)return{kind:`post-view`,id:t};let n=e.closest(`[data-timeline-item]`),r=n?.dataset.threadRootId??e.dataset.threadRootId??e.dataset.postId;if(n&&r)return{kind:`timeline-item`,id:r};let i=e.dataset.postId;return i?{kind:`post-card`,id:i}:null}function uR(e){let t=e.cloneNode(!0);t.querySelector(`[data-post-meta]`)?.remove(),t.querySelector(`.post-status-badges`)?.remove();let n=e.querySelector(`time.dt-published`);return{contentHtml:t.innerHTML,dateText:n?.textContent?.trim()??``}}function dR(e){let t=e.dataset.format;if(t===`note`||t===`link`||t===`quote`)return t}async function fR(e){await oR()?.openNew(e)}async function pR(e){let t=e.dataset.postId;if(!t)return;let n=oR();if(!n)return;let r=e.dataset.threadRootId??t,i=dR(e);await n.openReply(t,uR(e),r,lR(e)??void 0,i?{initialFormat:i}:void 0)}function mR(e){return e instanceof globalThis.Element?e.closest(`jant-compose-editor`):null}function hR(e){return e instanceof globalThis.Element?e.closest(`jant-compose-dialog`):null}async function gR(e){let t=await fetch(e,{headers:{Accept:`text/html`}});return t.ok?t.text():null}async function _R(e){try{let t=document.querySelector(`[data-timeline-item][data-thread-root-id="${e}"]`)?.querySelector(`[data-timeline-item-content]`);if(!t)return!1;let n=await gR(`/_/timeline-item/${encodeURIComponent(e)}`);return n?(t.innerHTML=n,y(t),!0):!1}catch{return!1}}async function vR(e){try{let t=document.querySelector(`article[data-post-id="${e}"]`)?.closest(`[data-timeline-item]`),n=await gR(`/_/post-card/${encodeURIComponent(e)}`);if(!n)return!1;if(t){let e=t.querySelector(`[data-timeline-item-content]`);return e?(e.innerHTML=n,y(e),!0):!1}let r=document.querySelector(`article[data-post-id="${e}"]`);if(!r)return!1;r.outerHTML=n;let i=document.querySelector(`article[data-post-id="${e}"]`);return i&&y(i),!0}catch{return!1}}async function yR(e){try{let t=document.querySelector(`[data-post-view][data-post-view-id="${e}"]`);if(!t)return!1;let n=await gR(`/_/post-view/${encodeURIComponent(e)}`);if(!n)return!1;t.outerHTML=n;let r=document.querySelector(`[data-post-view][data-post-view-id="${e}"]`);return r&&y(r),!0}catch{return!1}}async function bR(e){return!e.replyRefreshKind||!e.replyRefreshId?!1:e.replyRefreshKind===`timeline-item`?_R(e.replyThreadRootId??e.replyRefreshId):e.replyRefreshKind===`post-view`?yR(e.replyRefreshId):vR(e.replyRefreshId)}var xR=new Map,SR=new Set,CR=new Map;function wR(e){return new Promise(t=>{let n=setTimeout(()=>{a(),t(null)},3e3),r=URL.createObjectURL(e),i=document.createElement(`video`);i.muted=!0,i.playsInline=!0,i.preload=`auto`;function a(){clearTimeout(n),i.removeAttribute(`src`),i.load(),URL.revokeObjectURL(r)}i.onloadeddata=()=>{try{let e=i.videoWidth,n=i.videoHeight;if(!e||!n){a(),t(null);return}let r=Math.min(640/e,1),o=Math.round(e*r),s=Math.round(n*r),c=document.createElement(`canvas`);c.width=o,c.height=s;let l=c.getContext(`2d`);if(!l){a(),t(null);return}l.drawImage(i,0,0,o,s),c.toBlob(e=>{a(),t(e)},`image/webp`,.6)}catch{a(),t(null)}},i.onerror=()=>{a(),t(null)},i.src=r})}async function TR(e,t,n){try{let r,i,o,s,c,l,u;if(e.type.startsWith(`video/`)){if(!aR.isSupported())return n?.updateAttachmentStatus(t,`error`,null,`Your browser doesn't support video processing. Use Chrome or Edge to upload videos.`),null;wR(e).then(e=>{e&&n?.updateAttachmentPoster(t,e)}),n?.updateAttachmentStatus(t,`processing`,null,null);let a=await aR.processToFile(e,e=>{n?.updateAttachmentProgress(t,e)});r=a.file,i=a.width,o=a.height,s=a.durationSeconds,c=a.blurhash,u=a.poster,u&&n?.updateAttachmentPoster(t,u)}else if(e.type.startsWith(`audio/`)){if(!qL.isSupported())return n?.updateAttachmentStatus(t,`error`,null,`Your browser doesn't support audio processing. Use Chrome or Edge to upload audio.`),null;try{l=await NC(e)}catch{}n?.updateAttachmentStatus(t,`processing`,null,null),r=(await qL.processToFile(e,e=>{n?.updateAttachmentProgress(t,e)})).file}else if(e.type.startsWith(`image/`)||/\.heic$/i.test(e.name)||/\.heif$/i.test(e.name)){let s=e;try{let{isHeic:a,heicTo:c}=await kO(async()=>{let{isHeic:e,heicTo:t}=await import(`./chunks/heic-to-DUUaO23q.js`);return{isHeic:e,heicTo:t}},[]);if(await a(e)){n?.updateAttachmentStatus(t,`processing`,null,null);let r=await c({blob:e,type:`image/jpeg`,quality:.92});s=new File([r],e.name.replace(/\.heic$/i,`.jpg`),{type:`image/jpeg`}),n?.updateAttachmentPreview(t,s)}let l=await re.processToFile(s);r=l.file,i=l.width,o=l.height}catch{return n?.removeAttachment(t),a(`Image format not supported.`,`error`),null}}else r=e;if(n?.updateAttachmentStatus(t,`uploading`,null,null),!e.type.startsWith(`video/`)){let e=await PC(r);i??=e.width,o??=e.height,c??=e.blurhash,l??=e.waveform,!u&&e.poster&&(u=e.poster,n?.updateAttachmentPoster(t,u))}let d,f;if(sS(e.type)===`text`)try{let e=await r.text(),t=e.replace(/\s+/g,` `).trim();f=e.length,d=t.length<=100?t:t.slice(0,100)+`…`}catch{}let p=await GC(r,{width:i,height:o,durationSeconds:s,blurhash:c,waveform:l,poster:u,summary:d,chars:f},e=>{n?.updateAttachmentProgress(t,e)});return n?.updateAttachmentStatus(t,`done`,p.id,null),CR.set(t,p.id),p.id}catch(e){let r=e instanceof Error?e.message:`Upload failed`;return n?.updateAttachmentStatus(t,`error`,null,r),n||a(r,`error`),null}}document.addEventListener(`jant:attachment-removed`,e=>{let{clientId:t,mediaId:n}=e.detail;CR.delete(t),n?fetch(`/api/upload/${n}`,{method:`DELETE`}).catch(()=>{}):SR.add(t)}),document.addEventListener(`jant:files-selected`,e=>{let t=e,n=mR(t.target);for(let{file:e,clientId:r}of t.detail.files){let t=TR(e,r,n).then(e=>SR.has(r)?(SR.delete(r),e&&fetch(`/api/upload/${e}`,{method:`DELETE`}).catch(()=>{}),null):e);xR.set(r,t),t.finally(()=>xR.delete(r))}}),document.addEventListener(`click`,e=>{let t=e.target.closest(`[data-reply-trigger]`);if(!t)return;let n=t.closest(`article[data-post]`);n&&pR(n)});function ER(e,t){let n=e.format===`quote`,r=e.format===`link`,i=!!e.editPostId,a=e=>e||void 0,o=e=>e||null;return{format:e.format,title:n?void 0:i?o(e.title):a(e.title),body:i?o(e.body):a(e.body),url:r?i?o(e.url):a(e.url):i?null:void 0,sourceName:n?i?o(e.quoteAuthor):a(e.quoteAuthor):void 0,sourceUrl:n?i?o(e.url):a(e.url):void 0,quoteText:n?i?o(e.quoteText):a(e.quoteText):i?null:void 0,slug:e.slug||void 0,status:e.status,publishedAt:e.status===`published`?e.publishedAt:void 0,visibility:e.visibility||void 0,rating:i?e.rating>0?e.rating:null:e.rating||void 0,collectionIds:e.collectionIds,attachments:t.length>0?t:void 0,replyToId:e.replyToId||void 0,quietReply:e.quietReply||void 0}}function DR(e){return JSON.stringify(e.bodyJson)!==JSON.stringify(e.originalBodyJson??null)}function OR(e,t){let n=[];for(let r of e.attachments){if(r.type===`media`){let e=r.mediaId??t.get(r.clientId)??CR.get(r.clientId);if(!e)continue;n.push({type:`media`,mediaId:e,alt:r.alt});continue}if(r.mediaId&&!DR(r)){n.push({type:`media`,mediaId:r.mediaId});continue}n.push({type:`text`,contentFormat:`markdown`,content:AT(JSON.stringify(r.bodyJson)),summary:r.summary})}return n}document.addEventListener(`jant:compose-submit-deferred`,async e=>{let t=e,n=t.detail,r=hR(t.target)??document.querySelector(`jant-compose-dialog`),o=!!r?.pageMode,s=r?.labels,c=s?.uploading??`Uploading...`,l=n.threadPosts?n.threadPosts.some(e=>e.body.includes(`"blob:`)):n.body.includes(`"blob:`),u=n.pendingAttachments.length>0||l,f=s?.published??`Published!`,p=s?.view??`View`;u&&d(`compose-deferred`,c);let h=(e,t=`success`)=>{u?_(`compose-deferred`,e,t):a(e,t)},g=()=>{!o||!r||(r.reset(),r.updateComplete.then(()=>{r.querySelector(`jant-compose-editor`)?.focusInput()}))},v=()=>{!o||!r||(r.loading=!1)},y=()=>{if(te){n.editPostId&&r?.clearEditDraftFromStorage?.(n.editPostId);return}r?.clearLocalDraftFromStorage?.()},b=async()=>{if(!(!r||o)){if(te&&n.editPostId){if(typeof r.openEdit!=`function`)return;await r.openEdit(n.editPostId);return}if(n.replyToId){if(typeof r.openReply!=`function`)return;await r.openReply(n.replyToId,void 0,n.replyThreadRootId,n.replyRefreshKind&&n.replyRefreshId?{kind:n.replyRefreshKind,id:n.replyRefreshId}:void 0,{restoreDraft:!0,initialFormat:n.format});return}typeof r.openNew==`function`&&await r.openNew({restoreDraft:!0})}},x=async e=>{v(),await b(),h(e,`error`)},S=async()=>{await r?.refreshCollections()},C=(e,t)=>{m(e,`success`,t)},ee=()=>!o||!r||!r.consumePageLeaveRequest()?!1:(r.preparePageLeave(),globalThis.location.assign(r.closeHref||i(`/`)),!0),te=!!n.editPostId,ne=!!(n.threadPosts&&n.threadPosts.length>=2),re=null;try{let e=n.pendingAttachments.map(e=>e.clientId),t=e.map(e=>xR.get(e)??Promise.resolve(null)),a=await Promise.all(t);if(a.filter((t,n)=>t===null&&!CR.has(e[n]??``)).length>0)if(n.status===`published`&&!te)re=`upload`;else{await x(`Upload failed. Post not created.`);return}let c=new Map;for(let t=0;t<e.length;t++){let n=e[t],r=a[t];n&&r&&c.set(n,r)}if(ne&&n.threadPosts){let e=n.threadPosts,t=re?`draft`:n.status,i=(await Promise.all(e.map(async e=>{let n=e.body;if(n.includes(`"blob:`))try{let e=await $C(JSON.parse(n));n=e?JSON.stringify(e):``}catch{}return{...e,body:n,status:t}}))).map(e=>ER(e,OR(e,c))),a={posts:i};te&&n.editPostId&&(a.replaceThreadId=n.editPostId);let l=await fetch(`/compose/thread`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify(a)});if(!l.ok){if(n.status===`published`&&!re){let e={posts:i.map(e=>({...e,status:`draft`}))};if((await fetch(`/compose/thread`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify(e)})).ok){re=`server`,y();let e=s?.publishFailedDraft??`Couldn't publish. Saved as draft.`;await S(),ee()||g(),h(e);return}}await x(w(await le(l),`error`)??`Something went wrong`);return}if(re===`upload`){y();let e=s?.uploadFailedDraft??`Some uploads failed. Saved as draft.`;await S(),g(),h(e);return}let u=await le(l),d=w(u,`status`),m=w(u,`permalink`),_=w(u,`toast`);d===`published`?(y(),o?(await S(),g(),h(f)):(C(f,m?{label:p,href:m}:void 0),globalThis.location.reload())):(y(),await S(),ee()||g(),h(_??`Draft saved.`),kR(r,`draft`));return}let u=OR(n,c);if(l)try{let e=await $C(JSON.parse(n.body));n.body=e?JSON.stringify(e):``}catch{}let d=te?`/api/posts/${n.editPostId}`:`/compose`,m=te?`PUT`:`POST`,_=ER({...n,status:re?`draft`:n.status},u),v=await fetch(d,{method:m,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify(_)});if(!v.ok){if(n.status===`published`&&!te&&!re){let e={..._,status:`draft`},t=await fetch(d,{method:m,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify(e)});if(t.ok){re=`server`,y();let e=await le(t),n=s?.publishFailedDraft??`Couldn't publish. Saved as draft.`;await S(),ee()||g(),h(n);let r=w(e,`toast`);r&&h(r);return}}await x(w(await le(v),`error`)??`Something went wrong`);return}if(te){y();let e=await le(v),t=n.editPostId??``,r=w(e,`slug`),a=r?i(`/${r}`):null;if(o)a&&a!==globalThis.location.pathname?(C(`Post updated.`),globalThis.location.assign(a)):t&&await yR(t)?h(`Post updated.`):(C(`Post updated.`),globalThis.location.assign(globalThis.location.pathname));else if(t)if(document.querySelector(`[data-post-view][data-post-view-id="${t}"]`))await yR(t)?h(`Post updated.`):(C(`Post updated.`),globalThis.location.reload());else{let e=document.querySelector(`article[data-post-id="${t}"]`)?.closest(`[data-timeline-item]`)?.dataset.threadRootId;(e?await _R(e):await vR(t))?h(`Post updated.`):(C(`Post updated.`),globalThis.location.reload())}else C(`Post updated.`),globalThis.location.reload();return}if(re===`upload`){y();let e=s?.uploadFailedDraft??`Some uploads failed. Saved as draft.`;await S(),g(),h(e);return}let b=await le(v),ie=w(b,`status`),ae=w(b,`permalink`),oe=w(b,`toast`);if(ie===`published`){if(y(),o)await S(),g(),h(f);else if(n.replyToId){if(await S(),!await bR(n)){C(f,ae?{label:p,href:ae}:void 0),globalThis.location.reload();return}h(f)}else C(f,ae?{label:p,href:ae}:void 0),globalThis.location.reload();return}else y(),await S(),ee()||g(),h(oe??`Draft saved.`),kR(r,`draft`)}catch{await x(`Something went wrong`)}});function kR(e,t){(e??document).dispatchEvent(new CustomEvent(`jant:compose-submit-complete`,{bubbles:!0,detail:{status:t}}))}var AR=`.compose-prompt`,jR=`.compose-prompt-trigger`,MR=`jant.composeOpenShortcutDiscovery`,NR=`/api/settings/discovery/compose-open-shortcut`,PR=350,FR=1800,IR=3,LR=`compose-prompt-discovery-visible`,RR=!1,zR=null,BR=null,VR=null;function HR(){try{return globalThis.localStorage!==void 0}catch{return!1}}function UR(){if(!HR())return{shownCount:0,completed:!1};let e=globalThis.localStorage.getItem(MR);if(!e)return{shownCount:0,completed:!1};try{let t=JSON.parse(e);return{shownCount:typeof t.shownCount==`number`&&t.shownCount>=0?t.shownCount:0,completed:t.completed===!0}}catch{return globalThis.localStorage.removeItem(MR),{shownCount:0,completed:!1}}}function WR(e){if(HR())try{globalThis.localStorage.setItem(MR,JSON.stringify(e))}catch{}}function GR(){zR!==null&&(clearTimeout(zR),zR=null)}function KR(){BR!==null&&(clearTimeout(BR),BR=null)}function qR(){return typeof globalThis.matchMedia==`function`&&globalThis.matchMedia(`(min-width: 700px)`).matches}function JR(e){return UR().completed?!0:e?.dataset.composeOpenShortcutDiscovered===`true`}function YR(e){e&&(e.dataset.composeOpenShortcutDiscovered=`true`,e.classList.remove(LR)),document.querySelectorAll(AR).forEach(e=>{e.dataset.composeOpenShortcutDiscovered=`true`,e.classList.remove(LR)})}function XR(){let e=UR();e.completed||RR||e.shownCount>=IR||(RR=!0,WR({...e,shownCount:e.shownCount+1}))}function ZR(e){return!qR()||JR(e)?!1:UR().shownCount<IR||RR}function QR(e){GR(),KR(),e?e.classList.remove(LR):VR&&VR.classList.remove(LR),(!e||e===VR)&&(VR=null)}function $R(e){ZR(e)&&(QR(VR),VR=e,e.classList.add(LR),XR(),BR=setTimeout(()=>{QR(e)},FR))}function ez(e){ZR(e)&&(e.classList.contains(LR)||(GR(),zR=setTimeout(()=>{$R(e)},PR)))}function tz(e){if(e.dataset.composeOpenShortcutDiscoveryBound===`true`)return;let t=e.querySelector(jR);t&&(e.dataset.composeOpenShortcutDiscoveryBound=`true`,t.addEventListener(`pointerenter`,()=>{ez(e)}),t.addEventListener(`pointerleave`,()=>{QR(e)}),t.addEventListener(`focusin`,()=>{ez(e)}),t.addEventListener(`focusout`,()=>{QR(e)}))}function nz(e=document){e.querySelectorAll(AR).forEach(e=>tz(e))}function rz(){let e=UR();e.completed||WR({shownCount:Math.max(e.shownCount,IR),completed:!0}),YR(VR),QR(VR),typeof globalThis.fetch==`function`&&globalThis.fetch(NR,{method:`POST`,headers:{Accept:`application/json`},credentials:`same-origin`}).catch(()=>{})}document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,()=>{nz()}):nz();var iz=[`input`,`textarea`,`select`,`button`,`a[href]`,`[contenteditable='']`,`[contenteditable='true']`,`[role='textbox']`,`.ProseMirror`].join(`, `);function az(e){return e instanceof globalThis.Element&&e.closest(iz)!==null}function oz(e){if(e.defaultPrevented||e.isComposing||e.repeat||e.metaKey||e.ctrlKey||e.altKey||!oR()||document.querySelector(`[data-page="compose"]`)||document.querySelector(`dialog[open]`))return!0;let t=document.activeElement;return az(e.target)||t!==e.target&&az(t)}async function sz(e,t,n){try{if(!(await fetch(`/api/posts/${e}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({featured:t})})).ok)throw Error();t?n.setAttribute(`data-post-featured`,``):n.removeAttribute(`data-post-featured`),a(t?`Added to Featured.`:`Removed from Featured.`)}catch{a(`Could not update post. Try again.`,`error`)}}document.addEventListener(`keydown`,e=>{let t=e.key.toLowerCase();if(t!==`n`&&t!==`l`&&t!==`q`&&t!==`r`&&t!==`e`&&t!==`c`&&t!==`f`||oz(e))return;if(t===`n`||t===`l`||t===`q`){e.preventDefault(),rz();let n=sR(),r=t===`l`?`link`:t===`q`?`quote`:void 0;fR({...n?{collectionId:n}:void 0,...r?{initialFormat:r}:void 0});return}let n=cR();if(n){if(t===`r`){e.preventDefault(),pR(n);return}if(t===`e`){let t=n.dataset.postId;if(!t)return;e.preventDefault();let r=oR();r&&r.openEdit(t);return}if(t===`c`){e.preventDefault();let t=document.querySelector(`jant-post-menu`);t&&t.openCollectionsForPost(n);return}if(t===`f`){let t=n.dataset.postId;if(!t)return;e.preventDefault(),sz(t,!n.hasAttribute(`data-post-featured`),n);return}}});var cz=[`en`,`zh-Hans`,`zh-Hant`];function lz(e){return typeof e==`string`&&cz.includes(e)}function uz(e){let t=e.trim();if(!t)return`en`;let n;try{n=new Intl.Locale(t)}catch{return`en`}if(lz(n.baseName))return n.baseName;if(n.language===`zh`){let e=n.region;return n.script===`Hant`||e===`TW`||e===`HK`||e===`MO`?`zh-Hant`:`zh-Hans`}return`en`}var dz={en:1,"zh-Hans":1,"zh-Hant":1},fz=`en.zh-Hans.zh-Hant.ja.ko.es.fr.de.it.pt.ru.ar.hi.bn.ur.tr.vi.th.id.fa.he.nl.pl.sv.da.no.fi.cs.hu.el.ro.uk.en-GB.en-US.fr-CA.pt-BR.es-MX.zh-CN.zh-TW.zh-HK`.split(`.`),pz=null;function mz(e){let t=e,n=e;try{let n=new Intl.DisplayNames([e],{type:`language`}).of(e);typeof n==`string`&&n.length>0&&(t=n)}catch{}try{let t=new Intl.DisplayNames([`en`],{type:`language`}).of(e);typeof t==`string`&&t.length>0&&(n=t)}catch{}return{tag:e,native:t,english:n,coverage:hz(e)}}function hz(e){let t=uz(e);if(t===`en`){let t;try{t=new Intl.Locale(e).language}catch{return 0}if(t!==`en`)return 0}return dz[t]}function gz(){return pz||(pz=fz.map(mz),pz)}function _z(e){let t=e.trim();return gz().find(e=>e.tag===t)||mz(t)}var vz=class extends c{static properties={labels:{type:Object},timezones:{type:Array},cjkFonts:{type:Array,attribute:`cjk-fonts`},siteNameFallback:{type:String,attribute:`sitename-fallback`},siteDescriptionFallback:{type:String,attribute:`sitedescription-fallback`},demoMode:{type:Boolean,attribute:`demo-mode`},mainFeedUrl:{type:String,attribute:`main-feed-url`},latestFeedUrl:{type:String,attribute:`latest-feed-url`},featuredFeedUrl:{type:String,attribute:`featured-feed-url`},_siteName:{state:!0},_siteDescription:{state:!0},_siteFooter:{state:!0},_origSite:{state:!0},_siteDirty:{state:!0},_siteLoading:{state:!0},_siteLanguage:{state:!0},_localeOpen:{state:!0},_localeQuery:{state:!0},_cjkSerifFont:{state:!0},_timeZone:{state:!0},_origLocale:{state:!0},_localeDirty:{state:!0},_localeLoading:{state:!0},_mainRssFeed:{state:!0},_origMainRssFeed:{state:!0},_feedDirty:{state:!0},_feedLoading:{state:!0},_showJantBrandingOnHome:{state:!0},_origShowJantBrandingOnHome:{state:!0},_homeLoading:{state:!0},_noindex:{state:!0},_origNoindex:{state:!0},_searchLoading:{state:!0}};_descEditor=null;_footerEditor=null;createRenderRoot(){return this.innerHTML=``,this}constructor(){super(),this.labels={},this.timezones=[],this.cjkFonts=[],this.siteNameFallback=``,this.siteDescriptionFallback=``,this.demoMode=!1,this.mainFeedUrl=`/feed`,this.latestFeedUrl=`/feed/latest`,this.featuredFeedUrl=`/feed/featured`,this._siteName=``,this._siteDescription=``,this._siteFooter=``,this._origSite={siteName:``,siteDescription:``,siteFooter:``},this._siteDirty=!1,this._siteLoading=!1,this._siteLanguage=`en`,this._localeOpen=!1,this._localeQuery=``,this._cjkSerifFont=`off`,this._timeZone=`UTC`,this._origLocale={siteLanguage:`en`,cjkSerifFont:`off`,timeZone:`UTC`},this._localeDirty=!1,this._localeLoading=!1,this._mainRssFeed=`featured`,this._origMainRssFeed=`featured`,this._feedDirty=!1,this._feedLoading=!1,this._noindex=!1,this._origNoindex=!1,this._showJantBrandingOnHome=!1,this._origShowJantBrandingOnHome=!1,this._homeLoading=!1,this._searchLoading=!1}connectedCallback(){super.connectedCallback(),document.addEventListener(`click`,this._onLocalePickerDocumentClick),document.addEventListener(`keydown`,this._onLocalePickerKeydown)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(`click`,this._onLocalePickerDocumentClick),document.removeEventListener(`keydown`,this._onLocalePickerKeydown),this._descEditor?.destroy(),this._descEditor=null,this._footerEditor?.destroy(),this._footerEditor=null}initData(e){this._siteName=e.siteName,this._siteDescription=e.siteDescription,this._siteFooter=e.siteFooter,this._siteLanguage=e.siteLanguage,this._cjkSerifFont=e.cjkSerifFont,this._timeZone=e.timeZone,this._origLocale={siteLanguage:e.siteLanguage,cjkSerifFont:e.cjkSerifFont,timeZone:e.timeZone},this._mainRssFeed=e.mainRssFeed,this._origMainRssFeed=e.mainRssFeed,this._showJantBrandingOnHome=e.showJantBrandingOnHome,this._origShowJantBrandingOnHome=e.showJantBrandingOnHome,this._noindex=e.noindex,this._origNoindex=e.noindex,this.updateComplete.then(()=>{this._initEditors(),this._origSite={siteName:e.siteName,siteDescription:this._siteDescription,siteFooter:this._siteFooter}})}sectionSaved(e){e===`site`?(this._origSite={siteName:this._siteName,siteDescription:this._siteDescription,siteFooter:this._siteFooter},this._siteDirty=!1,this._siteLoading=!1):e===`language-time`?(this._origLocale={siteLanguage:this._siteLanguage,cjkSerifFont:this._cjkSerifFont,timeZone:this._timeZone},this._localeDirty=!1,this._localeLoading=!1):e===`feeds`?(this._origMainRssFeed=this._mainRssFeed,this._feedDirty=!1,this._feedLoading=!1):e===`home`?(this._origShowJantBrandingOnHome=this._showJantBrandingOnHome,this._homeLoading=!1):e===`search`&&(this._origNoindex=this._noindex,this._searchLoading=!1)}sectionError(e){e===`site`?this._siteLoading=!1:e===`language-time`?this._localeLoading=!1:e===`feeds`?this._feedLoading=!1:e===`home`?(this._showJantBrandingOnHome=this._origShowJantBrandingOnHome,this._homeLoading=!1):e===`search`&&(this._noindex=this._origNoindex,this._searchLoading=!1)}_initEditors(){this._initDescEditor(),this._initFooterEditor()}_initDescEditor(){let e=this.querySelector(`[data-settings-desc-editor]`);if(!e||this._descEditor)return;this._descEditor=NT({element:e,placeholder:this.siteDescriptionFallback,content:this._siteDescription||void 0,onUpdate:e=>{this._siteDescription=e,this._syncSiteDirty()}}),this._siteDescription=MT(this._descEditor.getJSON());let t=e.querySelector(`.ProseMirror`);t&&(t.style.outline=`none`,t.style.minHeight=`3rem`)}_initFooterEditor(){let e=this.querySelector(`[data-settings-footer-editor]`);if(!e||this._footerEditor)return;this._footerEditor=NT({element:e,content:this._siteFooter||void 0,onUpdate:e=>{this._siteFooter=e,this._syncSiteDirty()}}),this._siteFooter=MT(this._footerEditor.getJSON());let t=e.querySelector(`.ProseMirror`);t&&(t.style.outline=`none`,t.style.minHeight=`6rem`)}_syncSiteDirty(){this._siteDirty=this._siteName!==this._origSite.siteName||this._siteDescription!==this._origSite.siteDescription||this._siteFooter!==this._origSite.siteFooter}_saveSite(){this._siteLoading||!this._siteDirty||(this._siteLoading=!0,this.dispatchEvent(new CustomEvent(`jant:settings-save`,{bubbles:!0,detail:{endpoint:`/settings/general`,data:{siteName:this._siteName,siteDescription:this._siteDescription,siteFooter:this._siteFooter},section:`site`}})))}_syncLocaleDirty(){this._localeDirty=this._siteLanguage!==this._origLocale.siteLanguage||this._cjkSerifFont!==this._origLocale.cjkSerifFont||this._timeZone!==this._origLocale.timeZone}_saveLocale(){this._localeLoading||!this._localeDirty||(this._localeLoading=!0,this.dispatchEvent(new CustomEvent(`jant:settings-save`,{bubbles:!0,detail:{endpoint:`/settings/general/language-time`,data:{siteLanguage:this._siteLanguage,cjkSerifFont:this._cjkSerifFont,timeZone:this._timeZone},section:`language-time`}})))}_filteredLocaleEntries(){let e=gz(),t=this._localeQuery.trim().toLowerCase();return t?e.filter(e=>e.tag.toLowerCase().includes(t)||e.native.toLowerCase().includes(t)||e.english.toLowerCase().includes(t)):e}_toggleLocalePicker=()=>{this._localeOpen=!this._localeOpen,this._localeOpen?this.updateComplete.then(()=>{this.querySelector(`[data-locale-search]`)?.focus()}):this._localeQuery=``};_selectLocale(e){this._siteLanguage=e,this._localeOpen=!1,this._localeQuery=``,this._syncLocaleDirty()}_onLocalePickerDocumentClick=e=>{if(!this._localeOpen)return;let t=e.target,n=this.querySelector(`[data-locale-picker]`);n&&t&&!n.contains(t)&&(this._localeOpen=!1)};_onLocalePickerKeydown=e=>{e.key===`Escape`&&this._localeOpen&&(this._localeOpen=!1,this._localeQuery=``)};_renderLanguagePicker(){let e=_z(this._siteLanguage||`en`),t=this._filteredLocaleEntries(),n=this.labels.siteLanguageSearchPlaceholder||`Search…`,r=this.labels.siteLanguageNoMatches||`No matches.`;return u`
|
|
2166
|
+
`+this._getInvalidityExplanation().join(``));if(this._executed)throw Error(`Conversion cannot be executed twice.`);if(this._executed=!0,this.onProgress){let e=this.utilizedTracks.map(e=>e.computeDuration()),t=Math.max(0,...await Promise.all(e));this._computeProgress=!0,this._totalDuration=Math.min(t-this._startTimestamp,this._endTimestamp-this._startTimestamp);for(let e of this.utilizedTracks)this._maxTimestamps.set(e.id,0);this.onProgress?.(0)}await this.output.start(),this._start();try{await Promise.all(this._trackPromises)}catch(e){throw this._canceled||this.cancel(),e}if(this._canceled)throw new VL;await this.output.finalize(),this._computeProgress&&this.onProgress?.(1)}async cancel(){if(!(this.output.state===`finalizing`||this.output.state===`finalized`)){if(this._canceled){console.warn(`Conversion already canceled.`);return}this._canceled=!0,await this.output.cancel()}}async _processVideoTrack(e,t){let n=e.codec;if(!n){this.discardedTracks.push({track:e,reason:`unknown_source_codec`});return}let r,i=LO(e.rotation+(t.rotate??0)),a=i,o=this.output.format.supportsVideoRotationMetadata&&(t.allowRotationMetadata??!0),[s,c]=i%180==0?[e.squarePixelWidth,e.squarePixelHeight]:[e.squarePixelHeight,e.squarePixelWidth],l=t.crop;l&&Zj(l,s,c);let[u,d]=l?[l.width,l.height]:[s,c],f=u,p=d,m=f/p,h=e=>Math.ceil(e/2)*2;t.width!==void 0&&t.height===void 0?(f=h(t.width),p=h(Math.round(f/m))):t.width===void 0&&t.height!==void 0?(p=h(t.height),f=h(Math.round(p*m))):t.width!==void 0&&t.height!==void 0&&(f=h(t.width),p=h(t.height));let g=await e.getFirstTimestamp(),_=this.output.format.getSupportedVideoCodecs(),v=!!t.forceTranscode||g<this._startTimestamp||!!t.frameRate||t.keyFrameInterval!==void 0||t.process!==void 0||t.bitrate!==void 0||!_.includes(n)||t.codec&&t.codec!==n||f!==u||p!==d||i!==0&&!o||!!l,y=t.alpha??`discard`;if(v){if(!await e.canDecode()){this.discardedTracks.push({track:e,reason:`undecodable_source_codec`});return}t.codec&&(_=_.filter(e=>e===t.codec));let n=t.bitrate??_L,s=await xL(_,{width:t.process&&t.processedWidth?t.processedWidth:f,height:t.process&&t.processedHeight?t.processedHeight:p,bitrate:n});if(!s){this.discardedTracks.push({track:e,reason:`no_encodable_target_codec`});return}let c={codec:s,bitrate:n,keyFrameInterval:t.keyFrameInterval,sizeChangeBehavior:t.fit??`passThrough`,alpha:y,hardwareAcceleration:t.hardwareAcceleration},m=new DL(c);r=m;let h=f!==u||p!==d||i!==0&&(!o||t.process!==void 0)||!!l||e.squarePixelWidth!==e.codedWidth||e.squarePixelHeight!==e.codedHeight;if(!h){let t=new FL({format:new cL,target:new eL}),n=new DL(c);t.addVideoTrack(n),await t.start();let r=await new yM(e).getSample(g);if(r)try{await n.add(r),r.close(),await t.finalize()}catch(e){console.info(`Error when probing encoder support. Falling back to rerender path.`,e),h=!0,t.cancel()}else await t.cancel()}h?this._trackPromises.push((async()=>{await this._started;let n=new bM(e,{width:f,height:p,fit:t.fit??`fill`,rotation:i,crop:t.crop,poolSize:1,alpha:y===`keep`}).canvases(this._startTimestamp,this._endTimestamp),r=t.frameRate;a=0;let o=null,s=null,c=null,l=async n=>{U(o),U(r!==void 0);let i=Math.round((n-s)*r);for(let n=1;n<i;n++){let i=new Jj(o,{timestamp:s+n/r,duration:1/r});await this._registerVideoSample(e,t,m,i),i.close()}};for await(let{canvas:i,timestamp:a,duration:u}of n){if(this._canceled)return;let n=Math.max(a-this._startTimestamp,0);if(c=n+u,r!==void 0){let e=Math.floor(n*r)/r;if(o!==null)if(e<=s){o=i,s=e;continue}else await l(e);n=e}let d=new Jj(i,{timestamp:n,duration:r===void 0?u:1/r});await this._registerVideoSample(e,t,m,d),d.close(),r!==void 0&&(o=i,s=n)}o&&(U(c!==null),U(r!==void 0),await l(Math.floor(c*r)/r)),m.close(),this._synchronizer.closeTrack(e.id)})()):this._trackPromises.push((async()=>{await this._started;let n=new yM(e),r=t.frameRate,i=null,a=null,o=null,s=async n=>{U(i),U(r!==void 0);let o=Math.round((n-a)*r);for(let n=1;n<o;n++)i.setTimestamp(a+n/r),i.setDuration(1/r),await this._registerVideoSample(e,t,m,i);i.close()};for await(let c of n.samples(this._startTimestamp,this._endTimestamp)){if(this._canceled){c.close(),i?.close();return}let n=Math.max(c.timestamp-this._startTimestamp,0);if(o=n+c.duration,r!==void 0){let e=Math.floor(n*r)/r;if(i!==null)if(e<=a){i.close(),i=c,a=e;continue}else await s(e);n=e,c.setDuration(1/r)}c.setTimestamp(n),await this._registerVideoSample(e,t,m,c),r===void 0?c.close():(i=c,a=n)}i&&(U(o!==null),U(r!==void 0),await s(Math.floor(o*r)/r)),m.close(),this._synchronizer.closeTrack(e.id)})())}else{let t=new wL(n);r=t,this._trackPromises.push((async()=>{await this._started;let n=new pM(e),r={decoderConfig:await e.getDecoderConfig()??void 0},i=Number.isFinite(this._endTimestamp)?await n.getPacket(this._endTimestamp,{metadataOnly:!0})??void 0:void 0;for await(let a of n.packets(void 0,i,{verifyKeyPackets:!0})){if(this._canceled)return;let n=a.clone({timestamp:a.timestamp-this._startTimestamp,sideData:y===`discard`?{}:a.sideData});U(n.timestamp>=0),this._reportProgress(e.id,n.timestamp),await t.add(n,r),this._synchronizer.shouldWait(e.id,n.timestamp)&&await this._synchronizer.wait(n.timestamp)}t.close(),this._synchronizer.closeTrack(e.id)})())}this.output.addVideoTrack(r,{frameRate:t.frameRate,languageCode:Ck(e.languageCode)?e.languageCode:void 0,name:e.name??void 0,disposition:e.disposition,rotation:a}),this._addedCounts.video++,this._totalTrackCount++,this.utilizedTracks.push(e)}async _registerVideoSample(e,t,n,r){if(this._canceled)return;this._reportProgress(e.id,r.timestamp);let i;if(!t.process)i=[r];else{let e=t.process(r);e instanceof Promise&&(e=await e),Array.isArray(e)||(e=e===null?[]:[e]),i=e.map(e=>e instanceof Jj?e:typeof VideoFrame<`u`&&e instanceof VideoFrame?new Jj(e):new Jj(e,{timestamp:r.timestamp,duration:r.duration}))}for(let t of i){if(this._canceled)break;await n.add(t),this._synchronizer.shouldWait(e.id,t.timestamp)&&await this._synchronizer.wait(t.timestamp)}for(let e of i)e!==r&&e.close()}async _processAudioTrack(e,t){let n=e.codec;if(!n){this.discardedTracks.push({track:e,reason:`unknown_source_codec`});return}let r,i=e.numberOfChannels,a=e.sampleRate,o=await e.getFirstTimestamp(),s=t.numberOfChannels??i,c=t.sampleRate??a,l=s!==i||c!==a||o<this._startTimestamp||o>this._startTimestamp&&!this.output.format.supportsTimestampedMediaData,u=this.output.format.getSupportedAudioCodecs();if(!t.forceTranscode&&!t.bitrate&&!l&&u.includes(n)&&(!t.codec||t.codec===n)&&!t.process){let t=new kL(n);r=t,this._trackPromises.push((async()=>{await this._started;let n=new pM(e),r={decoderConfig:await e.getDecoderConfig()??void 0},i=Number.isFinite(this._endTimestamp)?await n.getPacket(this._endTimestamp,{metadataOnly:!0})??void 0:void 0;for await(let a of n.packets(void 0,i)){if(this._canceled)return;let n=a.clone({timestamp:a.timestamp-this._startTimestamp});U(n.timestamp>=0),this._reportProgress(e.id,n.timestamp),await t.add(n,r),this._synchronizer.shouldWait(e.id,n.timestamp)&&await this._synchronizer.wait(n.timestamp)}t.close(),this._synchronizer.closeTrack(e.id)})())}else{if(!await e.canDecode()){this.discardedTracks.push({track:e,reason:`undecodable_source_codec`});return}let n=null;t.codec&&(u=u.filter(e=>e===t.codec));let i=t.bitrate??_L,a=await bL(u,{numberOfChannels:t.process&&t.processedNumberOfChannels?t.processedNumberOfChannels:s,sampleRate:t.process&&t.processedSampleRate?t.processedSampleRate:c,bitrate:i});if(!a.some(e=>tA.includes(e))&&u.some(e=>tA.includes(e))&&(s!==RL||c!==zL)){let e=(await bL(u,{numberOfChannels:RL,sampleRate:zL,bitrate:i})).find(e=>tA.includes(e));e&&(l=!0,n=e,s=RL,c=zL)}else n=a[0]??null;if(n===null){this.discardedTracks.push({track:e,reason:`no_encodable_target_codec`});return}if(l)r=this._resampleAudio(e,t,n,s,c,i);else{let a=new jL({codec:n,bitrate:i});r=a,this._trackPromises.push((async()=>{await this._started;let n=new CM(e);for await(let r of n.samples(void 0,this._endTimestamp)){if(this._canceled){r.close();return}r.setTimestamp(r.timestamp-this._startTimestamp),await this._registerAudioSample(e,t,a,r),r.close()}a.close(),this._synchronizer.closeTrack(e.id)})())}}this.output.addAudioTrack(r,{languageCode:Ck(e.languageCode)?e.languageCode:void 0,name:e.name??void 0,disposition:e.disposition}),this._addedCounts.audio++,this._totalTrackCount++,this.utilizedTracks.push(e)}async _registerAudioSample(e,t,n,r){if(this._canceled)return;this._reportProgress(e.id,r.timestamp);let i;if(!t.process)i=[r];else{let e=t.process(r);if(e instanceof Promise&&(e=await e),Array.isArray(e)||(e=e===null?[]:[e]),!e.every(e=>e instanceof rM))throw TypeError(`The audio process function must return an AudioSample, null, or an array of AudioSamples.`);i=e}for(let t of i){if(this._canceled)break;await n.add(t),this._synchronizer.shouldWait(e.id,t.timestamp)&&await this._synchronizer.wait(t.timestamp)}for(let e of i)e!==r&&e.close()}_resampleAudio(e,t,n,r,i,a){let o=new jL({codec:n,bitrate:a});return this._trackPromises.push((async()=>{await this._started;let n=new WL({targetNumberOfChannels:r,targetSampleRate:i,startTime:this._startTimestamp,endTime:this._endTimestamp,onSample:async n=>{await this._registerAudioSample(e,t,o,n),n.close()}}),a=new CM(e).samples(this._startTimestamp,this._endTimestamp);for await(let e of a){if(this._canceled){e.close();return}await n.add(e),e.close()}await n.finalize(),o.close(),this._synchronizer.closeTrack(e.id)})()),o}_reportProgress(e,t){if(!this._computeProgress)return;U(this._totalDuration!==null),this._maxTimestamps.set(e,Math.max(t,this._maxTimestamps.get(e)));let n=_k(Math.min(...this._maxTimestamps.values())/this._totalDuration,0,1);n!==this._lastProgress&&(this._lastProgress=n,this.onProgress?.(n))}},VL=class extends Error{constructor(e=`Conversion has been canceled.`){super(e),this.name=`ConversionCanceledError`}},HL=5,UL=class{constructor(){this.maxTimestamps=new Map,this.resolvers=[]}computeMinAndMaybeResolve(){let e=1/0;for(let[,t]of this.maxTimestamps)e=Math.min(e,t);for(let t=0;t<this.resolvers.length;t++){let n=this.resolvers[t];n.timestamp-e<HL&&(n.resolve(),this.resolvers.splice(t,1),t--)}return e}shouldWait(e,t){return this.maxTimestamps.set(e,Math.max(t,this.maxTimestamps.get(e)??-1/0)),t-this.computeMinAndMaybeResolve()>=HL}wait(e){let{promise:t,resolve:n}=ok();return this.resolvers.push({timestamp:e,resolve:n}),t}closeTrack(e){this.maxTimestamps.delete(e),this.computeMinAndMaybeResolve()}},WL=class{constructor(e){this.sourceSampleRate=null,this.sourceNumberOfChannels=null,this.targetSampleRate=e.targetSampleRate,this.targetNumberOfChannels=e.targetNumberOfChannels,this.startTime=e.startTime,this.endTime=e.endTime,this.onSample=e.onSample,this.bufferSizeInFrames=Math.floor(this.targetSampleRate*5),this.bufferSizeInSamples=this.bufferSizeInFrames*this.targetNumberOfChannels,this.outputBuffer=new Float32Array(this.bufferSizeInSamples),this.bufferStartFrame=0,this.maxWrittenFrame=-1}doChannelMixerSetup(){U(this.sourceNumberOfChannels!==null);let e=this.sourceNumberOfChannels,t=this.targetNumberOfChannels;e===1&&t===2?this.channelMixer=(t,n)=>t[n*e]:e===1&&t===4?this.channelMixer=(t,n,r)=>t[n*e]*+(r<2):e===1&&t===6?this.channelMixer=(t,n,r)=>t[n*e]*+(r===2):e===2&&t===1?this.channelMixer=(t,n)=>{let r=n*e;return .5*(t[r]+t[r+1])}:e===2&&t===4||e===2&&t===6?this.channelMixer=(t,n,r)=>t[n*e+r]*+(r<2):e===4&&t===1?this.channelMixer=(t,n)=>{let r=n*e;return .25*(t[r]+t[r+1]+t[r+2]+t[r+3])}:e===4&&t===2?this.channelMixer=(t,n,r)=>{let i=n*e;return .5*(t[i+r]+t[i+r+2])}:e===4&&t===6?this.channelMixer=(t,n,r)=>{let i=n*e;return r<2?t[i+r]:r===2||r===3?0:t[i+r-2]}:e===6&&t===1?this.channelMixer=(t,n)=>{let r=n*e;return Math.SQRT1_2*(t[r]+t[r+1])+t[r+2]+.5*(t[r+4]+t[r+5])}:e===6&&t===2?this.channelMixer=(t,n,r)=>{let i=n*e;return t[i+r]+Math.SQRT1_2*(t[i+2]+t[i+r+4])}:e===6&&t===4?this.channelMixer=(t,n,r)=>{let i=n*e;return r<2?t[i+r]+Math.SQRT1_2*t[i+2]:t[i+r+2]}:this.channelMixer=(t,n,r)=>r<e?t[n*e+r]:0}ensureTempBufferSize(e){let t=this.tempSourceBuffer.length;for(;t<e;)t*=2;if(t!==this.tempSourceBuffer.length){let e=new Float32Array(t);e.set(this.tempSourceBuffer),this.tempSourceBuffer=e}}async add(e){this.sourceSampleRate===null&&(this.sourceSampleRate=e.sampleRate,this.sourceNumberOfChannels=e.numberOfChannels,this.tempSourceBuffer=new Float32Array(this.sourceSampleRate*this.sourceNumberOfChannels),this.doChannelMixerSetup());let t=e.numberOfFrames*e.numberOfChannels;this.ensureTempBufferSize(t);let n=e.allocationSize({planeIndex:0,format:`f32`}),r=new Float32Array(this.tempSourceBuffer.buffer,0,n/4);e.copyTo(r,{planeIndex:0,format:`f32`});let i=e.timestamp-this.startTime,a=e.numberOfFrames/this.sourceSampleRate,o=Math.min(i+a,this.endTime-this.startTime),s=Math.floor(i*this.targetSampleRate),c=Math.ceil(o*this.targetSampleRate);for(let t=s;t<c;t++){if(t<this.bufferStartFrame)continue;for(;t>=this.bufferStartFrame+this.bufferSizeInFrames;)await this.finalizeCurrentBuffer(),this.bufferStartFrame+=this.bufferSizeInFrames;let n=t-this.bufferStartFrame;U(n<this.bufferSizeInFrames);let a=(t/this.targetSampleRate-i)*this.sourceSampleRate,o=Math.floor(a),s=Math.ceil(a),c=a-o;for(let t=0;t<this.targetNumberOfChannels;t++){let i=0,a=0;o>=0&&o<e.numberOfFrames&&(i=this.channelMixer(r,o,t)),s>=0&&s<e.numberOfFrames&&(a=this.channelMixer(r,s,t));let l=i+c*(a-i),u=n*this.targetNumberOfChannels+t;this.outputBuffer[u]+=l}this.maxWrittenFrame=Math.max(this.maxWrittenFrame,n)}}async finalizeCurrentBuffer(){if(this.maxWrittenFrame<0)return;let e=(this.maxWrittenFrame+1)*this.targetNumberOfChannels,t=new Float32Array(e);t.set(this.outputBuffer.subarray(0,e));let n=this.bufferStartFrame/this.targetSampleRate,r=new rM({format:`f32`,sampleRate:this.targetSampleRate,numberOfChannels:this.targetNumberOfChannels,timestamp:n,data:t});await this.onSample(r),this.outputBuffer.fill(0),this.maxWrittenFrame=-1}finalize(){return this.finalizeCurrentBuffer()}};function GL(){return typeof AudioEncoder<`u`}async function KL(e,t){let n=new MP({source:new OP(e),formats:EP}),r=new $I,i=new FL({format:new cL({fastStart:`in-memory`}),target:r});try{let a=await BL.init({input:n,output:i,video:{discard:!0},audio:{codec:`aac`,bitrate:_L}});t&&(a.onProgress=t),await a.execute();let o=r.buffer;if(!o)throw Error(`Audio processing produced no output`);let s=e.name.replace(/\.[^.]+$/,``);return{file:new File([o],`${s}.m4a`,{type:`audio/mp4`})}}finally{n.dispose()}}var qL={isSupported:GL,processToFile:KL},JL=1920,YL=1080,XL=640,ZL=32;function QL(){return typeof VideoEncoder<`u`}async function $L(e){let t=new MP({source:new OP(e),formats:EP});try{let e=await t.getPrimaryVideoTrack();if(!e)return{};let n=e.displayWidth,r=e.displayHeight,i=e.rotation,a=await t.computeDuration(),o=s(a),c=Math.min(a*.1,3),l=await new bM(e).getCanvas(c);if(!l)return{sourceWidth:n,sourceHeight:r,rotation:i,durationSeconds:o};let u=l.canvas,d=u.width,f=u.height,p=Math.min(XL/d,1),m=Math.round(d*p),h=Math.round(f*p),g=document.createElement(`canvas`);g.width=m,g.height=h;let _=g.getContext(`2d`);if(!_)return{sourceWidth:n,sourceHeight:r};_.drawImage(u,0,0,m,h);let v=await new Promise(e=>{g.toBlob(t=>e(t??void 0),`image/webp`,.8)}),y=Math.min(ZL/d,ZL/f,1),b=Math.max(Math.round(d*y),1),x=Math.max(Math.round(f*y),1),S=document.createElement(`canvas`);S.width=b,S.height=x;let C=S.getContext(`2d`);return C?(C.drawImage(u,0,0,b,x),{poster:v,blurhash:AC(C.getImageData(0,0,b,x).data,b,x,4,3),sourceWidth:n,sourceHeight:r,rotation:i,durationSeconds:o}):{poster:v,sourceWidth:n,sourceHeight:r}}catch{return{}}finally{t.dispose()}}async function eR(e,t){let{poster:n,blurhash:r,sourceWidth:i,sourceHeight:a,rotation:o,durationSeconds:s}=await $L(e),c=i||JL,l=a||YL;if(i&&a){let e=Math.max(i,a),t=Math.min(i,a),n=Math.min(JL/e,YL/t,1);c=Math.round(i*n),l=Math.round(a*n)}c+=c%2,l+=l%2;let u=new MP({source:new OP(e),formats:EP}),d=new $I,f=new FL({format:new cL({fastStart:`in-memory`}),target:d});try{let i=await BL.init({input:u,output:f,video:{codec:`avc`,width:c,height:l,fit:`contain`,bitrate:_L},audio:{codec:`aac`}});t&&(i.onProgress=t),await i.execute();let a=d.buffer;if(!a)throw Error(`Video processing produced no output`);let p=e.name.replace(/\.[^.]+$/,``),m=new File([a],`${p}.mp4`,{type:`video/mp4`}),h=await iR(m),g=Math.abs(h.width-c)<=2&&Math.abs(h.height-l)<=2;return o&&!g&&(rR(a),m=new File([a],`${p}.mp4`,{type:`video/mp4`}),h=await iR(m)),{file:m,width:h.width,height:h.height,durationSeconds:s,poster:n,blurhash:r}}finally{u.dispose()}}var tR=[65536,0,0,0,65536,0,0,0,1073741824];function nR(e,t,n,r){let i=t;for(;i+8<=n;){let t=e.getUint32(i),a=String.fromCharCode(e.getUint8(i+4),e.getUint8(i+5),e.getUint8(i+6),e.getUint8(i+7));if(t===0&&(t=n-i),t<8||i+t>n)break;r(i,t,a),(a===`moov`||a===`trak`||a===`mdia`||a===`edts`)&&nR(e,i+8,i+t,r),i+=t}}function rR(e){let t=new DataView(e);nR(t,0,e.byteLength,(n,r,i)=>{if(i!==`tkhd`)return;let a=n+8,o=a+(t.getUint8(a)===0?40:52);if(o+36>e.byteLength)return;let s=!0;for(let e=0;e<9;e++)if(t.getInt32(o+e*4)!==tR[e]){s=!1;break}if(!s)for(let e=0;e<9;e++)t.setInt32(o+e*4,tR[e])})}function iR(e){return new Promise((t,n)=>{let r=URL.createObjectURL(e),i=document.createElement(`video`);i.preload=`metadata`,i.onloadedmetadata=()=>{URL.revokeObjectURL(r),t({width:i.videoWidth,height:i.videoHeight})},i.onerror=()=>{URL.revokeObjectURL(r),n(Error(`Failed to probe transcoded video dimensions`))},i.src=r})}var aR={isSupported:QL,processToFile:eR};function oR(){return document.querySelector(`jant-compose-dialog`)}function sR(){return document.querySelector(`[data-page='collection'][data-collection-id]`)?.dataset.collectionId||void 0}function cR(e=document){if(e===document){let e=document.querySelector(`[data-page='post'] article[data-post]:hover`);if(e)return e}return e.querySelector(`[data-post-current] article[data-post]`)||e.querySelector(`[data-post-view] article[data-post]`)||(e===document?document.querySelector(`article[data-post]:hover`):null)}function lR(e){let t=e.closest(`[data-post-view]`)?.dataset.postViewId;if(t)return{kind:`post-view`,id:t};let n=e.closest(`[data-timeline-item]`),r=n?.dataset.threadRootId??e.dataset.threadRootId??e.dataset.postId;if(n&&r)return{kind:`timeline-item`,id:r};let i=e.dataset.postId;return i?{kind:`post-card`,id:i}:null}function uR(e){let t=e.cloneNode(!0);t.querySelector(`[data-post-meta]`)?.remove(),t.querySelector(`.post-status-badges`)?.remove();let n=e.querySelector(`time.dt-published`);return{contentHtml:t.innerHTML,dateText:n?.textContent?.trim()??``}}function dR(e){let t=e.dataset.format;if(t===`note`||t===`link`||t===`quote`)return t}async function fR(e){await oR()?.openNew(e)}async function pR(e){let t=e.dataset.postId;if(!t)return;let n=oR();if(!n)return;let r=e.dataset.threadRootId??t,i=dR(e);await n.openReply(t,uR(e),r,lR(e)??void 0,i?{initialFormat:i}:void 0)}function mR(e){return e instanceof globalThis.Element?e.closest(`jant-compose-editor`):null}function hR(e){return e instanceof globalThis.Element?e.closest(`jant-compose-dialog`):null}async function gR(e){let t=await fetch(e,{headers:{Accept:`text/html`}});return t.ok?t.text():null}async function _R(e){try{let t=document.querySelector(`[data-timeline-item][data-thread-root-id="${e}"]`)?.querySelector(`[data-timeline-item-content]`);if(!t)return!1;let n=await gR(`/_/timeline-item/${encodeURIComponent(e)}`);return n?(t.innerHTML=n,y(t),!0):!1}catch{return!1}}async function vR(e){try{let t=document.querySelector(`article[data-post-id="${e}"]`)?.closest(`[data-timeline-item]`),n=await gR(`/_/post-card/${encodeURIComponent(e)}`);if(!n)return!1;if(t){let e=t.querySelector(`[data-timeline-item-content]`);return e?(e.innerHTML=n,y(e),!0):!1}let r=document.querySelector(`article[data-post-id="${e}"]`);if(!r)return!1;r.outerHTML=n;let i=document.querySelector(`article[data-post-id="${e}"]`);return i&&y(i),!0}catch{return!1}}async function yR(e){try{let t=document.querySelector(`[data-post-view][data-post-view-id="${e}"]`);if(!t)return!1;let n=await gR(`/_/post-view/${encodeURIComponent(e)}`);if(!n)return!1;t.outerHTML=n;let r=document.querySelector(`[data-post-view][data-post-view-id="${e}"]`);return r&&y(r),!0}catch{return!1}}async function bR(e){return!e.replyRefreshKind||!e.replyRefreshId?!1:e.replyRefreshKind===`timeline-item`?_R(e.replyThreadRootId??e.replyRefreshId):e.replyRefreshKind===`post-view`?yR(e.replyRefreshId):vR(e.replyRefreshId)}var xR=new Map,SR=new Set,CR=new Map;function wR(e){return new Promise(t=>{let n=setTimeout(()=>{a(),t(null)},3e3),r=URL.createObjectURL(e),i=document.createElement(`video`);i.muted=!0,i.playsInline=!0,i.preload=`auto`;function a(){clearTimeout(n),i.removeAttribute(`src`),i.load(),URL.revokeObjectURL(r)}i.onloadeddata=()=>{try{let e=i.videoWidth,n=i.videoHeight;if(!e||!n){a(),t(null);return}let r=Math.min(640/e,1),o=Math.round(e*r),s=Math.round(n*r),c=document.createElement(`canvas`);c.width=o,c.height=s;let l=c.getContext(`2d`);if(!l){a(),t(null);return}l.drawImage(i,0,0,o,s),c.toBlob(e=>{a(),t(e)},`image/webp`,.6)}catch{a(),t(null)}},i.onerror=()=>{a(),t(null)},i.src=r})}async function TR(e,t,n){try{let r,i,o,s,c,l,u;if(e.type.startsWith(`video/`)){if(!aR.isSupported())return n?.updateAttachmentStatus(t,`error`,null,`Your browser doesn't support video processing. Use Chrome or Edge to upload videos.`),null;wR(e).then(e=>{e&&n?.updateAttachmentPoster(t,e)}),n?.updateAttachmentStatus(t,`processing`,null,null);let a=await aR.processToFile(e,e=>{n?.updateAttachmentProgress(t,e)});r=a.file,i=a.width,o=a.height,s=a.durationSeconds,c=a.blurhash,u=a.poster,u&&n?.updateAttachmentPoster(t,u)}else if(e.type.startsWith(`audio/`)){if(!qL.isSupported())return n?.updateAttachmentStatus(t,`error`,null,`Your browser doesn't support audio processing. Use Chrome or Edge to upload audio.`),null;try{l=await NC(e)}catch{}n?.updateAttachmentStatus(t,`processing`,null,null),r=(await qL.processToFile(e,e=>{n?.updateAttachmentProgress(t,e)})).file}else if(e.type.startsWith(`image/`)||/\.heic$/i.test(e.name)||/\.heif$/i.test(e.name)){let s=e;try{let{isHeic:a,heicTo:c}=await kO(async()=>{let{isHeic:e,heicTo:t}=await import(`./chunks/heic-to-DUUaO23q.js`);return{isHeic:e,heicTo:t}},[]);if(await a(e)){n?.updateAttachmentStatus(t,`processing`,null,null);let r=await c({blob:e,type:`image/jpeg`,quality:.92});s=new File([r],e.name.replace(/\.heic$/i,`.jpg`),{type:`image/jpeg`}),n?.updateAttachmentPreview(t,s)}let l=await re.processToFile(s);r=l.file,i=l.width,o=l.height}catch{return n?.removeAttachment(t),a(`Image format not supported.`,`error`),null}}else r=e;if(n?.updateAttachmentStatus(t,`uploading`,null,null),!e.type.startsWith(`video/`)){let e=await PC(r);i??=e.width,o??=e.height,c??=e.blurhash,l??=e.waveform,!u&&e.poster&&(u=e.poster,n?.updateAttachmentPoster(t,u))}let d,f;if(sS(e.type)===`text`)try{let e=await r.text(),t=e.replace(/\s+/g,` `).trim();f=e.length,d=t.length<=100?t:t.slice(0,100)+`…`}catch{}let p=await GC(r,{width:i,height:o,durationSeconds:s,blurhash:c,waveform:l,poster:u,summary:d,chars:f},e=>{n?.updateAttachmentProgress(t,e)});return n?.updateAttachmentStatus(t,`done`,p.id,null),CR.set(t,p.id),p.id}catch(e){let r=e instanceof Error?e.message:`Upload failed`;return n?.updateAttachmentStatus(t,`error`,null,r),n||a(r,`error`),null}}document.addEventListener(`jant:attachment-removed`,e=>{let{clientId:t,mediaId:n}=e.detail;CR.delete(t),n?fetch(`/api/upload/${n}`,{method:`DELETE`}).catch(()=>{}):SR.add(t)}),document.addEventListener(`jant:files-selected`,e=>{let t=e,n=mR(t.target);for(let{file:e,clientId:r}of t.detail.files){let t=TR(e,r,n).then(e=>SR.has(r)?(SR.delete(r),e&&fetch(`/api/upload/${e}`,{method:`DELETE`}).catch(()=>{}),null):e);xR.set(r,t),t.finally(()=>xR.delete(r))}}),document.addEventListener(`click`,e=>{let t=e.target.closest(`[data-reply-trigger]`);if(!t)return;let n=t.closest(`article[data-post]`);n&&pR(n)});function ER(e,t){let n=e.format===`quote`,r=e.format===`link`,i=!!e.editPostId,a=e=>e||void 0,o=e=>e||null;return{format:e.format,title:n?void 0:i?o(e.title):a(e.title),body:i?o(e.body):a(e.body),url:r?i?o(e.url):a(e.url):i?null:void 0,sourceName:n?i?o(e.quoteAuthor):a(e.quoteAuthor):void 0,sourceUrl:n?i?o(e.url):a(e.url):void 0,quoteText:n?i?o(e.quoteText):a(e.quoteText):i?null:void 0,slug:e.slug||void 0,status:e.status,publishedAt:e.status===`published`?e.publishedAt:void 0,visibility:e.visibility||void 0,rating:i?e.rating>0?e.rating:null:e.rating||void 0,collectionIds:e.collectionIds,attachments:t.length>0?t:void 0,replyToId:e.replyToId||void 0,quietReply:e.quietReply||void 0}}function DR(e){return JSON.stringify(e.bodyJson)!==JSON.stringify(e.originalBodyJson??null)}function OR(e,t){let n=[];for(let r of e.attachments){if(r.type===`media`){let e=r.mediaId??t.get(r.clientId)??CR.get(r.clientId);if(!e)continue;n.push({type:`media`,mediaId:e,alt:r.alt});continue}if(r.mediaId&&!DR(r)){n.push({type:`media`,mediaId:r.mediaId});continue}n.push({type:`text`,contentFormat:`markdown`,content:AT(JSON.stringify(r.bodyJson)),summary:r.summary})}return n}document.addEventListener(`jant:compose-submit-deferred`,async e=>{let t=e,n=t.detail,r=hR(t.target)??document.querySelector(`jant-compose-dialog`),o=!!r?.pageMode,s=r?.labels,c=s?.uploading??`Uploading...`,l=n.threadPosts?n.threadPosts.some(e=>e.body.includes(`"blob:`)):n.body.includes(`"blob:`),u=n.pendingAttachments.length>0||l,f=s?.published??`Published!`,p=s?.view??`View`;u&&d(`compose-deferred`,c);let h=(e,t=`success`)=>{u?_(`compose-deferred`,e,t):a(e,t)},g=()=>{!o||!r||(r.reset(),r.updateComplete.then(()=>{r.querySelector(`jant-compose-editor`)?.focusInput()}))},v=()=>{!o||!r||(r.loading=!1)},y=()=>{if(te){n.editPostId&&r?.clearEditDraftFromStorage?.(n.editPostId);return}r?.clearLocalDraftFromStorage?.()},b=async()=>{if(!(!r||o)){if(te&&n.editPostId){if(typeof r.openEdit!=`function`)return;await r.openEdit(n.editPostId);return}if(n.replyToId){if(typeof r.openReply!=`function`)return;await r.openReply(n.replyToId,void 0,n.replyThreadRootId,n.replyRefreshKind&&n.replyRefreshId?{kind:n.replyRefreshKind,id:n.replyRefreshId}:void 0,{restoreDraft:!0,initialFormat:n.format});return}typeof r.openNew==`function`&&await r.openNew({restoreDraft:!0})}},x=async e=>{v(),await b(),h(e,`error`)},S=async()=>{await r?.refreshCollections()},C=(e,t)=>{m(e,`success`,t)},ee=()=>!o||!r||!r.consumePageLeaveRequest()?!1:(r.preparePageLeave(),globalThis.location.assign(r.closeHref||i(`/`)),!0),te=!!n.editPostId,ne=!!(n.threadPosts&&n.threadPosts.length>=2),re=null;try{let e=n.pendingAttachments.map(e=>e.clientId),t=e.map(e=>xR.get(e)??Promise.resolve(null)),a=await Promise.all(t);if(a.filter((t,n)=>t===null&&!CR.has(e[n]??``)).length>0)if(n.status===`published`&&!te)re=`upload`;else{await x(`Upload failed. Post not created.`);return}let c=new Map;for(let t=0;t<e.length;t++){let n=e[t],r=a[t];n&&r&&c.set(n,r)}if(ne&&n.threadPosts){let e=n.threadPosts,t=re?`draft`:n.status,i=(await Promise.all(e.map(async e=>{let n=e.body;if(n.includes(`"blob:`))try{let e=await $C(JSON.parse(n));n=e?JSON.stringify(e):``}catch{}return{...e,body:n,status:t}}))).map(e=>ER(e,OR(e,c))),a={posts:i};te&&n.editPostId&&(a.replaceThreadId=n.editPostId);let l=await fetch(`/compose/thread`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify(a)});if(!l.ok){if(n.status===`published`&&!re){let e={posts:i.map(e=>({...e,status:`draft`}))};if((await fetch(`/compose/thread`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify(e)})).ok){re=`server`,y();let e=s?.publishFailedDraft??`Couldn't publish. Saved as draft.`;await S(),ee()||g(),h(e);return}}await x(w(await le(l),`error`)??`Something went wrong`);return}if(re===`upload`){y();let e=s?.uploadFailedDraft??`Some uploads failed. Saved as draft.`;await S(),g(),h(e);return}let u=await le(l),d=w(u,`status`),m=w(u,`permalink`),_=w(u,`toast`);d===`published`?(y(),o?(await S(),g(),h(f)):(C(f,m?{label:p,href:m}:void 0),globalThis.location.reload())):(y(),await S(),ee()||g(),h(_??`Draft saved.`),kR(r,`draft`));return}let u=OR(n,c);if(l)try{let e=await $C(JSON.parse(n.body));n.body=e?JSON.stringify(e):``}catch{}let d=te?`/api/posts/${n.editPostId}`:`/compose`,m=te?`PUT`:`POST`,_=ER({...n,status:re?`draft`:n.status},u),v=await fetch(d,{method:m,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify(_)});if(!v.ok){if(n.status===`published`&&!te&&!re){let e={..._,status:`draft`},t=await fetch(d,{method:m,headers:{"Content-Type":`application/json`,Accept:`application/json`},body:JSON.stringify(e)});if(t.ok){re=`server`,y();let e=await le(t),n=s?.publishFailedDraft??`Couldn't publish. Saved as draft.`;await S(),ee()||g(),h(n);let r=w(e,`toast`);r&&h(r);return}}await x(w(await le(v),`error`)??`Something went wrong`);return}if(te){y();let e=await le(v),t=n.editPostId??``,r=w(e,`slug`),a=r?i(`/${r}`):null;if(o)a&&a!==globalThis.location.pathname?(C(`Post updated.`),globalThis.location.assign(a)):t&&await yR(t)?h(`Post updated.`):(C(`Post updated.`),globalThis.location.assign(globalThis.location.pathname));else if(t)if(document.querySelector(`[data-post-view][data-post-view-id="${t}"]`)){if(a&&a!==globalThis.location.pathname){C(`Post updated.`),globalThis.location.assign(a);return}await yR(t)?h(`Post updated.`):(C(`Post updated.`),globalThis.location.reload())}else{let e=document.querySelector(`article[data-post-id="${t}"]`)?.closest(`[data-timeline-item]`)?.dataset.threadRootId;(e?await _R(e):await vR(t))?h(`Post updated.`):(C(`Post updated.`),globalThis.location.reload())}else C(`Post updated.`),globalThis.location.reload();return}if(re===`upload`){y();let e=s?.uploadFailedDraft??`Some uploads failed. Saved as draft.`;await S(),g(),h(e);return}let b=await le(v),ie=w(b,`status`),ae=w(b,`permalink`),oe=w(b,`toast`);if(ie===`published`){if(y(),o)await S(),g(),h(f);else if(n.replyToId){if(await S(),!await bR(n)){C(f,ae?{label:p,href:ae}:void 0),globalThis.location.reload();return}h(f)}else C(f,ae?{label:p,href:ae}:void 0),globalThis.location.reload();return}else y(),await S(),ee()||g(),h(oe??`Draft saved.`),kR(r,`draft`)}catch{await x(`Something went wrong`)}});function kR(e,t){(e??document).dispatchEvent(new CustomEvent(`jant:compose-submit-complete`,{bubbles:!0,detail:{status:t}}))}var AR=`.compose-prompt`,jR=`.compose-prompt-trigger`,MR=`jant.composeOpenShortcutDiscovery`,NR=`/api/settings/discovery/compose-open-shortcut`,PR=350,FR=1800,IR=3,LR=`compose-prompt-discovery-visible`,RR=!1,zR=null,BR=null,VR=null;function HR(){try{return globalThis.localStorage!==void 0}catch{return!1}}function UR(){if(!HR())return{shownCount:0,completed:!1};let e=globalThis.localStorage.getItem(MR);if(!e)return{shownCount:0,completed:!1};try{let t=JSON.parse(e);return{shownCount:typeof t.shownCount==`number`&&t.shownCount>=0?t.shownCount:0,completed:t.completed===!0}}catch{return globalThis.localStorage.removeItem(MR),{shownCount:0,completed:!1}}}function WR(e){if(HR())try{globalThis.localStorage.setItem(MR,JSON.stringify(e))}catch{}}function GR(){zR!==null&&(clearTimeout(zR),zR=null)}function KR(){BR!==null&&(clearTimeout(BR),BR=null)}function qR(){return typeof globalThis.matchMedia==`function`&&globalThis.matchMedia(`(min-width: 700px)`).matches}function JR(e){return UR().completed?!0:e?.dataset.composeOpenShortcutDiscovered===`true`}function YR(e){e&&(e.dataset.composeOpenShortcutDiscovered=`true`,e.classList.remove(LR)),document.querySelectorAll(AR).forEach(e=>{e.dataset.composeOpenShortcutDiscovered=`true`,e.classList.remove(LR)})}function XR(){let e=UR();e.completed||RR||e.shownCount>=IR||(RR=!0,WR({...e,shownCount:e.shownCount+1}))}function ZR(e){return!qR()||JR(e)?!1:UR().shownCount<IR||RR}function QR(e){GR(),KR(),e?e.classList.remove(LR):VR&&VR.classList.remove(LR),(!e||e===VR)&&(VR=null)}function $R(e){ZR(e)&&(QR(VR),VR=e,e.classList.add(LR),XR(),BR=setTimeout(()=>{QR(e)},FR))}function ez(e){ZR(e)&&(e.classList.contains(LR)||(GR(),zR=setTimeout(()=>{$R(e)},PR)))}function tz(e){if(e.dataset.composeOpenShortcutDiscoveryBound===`true`)return;let t=e.querySelector(jR);t&&(e.dataset.composeOpenShortcutDiscoveryBound=`true`,t.addEventListener(`pointerenter`,()=>{ez(e)}),t.addEventListener(`pointerleave`,()=>{QR(e)}),t.addEventListener(`focusin`,()=>{ez(e)}),t.addEventListener(`focusout`,()=>{QR(e)}))}function nz(e=document){e.querySelectorAll(AR).forEach(e=>tz(e))}function rz(){let e=UR();e.completed||WR({shownCount:Math.max(e.shownCount,IR),completed:!0}),YR(VR),QR(VR),typeof globalThis.fetch==`function`&&globalThis.fetch(NR,{method:`POST`,headers:{Accept:`application/json`},credentials:`same-origin`}).catch(()=>{})}document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,()=>{nz()}):nz();var iz=[`input`,`textarea`,`select`,`button`,`a[href]`,`[contenteditable='']`,`[contenteditable='true']`,`[role='textbox']`,`.ProseMirror`].join(`, `);function az(e){return e instanceof globalThis.Element&&e.closest(iz)!==null}function oz(e){if(e.defaultPrevented||e.isComposing||e.repeat||e.metaKey||e.ctrlKey||e.altKey||!oR()||document.querySelector(`[data-page="compose"]`)||document.querySelector(`dialog[open]`))return!0;let t=document.activeElement;return az(e.target)||t!==e.target&&az(t)}async function sz(e,t,n){try{if(!(await fetch(`/api/posts/${e}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({featured:t})})).ok)throw Error();t?n.setAttribute(`data-post-featured`,``):n.removeAttribute(`data-post-featured`),a(t?`Added to Featured.`:`Removed from Featured.`)}catch{a(`Could not update post. Try again.`,`error`)}}document.addEventListener(`keydown`,e=>{let t=e.key.toLowerCase();if(t!==`n`&&t!==`l`&&t!==`q`&&t!==`r`&&t!==`e`&&t!==`c`&&t!==`f`||oz(e))return;if(t===`n`||t===`l`||t===`q`){e.preventDefault(),rz();let n=sR(),r=t===`l`?`link`:t===`q`?`quote`:void 0;fR({...n?{collectionId:n}:void 0,...r?{initialFormat:r}:void 0});return}let n=cR();if(n){if(t===`r`){e.preventDefault(),pR(n);return}if(t===`e`){let t=n.dataset.postId;if(!t)return;e.preventDefault();let r=oR();r&&r.openEdit(t);return}if(t===`c`){e.preventDefault();let t=document.querySelector(`jant-post-menu`);t&&t.openCollectionsForPost(n);return}if(t===`f`){let t=n.dataset.postId;if(!t)return;e.preventDefault(),sz(t,!n.hasAttribute(`data-post-featured`),n);return}}});var cz=[`en`,`zh-Hans`,`zh-Hant`];function lz(e){return typeof e==`string`&&cz.includes(e)}function uz(e){let t=e.trim();if(!t)return`en`;let n;try{n=new Intl.Locale(t)}catch{return`en`}if(lz(n.baseName))return n.baseName;if(n.language===`zh`){let e=n.region;return n.script===`Hant`||e===`TW`||e===`HK`||e===`MO`?`zh-Hant`:`zh-Hans`}return`en`}var dz={en:1,"zh-Hans":1,"zh-Hant":1},fz=`en.zh-Hans.zh-Hant.ja.ko.es.fr.de.it.pt.ru.ar.hi.bn.ur.tr.vi.th.id.fa.he.nl.pl.sv.da.no.fi.cs.hu.el.ro.uk.en-GB.en-US.fr-CA.pt-BR.es-MX.zh-CN.zh-TW.zh-HK`.split(`.`),pz=null;function mz(e){let t=e,n=e;try{let n=new Intl.DisplayNames([e],{type:`language`}).of(e);typeof n==`string`&&n.length>0&&(t=n)}catch{}try{let t=new Intl.DisplayNames([`en`],{type:`language`}).of(e);typeof t==`string`&&t.length>0&&(n=t)}catch{}return{tag:e,native:t,english:n,coverage:hz(e)}}function hz(e){let t=uz(e);if(t===`en`){let t;try{t=new Intl.Locale(e).language}catch{return 0}if(t!==`en`)return 0}return dz[t]}function gz(){return pz||(pz=fz.map(mz),pz)}function _z(e){let t=e.trim();return gz().find(e=>e.tag===t)||mz(t)}var vz=class extends c{static properties={labels:{type:Object},timezones:{type:Array},cjkFonts:{type:Array,attribute:`cjk-fonts`},siteNameFallback:{type:String,attribute:`sitename-fallback`},siteDescriptionFallback:{type:String,attribute:`sitedescription-fallback`},demoMode:{type:Boolean,attribute:`demo-mode`},mainFeedUrl:{type:String,attribute:`main-feed-url`},latestFeedUrl:{type:String,attribute:`latest-feed-url`},featuredFeedUrl:{type:String,attribute:`featured-feed-url`},_siteName:{state:!0},_siteDescription:{state:!0},_siteFooter:{state:!0},_origSite:{state:!0},_siteDirty:{state:!0},_siteLoading:{state:!0},_siteLanguage:{state:!0},_localeOpen:{state:!0},_localeQuery:{state:!0},_cjkSerifFont:{state:!0},_timeZone:{state:!0},_origLocale:{state:!0},_localeDirty:{state:!0},_localeLoading:{state:!0},_mainRssFeed:{state:!0},_origMainRssFeed:{state:!0},_feedDirty:{state:!0},_feedLoading:{state:!0},_showJantBrandingOnHome:{state:!0},_origShowJantBrandingOnHome:{state:!0},_homeLoading:{state:!0},_noindex:{state:!0},_origNoindex:{state:!0},_searchLoading:{state:!0}};_descEditor=null;_footerEditor=null;createRenderRoot(){return this.innerHTML=``,this}constructor(){super(),this.labels={},this.timezones=[],this.cjkFonts=[],this.siteNameFallback=``,this.siteDescriptionFallback=``,this.demoMode=!1,this.mainFeedUrl=`/feed`,this.latestFeedUrl=`/feed/latest`,this.featuredFeedUrl=`/feed/featured`,this._siteName=``,this._siteDescription=``,this._siteFooter=``,this._origSite={siteName:``,siteDescription:``,siteFooter:``},this._siteDirty=!1,this._siteLoading=!1,this._siteLanguage=`en`,this._localeOpen=!1,this._localeQuery=``,this._cjkSerifFont=`off`,this._timeZone=`UTC`,this._origLocale={siteLanguage:`en`,cjkSerifFont:`off`,timeZone:`UTC`},this._localeDirty=!1,this._localeLoading=!1,this._mainRssFeed=`featured`,this._origMainRssFeed=`featured`,this._feedDirty=!1,this._feedLoading=!1,this._noindex=!1,this._origNoindex=!1,this._showJantBrandingOnHome=!1,this._origShowJantBrandingOnHome=!1,this._homeLoading=!1,this._searchLoading=!1}connectedCallback(){super.connectedCallback(),document.addEventListener(`click`,this._onLocalePickerDocumentClick),document.addEventListener(`keydown`,this._onLocalePickerKeydown)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(`click`,this._onLocalePickerDocumentClick),document.removeEventListener(`keydown`,this._onLocalePickerKeydown),this._descEditor?.destroy(),this._descEditor=null,this._footerEditor?.destroy(),this._footerEditor=null}initData(e){this._siteName=e.siteName,this._siteDescription=e.siteDescription,this._siteFooter=e.siteFooter,this._siteLanguage=e.siteLanguage,this._cjkSerifFont=e.cjkSerifFont,this._timeZone=e.timeZone,this._origLocale={siteLanguage:e.siteLanguage,cjkSerifFont:e.cjkSerifFont,timeZone:e.timeZone},this._mainRssFeed=e.mainRssFeed,this._origMainRssFeed=e.mainRssFeed,this._showJantBrandingOnHome=e.showJantBrandingOnHome,this._origShowJantBrandingOnHome=e.showJantBrandingOnHome,this._noindex=e.noindex,this._origNoindex=e.noindex,this.updateComplete.then(()=>{this._initEditors(),this._origSite={siteName:e.siteName,siteDescription:this._siteDescription,siteFooter:this._siteFooter}})}sectionSaved(e){e===`site`?(this._origSite={siteName:this._siteName,siteDescription:this._siteDescription,siteFooter:this._siteFooter},this._siteDirty=!1,this._siteLoading=!1):e===`language-time`?(this._origLocale={siteLanguage:this._siteLanguage,cjkSerifFont:this._cjkSerifFont,timeZone:this._timeZone},this._localeDirty=!1,this._localeLoading=!1):e===`feeds`?(this._origMainRssFeed=this._mainRssFeed,this._feedDirty=!1,this._feedLoading=!1):e===`home`?(this._origShowJantBrandingOnHome=this._showJantBrandingOnHome,this._homeLoading=!1):e===`search`&&(this._origNoindex=this._noindex,this._searchLoading=!1)}sectionError(e){e===`site`?this._siteLoading=!1:e===`language-time`?this._localeLoading=!1:e===`feeds`?this._feedLoading=!1:e===`home`?(this._showJantBrandingOnHome=this._origShowJantBrandingOnHome,this._homeLoading=!1):e===`search`&&(this._noindex=this._origNoindex,this._searchLoading=!1)}_initEditors(){this._initDescEditor(),this._initFooterEditor()}_initDescEditor(){let e=this.querySelector(`[data-settings-desc-editor]`);if(!e||this._descEditor)return;this._descEditor=NT({element:e,placeholder:this.siteDescriptionFallback,content:this._siteDescription||void 0,onUpdate:e=>{this._siteDescription=e,this._syncSiteDirty()}}),this._siteDescription=MT(this._descEditor.getJSON());let t=e.querySelector(`.ProseMirror`);t&&(t.style.outline=`none`,t.style.minHeight=`3rem`)}_initFooterEditor(){let e=this.querySelector(`[data-settings-footer-editor]`);if(!e||this._footerEditor)return;this._footerEditor=NT({element:e,content:this._siteFooter||void 0,onUpdate:e=>{this._siteFooter=e,this._syncSiteDirty()}}),this._siteFooter=MT(this._footerEditor.getJSON());let t=e.querySelector(`.ProseMirror`);t&&(t.style.outline=`none`,t.style.minHeight=`6rem`)}_syncSiteDirty(){this._siteDirty=this._siteName!==this._origSite.siteName||this._siteDescription!==this._origSite.siteDescription||this._siteFooter!==this._origSite.siteFooter}_saveSite(){this._siteLoading||!this._siteDirty||(this._siteLoading=!0,this.dispatchEvent(new CustomEvent(`jant:settings-save`,{bubbles:!0,detail:{endpoint:`/settings/general`,data:{siteName:this._siteName,siteDescription:this._siteDescription,siteFooter:this._siteFooter},section:`site`}})))}_syncLocaleDirty(){this._localeDirty=this._siteLanguage!==this._origLocale.siteLanguage||this._cjkSerifFont!==this._origLocale.cjkSerifFont||this._timeZone!==this._origLocale.timeZone}_saveLocale(){this._localeLoading||!this._localeDirty||(this._localeLoading=!0,this.dispatchEvent(new CustomEvent(`jant:settings-save`,{bubbles:!0,detail:{endpoint:`/settings/general/language-time`,data:{siteLanguage:this._siteLanguage,cjkSerifFont:this._cjkSerifFont,timeZone:this._timeZone},section:`language-time`}})))}_filteredLocaleEntries(){let e=gz(),t=this._localeQuery.trim().toLowerCase();return t?e.filter(e=>e.tag.toLowerCase().includes(t)||e.native.toLowerCase().includes(t)||e.english.toLowerCase().includes(t)):e}_toggleLocalePicker=()=>{this._localeOpen=!this._localeOpen,this._localeOpen?this.updateComplete.then(()=>{this.querySelector(`[data-locale-search]`)?.focus()}):this._localeQuery=``};_selectLocale(e){this._siteLanguage=e,this._localeOpen=!1,this._localeQuery=``,this._syncLocaleDirty()}_onLocalePickerDocumentClick=e=>{if(!this._localeOpen)return;let t=e.target,n=this.querySelector(`[data-locale-picker]`);n&&t&&!n.contains(t)&&(this._localeOpen=!1)};_onLocalePickerKeydown=e=>{e.key===`Escape`&&this._localeOpen&&(this._localeOpen=!1,this._localeQuery=``)};_renderLanguagePicker(){let e=_z(this._siteLanguage||`en`),t=this._filteredLocaleEntries(),n=this.labels.siteLanguageSearchPlaceholder||`Search…`,r=this.labels.siteLanguageNoMatches||`No matches.`;return u`
|
|
2167
2167
|
<div class="relative" data-locale-picker>
|
|
2168
2168
|
<button
|
|
2169
2169
|
type="button"
|
|
@@ -4616,4 +4616,4 @@ Check the discardedTracks field for more info.`)}return e}async execute(){if(!th
|
|
|
4616
4616
|
`:this._query.trim()&&!this._loading?u`<div class="command-palette-empty">No results</div>`:g}
|
|
4617
4617
|
</div>
|
|
4618
4618
|
</dialog>
|
|
4619
|
-
`}};customElements.get(`jant-command-palette`)||customElements.define(`jant-command-palette`,iV),document.addEventListener(`keydown`,e=>{if(e.key.toLowerCase()!==`k`||!(e.metaKey||e.ctrlKey)||e.altKey||e.shiftKey||e.defaultPrevented||e.isComposing)return;e.preventDefault();let t=document.querySelector(`jant-command-palette`);if(t){if(t.querySelector(`dialog[open]`)){t.close();return}document.querySelector(`dialog[open]`)||t.open()}}),document.querySelector(`jant-compose-fullscreen`)||document.body.appendChild(document.createElement(`jant-compose-fullscreen`)),ye(),document.querySelector(`jant-command-palette`)||document.body.appendChild(document.createElement(`jant-command-palette`));
|
|
4619
|
+
`}};customElements.get(`jant-command-palette`)||customElements.define(`jant-command-palette`,iV),document.addEventListener(`keydown`,e=>{if(e.key.toLowerCase()!==`k`||!(e.metaKey||e.ctrlKey)||e.altKey||e.shiftKey||e.defaultPrevented||e.isComposing)return;e.preventDefault();let t=document.querySelector(`jant-command-palette`);if(t){if(t.querySelector(`dialog[open]`)){t.close();return}document.querySelector(`dialog[open]`)||t.open()}}),document.addEventListener(`click`,e=>{let t=e.target;if(!(t instanceof globalThis.Element)||!t.closest(`.site-header-search-link`)||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.button!==0||e.defaultPrevented)return;let n=document.querySelector(`jant-command-palette`);n&&(document.querySelector(`dialog[open]`)||(e.preventDefault(),n.open()))}),document.querySelector(`jant-compose-fullscreen`)||document.body.appendChild(document.createElement(`jant-compose-fullscreen`)),ye(),document.querySelector(`jant-command-palette`)||document.body.appendChild(document.createElement(`jant-command-palette`));
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ as url_exports } from "./url-umUptr5z.js";
|
|
2
|
-
import { A as NAV_ITEM_TYPES, C as toPostView, D as MAX_MEDIA_ATTACHMENTS, E as FORMATS, M as STATUSES, N as TEXT_ATTACHMENT_CONTENT_FORMATS, O as MAX_PINNED_POSTS, S as toNavItemViews, T as toSearchResultView, _ as createMediaContext, b as toMediaView, f as defaultFeedRenderer, j as SORT_ORDERS, k as MEDIA_KINDS, t as createApp, v as toArchiveGroups, w as toPostViews, x as toNavItemView, y as toArchiveGroupsWithMedia } from "./app-
|
|
2
|
+
import { A as NAV_ITEM_TYPES, C as toPostView, D as MAX_MEDIA_ATTACHMENTS, E as FORMATS, M as STATUSES, N as TEXT_ATTACHMENT_CONTENT_FORMATS, O as MAX_PINNED_POSTS, S as toNavItemViews, T as toSearchResultView, _ as createMediaContext, b as toMediaView, f as defaultFeedRenderer, j as SORT_ORDERS, k as MEDIA_KINDS, t as createApp, v as toArchiveGroups, w as toPostViews, x as toNavItemView, y as toArchiveGroupsWithMedia } from "./app-B9XQDSoB.js";
|
|
3
3
|
import { T as time_exports, a as markdown_exports } from "./export-ZBlfKSKm.js";
|
|
4
4
|
import "./env-CgaH9Mut.js";
|
|
5
5
|
import "./github-sync-bL1hnx3Q.js";
|
package/dist/node.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./url-umUptr5z.js";
|
|
2
|
-
import { F as BUILTIN_COLOR_THEMES, I as getPublicAssetBasePath, L as isAssetPath, P as buildThemeStyle, a as resolveDatabaseDialect, c as resolveConfig, d as getFontThemeCssVariables, g as schema_exports, h as createNodeDatabase, i as createSiteService, l as BUILTIN_FONT_THEMES, m as sqliteSchemaBundle, n as createNodeCliRuntime, o as getHostBasedStartupConfigurationIssues, p as pgSchemaBundle, r as createNodeRequestRuntime, s as createStorageDriver, t as createApp, u as getCjkSerifCssVariables } from "./app-
|
|
2
|
+
import { F as BUILTIN_COLOR_THEMES, I as getPublicAssetBasePath, L as isAssetPath, P as buildThemeStyle, a as resolveDatabaseDialect, c as resolveConfig, d as getFontThemeCssVariables, g as schema_exports, h as createNodeDatabase, i as createSiteService, l as BUILTIN_FONT_THEMES, m as sqliteSchemaBundle, n as createNodeCliRuntime, o as getHostBasedStartupConfigurationIssues, p as pgSchemaBundle, r as createNodeRequestRuntime, s as createStorageDriver, t as createApp, u as getCjkSerifCssVariables } from "./app-B9XQDSoB.js";
|
|
3
3
|
import { t as createExportService } from "./export-ZBlfKSKm.js";
|
|
4
4
|
import { b as getSiteResolutionMode, i as getConfiguredSingleSitePathPrefix, l as getEnvString, r as getConfiguredSingleSiteOrigin, x as shouldTrustProxy, y as getPort } from "./env-CgaH9Mut.js";
|
|
5
5
|
import "./github-sync-bL1hnx3Q.js";
|
|
@@ -474,7 +474,7 @@ async function createNodeRequestHandler(options) {
|
|
|
474
474
|
async function start(env = process.env, app) {
|
|
475
475
|
const handler = await createNodeRequestHandler({
|
|
476
476
|
env,
|
|
477
|
-
app: async () => app ?? (await import("./app-
|
|
477
|
+
app: async () => app ?? (await import("./app-CHW6VVQt.js")).createApp()
|
|
478
478
|
});
|
|
479
479
|
const hostname = resolveHost(env);
|
|
480
480
|
const port = resolvePort(env);
|
package/package.json
CHANGED
|
@@ -961,6 +961,11 @@ document.addEventListener("jant:compose-submit-deferred", async (e: Event) => {
|
|
|
961
961
|
`[data-post-view][data-post-view-id="${editPostId}"]`,
|
|
962
962
|
);
|
|
963
963
|
if (postView) {
|
|
964
|
+
if (newPath && newPath !== globalThis.location.pathname) {
|
|
965
|
+
queueSuccessToast("Post updated.");
|
|
966
|
+
globalThis.location.assign(newPath);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
964
969
|
const refreshed = await refreshPostPageView(editPostId);
|
|
965
970
|
if (refreshed) {
|
|
966
971
|
toastMsg("Post updated.");
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header search-link click handler (auth-only).
|
|
3
|
+
*
|
|
4
|
+
* Intercepts clicks on `.site-header-search-link` (the magnifying-glass icon
|
|
5
|
+
* shown when the inline search form collapses) and opens the command palette
|
|
6
|
+
* instead of navigating to /search. The anchor's `href` remains a working
|
|
7
|
+
* fallback for anonymous visitors (this script is not loaded for them) and
|
|
8
|
+
* for modifier-clicks that should open a new tab.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { JantCommandPalette } from "./components/jant-command-palette.js";
|
|
12
|
+
|
|
13
|
+
document.addEventListener("click", (event: globalThis.MouseEvent) => {
|
|
14
|
+
const target = event.target;
|
|
15
|
+
if (!(target instanceof globalThis.Element)) return;
|
|
16
|
+
|
|
17
|
+
const link = target.closest<HTMLAnchorElement>(".site-header-search-link");
|
|
18
|
+
if (!link) return;
|
|
19
|
+
|
|
20
|
+
// Let modifier-clicks fall through (new tab/window, save link, etc.)
|
|
21
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
22
|
+
if (event.button !== 0) return;
|
|
23
|
+
if (event.defaultPrevented) return;
|
|
24
|
+
|
|
25
|
+
const palette = document.querySelector<JantCommandPalette>(
|
|
26
|
+
"jant-command-palette",
|
|
27
|
+
);
|
|
28
|
+
if (!palette) return;
|
|
29
|
+
|
|
30
|
+
// Skip if another dialog (compose, confirm) is already open
|
|
31
|
+
if (document.querySelector("dialog[open]")) return;
|
|
32
|
+
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
void palette.open();
|
|
35
|
+
});
|
package/src/client-auth.ts
CHANGED
|
@@ -31,6 +31,7 @@ import "./client/collection-page-actions.js";
|
|
|
31
31
|
import "./client/custom-url-menu.js";
|
|
32
32
|
import "./client/components/jant-command-palette.js";
|
|
33
33
|
import "./client/palette-shortcuts.js";
|
|
34
|
+
import "./client/palette-search-trigger.js";
|
|
34
35
|
|
|
35
36
|
// Mount fullscreen overlay at body level to escape the dialog's containing
|
|
36
37
|
// block (dialog animation creates a containing block that traps fixed children).
|
|
@@ -27,7 +27,7 @@ const aliasDomain: SiteDomain = {
|
|
|
27
27
|
describe("hosted canonical redirects", () => {
|
|
28
28
|
it("bypasses hosted redirects for admin and auth paths", () => {
|
|
29
29
|
expect(
|
|
30
|
-
shouldBypassHostedCanonicalRedirect("/.well-known/jant-
|
|
30
|
+
shouldBypassHostedCanonicalRedirect("/.well-known/jant-verification"),
|
|
31
31
|
).toBe(true);
|
|
32
32
|
expect(shouldBypassHostedCanonicalRedirect("/signin")).toBe(true);
|
|
33
33
|
expect(shouldBypassHostedCanonicalRedirect("/settings/account")).toBe(true);
|
|
@@ -1,47 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
const VERIFICATION_HMAC_VERSION = "v1";
|
|
3
2
|
const textEncoder = new TextEncoder();
|
|
4
|
-
const textDecoder = new TextDecoder();
|
|
5
|
-
|
|
6
|
-
export interface HostedDomainCheckClaims {
|
|
7
|
-
aud: "jant-cloud";
|
|
8
|
-
domainId: string;
|
|
9
|
-
host: string;
|
|
10
|
-
iat: number;
|
|
11
|
-
iss: "jant-core";
|
|
12
|
-
nonce: string;
|
|
13
|
-
}
|
|
14
3
|
|
|
15
|
-
function
|
|
16
|
-
let
|
|
4
|
+
function bytesToHex(bytes: Uint8Array): string {
|
|
5
|
+
let out = "";
|
|
17
6
|
for (const byte of bytes) {
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return btoa(binary)
|
|
22
|
-
.replace(/\+/g, "-")
|
|
23
|
-
.replace(/\//g, "_")
|
|
24
|
-
.replace(/=+$/g, "");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function fromBase64Url(value: string): Uint8Array {
|
|
28
|
-
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
29
|
-
const padding =
|
|
30
|
-
normalized.length % 4 === 0 ? "" : "=".repeat(4 - (normalized.length % 4));
|
|
31
|
-
const binary = atob(`${normalized}${padding}`);
|
|
32
|
-
const bytes = new Uint8Array(binary.length);
|
|
33
|
-
|
|
34
|
-
for (let index = 0; index < binary.length; index += 1) {
|
|
35
|
-
bytes[index] = binary.charCodeAt(index);
|
|
7
|
+
out += byte.toString(16).padStart(2, "0");
|
|
36
8
|
}
|
|
37
|
-
|
|
38
|
-
return bytes;
|
|
9
|
+
return out;
|
|
39
10
|
}
|
|
40
11
|
|
|
41
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Compute the plaintext HMAC token returned from
|
|
14
|
+
* `/.well-known/jant-verification`.
|
|
15
|
+
*
|
|
16
|
+
* The control plane sends a nonce in the query string; the site replies with
|
|
17
|
+
* `jant-verification=<hex>` where `<hex>` is `HMAC-SHA256(secret, payload)`
|
|
18
|
+
* over `payload = "v1:" + host + ":" + nonce`. The shared secret is
|
|
19
|
+
* `HOSTED_CONTROL_PLANE_DOMAIN_CHECK_SECRET`.
|
|
20
|
+
*/
|
|
21
|
+
export async function computeHostedVerificationToken(
|
|
42
22
|
secret: string,
|
|
43
|
-
|
|
44
|
-
|
|
23
|
+
host: string,
|
|
24
|
+
nonce: string,
|
|
25
|
+
): Promise<string> {
|
|
45
26
|
const key = await crypto.subtle.importKey(
|
|
46
27
|
"raw",
|
|
47
28
|
textEncoder.encode(secret),
|
|
@@ -50,49 +31,9 @@ async function createHmacSignature(
|
|
|
50
31
|
["sign"],
|
|
51
32
|
);
|
|
52
33
|
|
|
53
|
-
|
|
34
|
+
const payload = `${VERIFICATION_HMAC_VERSION}:${host.trim().toLowerCase()}:${nonce}`;
|
|
35
|
+
const signature = new Uint8Array(
|
|
54
36
|
await crypto.subtle.sign("HMAC", key, textEncoder.encode(payload)),
|
|
55
37
|
);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
export async function signHostedDomainCheckToken(
|
|
59
|
-
secret: string,
|
|
60
|
-
claims: HostedDomainCheckClaims,
|
|
61
|
-
): Promise<string> {
|
|
62
|
-
const payload = toBase64Url(textEncoder.encode(JSON.stringify(claims)));
|
|
63
|
-
const signature = await createHmacSignature(secret, payload);
|
|
64
|
-
return `${payload}.${toBase64Url(signature)}`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function verifyHostedDomainCheckToken(
|
|
68
|
-
secret: string,
|
|
69
|
-
token: string,
|
|
70
|
-
): Promise<HostedDomainCheckClaims> {
|
|
71
|
-
const [payloadPart, signaturePart, ...rest] = token.split(".");
|
|
72
|
-
if (!payloadPart || !signaturePart || rest.length > 0) {
|
|
73
|
-
throw new Error("Malformed hosted domain check token.");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const expectedSignature = await createHmacSignature(secret, payloadPart);
|
|
77
|
-
const providedSignature = fromBase64Url(signaturePart);
|
|
78
|
-
if (!timingSafeEqualBytes(expectedSignature, providedSignature)) {
|
|
79
|
-
throw new Error("Invalid hosted domain check token signature.");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const claims = JSON.parse(
|
|
83
|
-
textDecoder.decode(fromBase64Url(payloadPart)),
|
|
84
|
-
) as Partial<HostedDomainCheckClaims>;
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
claims.iss !== "jant-core" ||
|
|
88
|
-
claims.aud !== "jant-cloud" ||
|
|
89
|
-
typeof claims.host !== "string" ||
|
|
90
|
-
typeof claims.domainId !== "string" ||
|
|
91
|
-
typeof claims.nonce !== "string" ||
|
|
92
|
-
typeof claims.iat !== "number"
|
|
93
|
-
) {
|
|
94
|
-
throw new Error("Invalid hosted domain check token payload.");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return claims as HostedDomainCheckClaims;
|
|
38
|
+
return bytesToHex(signature);
|
|
98
39
|
}
|
package/src/lib/hosted-domain.ts
CHANGED
|
@@ -143,6 +143,88 @@ describe("Internal site admin routes", () => {
|
|
|
143
143
|
]);
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
+
it("reports an unused key as available", async () => {
|
|
147
|
+
const { app } = createTestApp({
|
|
148
|
+
authenticated: false,
|
|
149
|
+
internalAdminToken: "internal-secret",
|
|
150
|
+
siteResolutionMode: "host-based",
|
|
151
|
+
});
|
|
152
|
+
app.route("/api/internal/sites", internalSitesRoutes);
|
|
153
|
+
|
|
154
|
+
const res = await app.request(
|
|
155
|
+
"/api/internal/sites/availability?key=Fresh-Key",
|
|
156
|
+
{
|
|
157
|
+
headers: { Authorization: "Bearer internal-secret" },
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
expect(res.status).toBe(200);
|
|
162
|
+
expect(await res.json()).toEqual({ available: true, key: "fresh-key" });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("reports an existing key as unavailable", async () => {
|
|
166
|
+
const { app } = createTestApp({
|
|
167
|
+
authenticated: false,
|
|
168
|
+
internalAdminToken: "internal-secret",
|
|
169
|
+
siteResolutionMode: "host-based",
|
|
170
|
+
});
|
|
171
|
+
app.route("/api/internal/sites", internalSitesRoutes);
|
|
172
|
+
|
|
173
|
+
const createRes = await app.request("/api/internal/sites", {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: {
|
|
176
|
+
Authorization: "Bearer internal-secret",
|
|
177
|
+
"Content-Type": "application/json",
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify({
|
|
180
|
+
key: "taken-key",
|
|
181
|
+
primaryHost: "taken-key.example.com",
|
|
182
|
+
siteName: "Taken Key",
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
expect(createRes.status).toBe(201);
|
|
186
|
+
|
|
187
|
+
const res = await app.request(
|
|
188
|
+
"/api/internal/sites/availability?key=taken-key",
|
|
189
|
+
{
|
|
190
|
+
headers: { Authorization: "Bearer internal-secret" },
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(res.status).toBe(200);
|
|
195
|
+
expect(await res.json()).toEqual({ available: false, key: "taken-key" });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("rejects availability checks without an admin token", async () => {
|
|
199
|
+
const { app } = createTestApp({
|
|
200
|
+
authenticated: false,
|
|
201
|
+
internalAdminToken: "internal-secret",
|
|
202
|
+
siteResolutionMode: "host-based",
|
|
203
|
+
});
|
|
204
|
+
app.route("/api/internal/sites", internalSitesRoutes);
|
|
205
|
+
|
|
206
|
+
const res = await app.request(
|
|
207
|
+
"/api/internal/sites/availability?key=any-key",
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(res.status).toBe(401);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("rejects availability checks with an invalid key", async () => {
|
|
214
|
+
const { app } = createTestApp({
|
|
215
|
+
authenticated: false,
|
|
216
|
+
internalAdminToken: "internal-secret",
|
|
217
|
+
siteResolutionMode: "host-based",
|
|
218
|
+
});
|
|
219
|
+
app.route("/api/internal/sites", internalSitesRoutes);
|
|
220
|
+
|
|
221
|
+
const res = await app.request("/api/internal/sites/availability?key=ab", {
|
|
222
|
+
headers: { Authorization: "Bearer internal-secret" },
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
expect(res.status).toBe(400);
|
|
226
|
+
});
|
|
227
|
+
|
|
146
228
|
it("returns managed site media usage in host-based mode", async () => {
|
|
147
229
|
const { app, services } = createTestApp({
|
|
148
230
|
authenticated: false,
|
|
@@ -512,4 +594,90 @@ describe("Internal site admin routes", () => {
|
|
|
512
594
|
],
|
|
513
595
|
});
|
|
514
596
|
});
|
|
597
|
+
|
|
598
|
+
it("leaves the demoted alias serving directly when adding a new primary", async () => {
|
|
599
|
+
const { app } = createTestApp({
|
|
600
|
+
authenticated: false,
|
|
601
|
+
internalAdminToken: "internal-secret",
|
|
602
|
+
siteResolutionMode: "host-based",
|
|
603
|
+
});
|
|
604
|
+
app.route("/api/internal/sites", internalSitesRoutes);
|
|
605
|
+
|
|
606
|
+
const createRes = await app.request("/api/internal/sites", {
|
|
607
|
+
method: "POST",
|
|
608
|
+
headers: {
|
|
609
|
+
Authorization: "Bearer internal-secret",
|
|
610
|
+
"Content-Type": "application/json",
|
|
611
|
+
},
|
|
612
|
+
body: JSON.stringify({
|
|
613
|
+
key: "redirect-demo",
|
|
614
|
+
primaryHost: "redirect-demo.jant.blog",
|
|
615
|
+
siteName: "Redirect Demo",
|
|
616
|
+
}),
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
expect(createRes.status).toBe(201);
|
|
620
|
+
const created = (await createRes.json()) as { siteId: string };
|
|
621
|
+
|
|
622
|
+
const addRes = await app.request(
|
|
623
|
+
`/api/internal/sites/${created.siteId}/domains`,
|
|
624
|
+
{
|
|
625
|
+
method: "POST",
|
|
626
|
+
headers: {
|
|
627
|
+
Authorization: "Bearer internal-secret",
|
|
628
|
+
"Content-Type": "application/json",
|
|
629
|
+
},
|
|
630
|
+
body: JSON.stringify({
|
|
631
|
+
host: "blog.example.com",
|
|
632
|
+
makePrimary: true,
|
|
633
|
+
}),
|
|
634
|
+
},
|
|
635
|
+
);
|
|
636
|
+
|
|
637
|
+
expect(addRes.status).toBe(201);
|
|
638
|
+
const addedBody = (await addRes.json()) as {
|
|
639
|
+
domains: Array<{
|
|
640
|
+
host: string;
|
|
641
|
+
id: string;
|
|
642
|
+
kind: string;
|
|
643
|
+
redirectToPrimary: boolean;
|
|
644
|
+
}>;
|
|
645
|
+
};
|
|
646
|
+
const newPrimary = addedBody.domains.find(
|
|
647
|
+
(domain) => domain.host === "blog.example.com",
|
|
648
|
+
);
|
|
649
|
+
const demotedAlias = addedBody.domains.find(
|
|
650
|
+
(domain) => domain.host === "redirect-demo.jant.blog",
|
|
651
|
+
);
|
|
652
|
+
expect(newPrimary?.kind).toBe("primary");
|
|
653
|
+
expect(demotedAlias?.kind).toBe("alias");
|
|
654
|
+
// The demoted managed host must keep serving its own content while the
|
|
655
|
+
// newly-added custom primary's DNS is still propagating.
|
|
656
|
+
expect(demotedAlias?.redirectToPrimary).toBe(false);
|
|
657
|
+
|
|
658
|
+
const flipRes = await app.request(
|
|
659
|
+
`/api/internal/sites/${created.siteId}/domains/${demotedAlias?.id}/redirect`,
|
|
660
|
+
{
|
|
661
|
+
method: "POST",
|
|
662
|
+
headers: {
|
|
663
|
+
Authorization: "Bearer internal-secret",
|
|
664
|
+
"Content-Type": "application/json",
|
|
665
|
+
},
|
|
666
|
+
body: JSON.stringify({ redirectToPrimary: true }),
|
|
667
|
+
},
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
expect(flipRes.status).toBe(200);
|
|
671
|
+
const flipBody = (await flipRes.json()) as {
|
|
672
|
+
domains: Array<{
|
|
673
|
+
host: string;
|
|
674
|
+
id: string;
|
|
675
|
+
redirectToPrimary: boolean;
|
|
676
|
+
}>;
|
|
677
|
+
};
|
|
678
|
+
const aliasAfterFlip = flipBody.domains.find(
|
|
679
|
+
(domain) => domain.id === demotedAlias?.id,
|
|
680
|
+
);
|
|
681
|
+
expect(aliasAfterFlip?.redirectToPrimary).toBe(true);
|
|
682
|
+
});
|
|
515
683
|
});
|
|
@@ -9,17 +9,23 @@ import type { AppVariables } from "../../../types/app-context.js";
|
|
|
9
9
|
|
|
10
10
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
11
11
|
|
|
12
|
+
const ManagedSiteKeySchema = z
|
|
13
|
+
.string()
|
|
14
|
+
.trim()
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.min(3)
|
|
17
|
+
.max(40)
|
|
18
|
+
.regex(
|
|
19
|
+
/^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])?$/,
|
|
20
|
+
"Site key must use lowercase letters, numbers, or hyphens.",
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const SiteKeyAvailabilityQuerySchema = z.object({
|
|
24
|
+
key: ManagedSiteKeySchema,
|
|
25
|
+
});
|
|
26
|
+
|
|
12
27
|
const CreateManagedSiteSchema = z.object({
|
|
13
|
-
key:
|
|
14
|
-
.string()
|
|
15
|
-
.trim()
|
|
16
|
-
.toLowerCase()
|
|
17
|
-
.min(3)
|
|
18
|
-
.max(40)
|
|
19
|
-
.regex(
|
|
20
|
-
/^[a-z0-9](?:[a-z0-9-]{1,38}[a-z0-9])?$/,
|
|
21
|
-
"Site key must use lowercase letters, numbers, or hyphens.",
|
|
22
|
-
),
|
|
28
|
+
key: ManagedSiteKeySchema,
|
|
23
29
|
primaryHost: z
|
|
24
30
|
.string()
|
|
25
31
|
.trim()
|
|
@@ -76,6 +82,23 @@ internalSitesRoutes.post("/", requireInternalAdminApi(), async (c) => {
|
|
|
76
82
|
);
|
|
77
83
|
});
|
|
78
84
|
|
|
85
|
+
internalSitesRoutes.get(
|
|
86
|
+
"/availability",
|
|
87
|
+
requireInternalAdminApi(),
|
|
88
|
+
async (c) => {
|
|
89
|
+
assertHostBasedMode(c.env);
|
|
90
|
+
|
|
91
|
+
const query = parseValidated(SiteKeyAvailabilityQuerySchema, {
|
|
92
|
+
key: c.req.query("key") ?? "",
|
|
93
|
+
});
|
|
94
|
+
const result = await c.var.services.siteAdmin.isManagedSiteKeyAvailable(
|
|
95
|
+
query.key,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return c.json(result);
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
79
102
|
internalSitesRoutes.delete("/:siteId", requireInternalAdminApi(), async (c) => {
|
|
80
103
|
assertHostBasedMode(c.env);
|
|
81
104
|
|
|
@@ -222,6 +245,36 @@ internalSitesRoutes.post(
|
|
|
222
245
|
},
|
|
223
246
|
);
|
|
224
247
|
|
|
248
|
+
const ManagedSiteDomainRedirectSchema = z.object({
|
|
249
|
+
redirectToPrimary: z.boolean(),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
internalSitesRoutes.post(
|
|
253
|
+
"/:siteId/domains/:domainId/redirect",
|
|
254
|
+
requireInternalAdminApi(),
|
|
255
|
+
async (c) => {
|
|
256
|
+
assertHostBasedMode(c.env);
|
|
257
|
+
const body = parseValidated(
|
|
258
|
+
ManagedSiteDomainRedirectSchema,
|
|
259
|
+
await c.req.json(),
|
|
260
|
+
);
|
|
261
|
+
const domains = await c.var.services.siteAdmin.setManagedSiteDomainRedirect(
|
|
262
|
+
c.req.param("siteId"),
|
|
263
|
+
c.req.param("domainId"),
|
|
264
|
+
body.redirectToPrimary,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return c.json({
|
|
268
|
+
domains: domains.map((domain) => ({
|
|
269
|
+
host: domain.host,
|
|
270
|
+
id: domain.id,
|
|
271
|
+
kind: domain.kind,
|
|
272
|
+
redirectToPrimary: domain.redirectToPrimary,
|
|
273
|
+
})),
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
|
|
225
278
|
internalSitesRoutes.delete(
|
|
226
279
|
"/:siteId/domains/:domainId",
|
|
227
280
|
requireInternalAdminApi(),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
import { errorHandler } from "../../../middleware/error-handler.js";
|
|
4
|
-
import {
|
|
4
|
+
import { computeHostedVerificationToken } from "../../../lib/hosted-domain-check.js";
|
|
5
5
|
import { hostedDomainCheckRoutes } from "../domain-check.js";
|
|
6
6
|
import type { Bindings } from "../../../types.js";
|
|
7
7
|
import type { AppVariables } from "../../../types/app-context.js";
|
|
@@ -42,12 +42,12 @@ function createHostedDomainCheckTestApp(options?: {
|
|
|
42
42
|
return app;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
describe("hostedDomainCheckRoutes", () => {
|
|
45
|
+
describe("hostedDomainCheckRoutes (jant-verification)", () => {
|
|
46
46
|
it("returns 404 when the domain check secret is not configured", async () => {
|
|
47
47
|
const app = createHostedDomainCheckTestApp();
|
|
48
48
|
|
|
49
49
|
const response = await app.request(
|
|
50
|
-
"/.well-known/jant-
|
|
50
|
+
"/.well-known/jant-verification?nonce=test-nonce",
|
|
51
51
|
);
|
|
52
52
|
|
|
53
53
|
expect(response.status).toBe(404);
|
|
@@ -59,36 +59,47 @@ describe("hostedDomainCheckRoutes", () => {
|
|
|
59
59
|
secret: "cloud-domain-check-secret-cloud-domain-check-secret",
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
const response = await app.request("/.well-known/jant-
|
|
62
|
+
const response = await app.request("/.well-known/jant-verification");
|
|
63
63
|
|
|
64
64
|
expect(response.status).toBe(400);
|
|
65
|
-
await expect(response.
|
|
66
|
-
error: "Missing nonce.",
|
|
67
|
-
});
|
|
65
|
+
await expect(response.text()).resolves.toBe("Missing nonce.");
|
|
68
66
|
});
|
|
69
67
|
|
|
70
|
-
it("returns
|
|
68
|
+
it("returns the plaintext HMAC token for the current hosted domain", async () => {
|
|
71
69
|
const secret = "cloud-domain-check-secret-cloud-domain-check-secret";
|
|
70
|
+
const host = "blog.example.com";
|
|
71
|
+
const nonce = "test-nonce";
|
|
72
72
|
const app = createHostedDomainCheckTestApp({
|
|
73
73
|
domainId: "sdom_custom",
|
|
74
|
-
host
|
|
74
|
+
host,
|
|
75
75
|
secret,
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
const response = await app.request(
|
|
79
|
-
|
|
79
|
+
`/.well-known/jant-verification?nonce=${nonce}`,
|
|
80
80
|
);
|
|
81
|
-
const body = (await response.
|
|
82
|
-
const
|
|
81
|
+
const body = (await response.text()).trim();
|
|
82
|
+
const expected = await computeHostedVerificationToken(secret, host, nonce);
|
|
83
83
|
|
|
84
84
|
expect(response.status).toBe(200);
|
|
85
85
|
expect(response.headers.get("cache-control")).toBe("no-store");
|
|
86
|
-
expect(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
expect(response.headers.get("content-type")).toMatch(/^text\/plain/);
|
|
87
|
+
expect(body).toBe(`jant-verification=${expected}`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("normalizes host casing when computing the token", async () => {
|
|
91
|
+
const secret = "cloud-domain-check-secret-cloud-domain-check-secret";
|
|
92
|
+
const nonce = "abc";
|
|
93
|
+
const lower = await computeHostedVerificationToken(
|
|
94
|
+
secret,
|
|
95
|
+
"blog.example.com",
|
|
96
|
+
nonce,
|
|
97
|
+
);
|
|
98
|
+
const mixed = await computeHostedVerificationToken(
|
|
99
|
+
secret,
|
|
100
|
+
"Blog.Example.COM",
|
|
101
|
+
nonce,
|
|
102
|
+
);
|
|
103
|
+
expect(lower).toBe(mixed);
|
|
93
104
|
});
|
|
94
105
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { getHostedControlPlaneDomainCheckSecret } from "../../lib/env.js";
|
|
3
|
-
import {
|
|
3
|
+
import { computeHostedVerificationToken } from "../../lib/hosted-domain-check.js";
|
|
4
4
|
import { NotFoundError } from "../../lib/errors.js";
|
|
5
5
|
import type { Bindings } from "../../types.js";
|
|
6
6
|
import type { AppVariables } from "../../types/app-context.js";
|
|
@@ -9,31 +9,26 @@ type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
|
9
9
|
|
|
10
10
|
export const hostedDomainCheckRoutes = new Hono<Env>();
|
|
11
11
|
|
|
12
|
-
hostedDomainCheckRoutes.get("/.well-known/jant-
|
|
12
|
+
hostedDomainCheckRoutes.get("/.well-known/jant-verification", async (c) => {
|
|
13
13
|
const secret = getHostedControlPlaneDomainCheckSecret(c.env);
|
|
14
14
|
if (!secret) {
|
|
15
|
-
throw new NotFoundError("Hosted domain
|
|
15
|
+
throw new NotFoundError("Hosted domain verification endpoint");
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
if (!c.var.currentSiteDomain) {
|
|
19
|
-
throw new NotFoundError("Hosted domain
|
|
19
|
+
throw new NotFoundError("Hosted domain verification endpoint");
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const nonce = c.req.query("nonce")?.trim();
|
|
23
23
|
if (!nonce) {
|
|
24
|
-
return c.
|
|
24
|
+
return c.text("Missing nonce.", 400);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
domainId: c.var.currentSiteDomain.id,
|
|
30
|
-
host: c.var.currentSiteDomain.host.trim().toLowerCase(),
|
|
31
|
-
iat: Math.floor(Date.now() / 1000),
|
|
32
|
-
iss: "jant-core",
|
|
33
|
-
nonce,
|
|
34
|
-
});
|
|
27
|
+
const host = c.var.currentSiteDomain.host.trim().toLowerCase();
|
|
28
|
+
const token = await computeHostedVerificationToken(secret, host, nonce);
|
|
35
29
|
|
|
36
|
-
return c.
|
|
30
|
+
return c.text(`jant-verification=${token}\n`, 200, {
|
|
37
31
|
"Cache-Control": "no-store",
|
|
32
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
38
33
|
});
|
|
39
34
|
});
|
|
@@ -87,12 +87,26 @@ export interface ManagedSiteMediaUsageResult {
|
|
|
87
87
|
siteId: string;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
export interface ManagedSiteKeyAvailabilityResult {
|
|
91
|
+
available: boolean;
|
|
92
|
+
key: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
export interface SiteAdminService {
|
|
91
96
|
addManagedSiteDomain(
|
|
92
97
|
siteId: string,
|
|
93
98
|
input: ManageManagedSiteDomainInput,
|
|
94
99
|
): Promise<SiteDomain[]>;
|
|
95
100
|
createManagedSite(input: CreateManagedSiteInput): Promise<ManagedSiteResult>;
|
|
101
|
+
/**
|
|
102
|
+
* Lookup whether the given site key is free in `site`. Returns the
|
|
103
|
+
* normalized key so the caller can confirm what was checked. Used by the
|
|
104
|
+
* control plane before reserving a cloud_site row, so the user sees a
|
|
105
|
+
* conflict on the form instead of after provisioning.
|
|
106
|
+
*/
|
|
107
|
+
isManagedSiteKeyAvailable(
|
|
108
|
+
key: string,
|
|
109
|
+
): Promise<ManagedSiteKeyAvailabilityResult>;
|
|
96
110
|
exportManagedSite(
|
|
97
111
|
siteId: string,
|
|
98
112
|
deps: ExportManagedSiteDeps,
|
|
@@ -115,6 +129,11 @@ export interface SiteAdminService {
|
|
|
115
129
|
siteId: string,
|
|
116
130
|
domainId: string,
|
|
117
131
|
): Promise<SiteDomain[]>;
|
|
132
|
+
setManagedSiteDomainRedirect(
|
|
133
|
+
siteId: string,
|
|
134
|
+
domainId: string,
|
|
135
|
+
redirectToPrimary: boolean,
|
|
136
|
+
): Promise<SiteDomain[]>;
|
|
118
137
|
}
|
|
119
138
|
|
|
120
139
|
export interface SiteAdminServiceConfig {
|
|
@@ -604,6 +623,16 @@ export function createSiteAdminService(
|
|
|
604
623
|
|
|
605
624
|
return createWithDatabase(db, input);
|
|
606
625
|
},
|
|
626
|
+
async isManagedSiteKeyAvailable(key) {
|
|
627
|
+
assertManagedSiteOperationsEnabled();
|
|
628
|
+
const normalizedKey = key.trim();
|
|
629
|
+
const existing = await db
|
|
630
|
+
.select({ id: sites.id })
|
|
631
|
+
.from(sites)
|
|
632
|
+
.where(eq(sites.key, normalizedKey))
|
|
633
|
+
.limit(1);
|
|
634
|
+
return { available: !existing[0], key: normalizedKey };
|
|
635
|
+
},
|
|
607
636
|
async getManagedSiteMediaUsage(siteId) {
|
|
608
637
|
assertManagedSiteOperationsEnabled();
|
|
609
638
|
return getManagedSiteMediaUsage(siteId);
|
|
@@ -819,11 +848,16 @@ export function createSiteAdminService(
|
|
|
819
848
|
|
|
820
849
|
const timestamp = now();
|
|
821
850
|
if (input.makePrimary) {
|
|
851
|
+
// Newly-added primaries (e.g. custom domains) are unverified at this
|
|
852
|
+
// point. Leave any demoted alias serving directly so the site stays
|
|
853
|
+
// reachable while the new primary's DNS propagates. The caller is
|
|
854
|
+
// expected to flip redirectToPrimary back on once the new primary is
|
|
855
|
+
// confirmed to work.
|
|
822
856
|
await targetDb
|
|
823
857
|
.update(siteDomains)
|
|
824
858
|
.set({
|
|
825
859
|
kind: "alias",
|
|
826
|
-
redirectToPrimary:
|
|
860
|
+
redirectToPrimary: false,
|
|
827
861
|
updatedAt: timestamp,
|
|
828
862
|
})
|
|
829
863
|
.where(eq(siteDomains.siteId, normalizedSiteId));
|
|
@@ -841,6 +875,33 @@ export function createSiteAdminService(
|
|
|
841
875
|
});
|
|
842
876
|
});
|
|
843
877
|
},
|
|
878
|
+
async setManagedSiteDomainRedirect(siteId, domainId, redirectToPrimary) {
|
|
879
|
+
assertManagedSiteOperationsEnabled();
|
|
880
|
+
return mutateSiteDomains(siteId, async (targetDb, normalizedSiteId) => {
|
|
881
|
+
await requireSite(targetDb, normalizedSiteId);
|
|
882
|
+
|
|
883
|
+
const normalizedDomainId = domainId.trim();
|
|
884
|
+
const current = await targetDb
|
|
885
|
+
.select({ id: siteDomains.id })
|
|
886
|
+
.from(siteDomains)
|
|
887
|
+
.where(
|
|
888
|
+
sql`${siteDomains.id} = ${normalizedDomainId} AND ${siteDomains.siteId} = ${normalizedSiteId}`,
|
|
889
|
+
)
|
|
890
|
+
.limit(1);
|
|
891
|
+
if (!current[0]) {
|
|
892
|
+
throw new NotFoundError("Site domain");
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const timestamp = now();
|
|
896
|
+
await targetDb
|
|
897
|
+
.update(siteDomains)
|
|
898
|
+
.set({
|
|
899
|
+
redirectToPrimary,
|
|
900
|
+
updatedAt: timestamp,
|
|
901
|
+
})
|
|
902
|
+
.where(eq(siteDomains.id, normalizedDomainId));
|
|
903
|
+
});
|
|
904
|
+
},
|
|
844
905
|
async setManagedSitePrimaryDomain(siteId, domainId) {
|
|
845
906
|
assertManagedSiteOperationsEnabled();
|
|
846
907
|
return mutateSiteDomains(siteId, async (targetDb, normalizedSiteId) => {
|