@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.
@@ -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.3.50-947a76e8ff575c8d";
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-Ce5WEAVS.js";
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.3.50-947a76e8ff575c8d";
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
- new TextDecoder();
6443
- function toBase64Url$1(bytes) {
6444
- let binary = "";
6445
- for (const byte of bytes) binary += String.fromCharCode(byte);
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
- async function createHmacSignature$1(secret, payload) {
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
- return new Uint8Array(await crypto.subtle.sign("HMAC", key, textEncoder$3.encode(payload)));
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-domain-check", async (c) => {
6427
+ hostedDomainCheckRoutes.get("/.well-known/jant-verification", async (c) => {
6463
6428
  const secret = getHostedControlPlaneDomainCheckSecret(c.env);
6464
- if (!secret) throw new NotFoundError("Hosted domain check endpoint");
6465
- if (!c.var.currentSiteDomain) throw new NotFoundError("Hosted domain check endpoint");
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.json({ error: "Missing nonce." }, 400);
6468
- const token = await signHostedDomainCheckToken(secret, {
6469
- aud: "jant-cloud",
6470
- domainId: c.var.currentSiteDomain.id,
6471
- host: c.var.currentSiteDomain.host.trim().toLowerCase(),
6472
- iat: Math.floor(Date.now() / 1e3),
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: 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."),
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-domain-check",
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: true,
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) => {
@@ -1,5 +1,5 @@
1
1
  import "./url-umUptr5z.js";
2
- import { t as createApp } from "./app-C7CtIQM-.js";
2
+ import { t as createApp } from "./app-B9XQDSoB.js";
3
3
  import "./export-ZBlfKSKm.js";
4
4
  import "./env-CgaH9Mut.js";
5
5
  import "./github-sync-bL1hnx3Q.js";
@@ -146,7 +146,7 @@
146
146
  "name": "url"
147
147
  },
148
148
  "src/client-auth.ts": {
149
- "file": "_assets/client-auth-Ce5WEAVS.js",
149
+ "file": "_assets/client-auth-DFDajqqT.js",
150
150
  "name": "client-auth",
151
151
  "src": "src/client-auth.ts",
152
152
  "isEntry": true,
@@ -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-C7CtIQM-.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-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-C7CtIQM-.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-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-CIx9SSOi.js")).createApp()
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jant/core",
3
- "version": "0.3.50",
3
+ "version": "0.4.0",
4
4
  "description": "A modern, open-source microblogging platform built on Cloudflare Workers",
5
5
  "type": "module",
6
6
  "exports": {
@@ -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
+ });
@@ -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-domain-check"),
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
- import { timingSafeEqualBytes } from "./crypto.js";
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 toBase64Url(bytes: Uint8Array): string {
16
- let binary = "";
4
+ function bytesToHex(bytes: Uint8Array): string {
5
+ let out = "";
17
6
  for (const byte of bytes) {
18
- binary += String.fromCharCode(byte);
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
- async function createHmacSignature(
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
- payload: string,
44
- ): Promise<Uint8Array> {
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
- return new Uint8Array(
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
  }
@@ -2,7 +2,7 @@ import type { Site, SiteDomain } from "../types.js";
2
2
  import type { Services } from "../services/index.js";
3
3
 
4
4
  const CANONICAL_REDIRECT_BYPASS_PREFIXES = [
5
- "/.well-known/jant-domain-check",
5
+ "/.well-known/jant-verification",
6
6
  "/__dev",
7
7
  "/__sso",
8
8
  "/api/",
@@ -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: z
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 { verifyHostedDomainCheckToken } from "../../../lib/hosted-domain-check.js";
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-domain-check?nonce=test-nonce",
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-domain-check");
62
+ const response = await app.request("/.well-known/jant-verification");
63
63
 
64
64
  expect(response.status).toBe(400);
65
- await expect(response.json()).resolves.toEqual({
66
- error: "Missing nonce.",
67
- });
65
+ await expect(response.text()).resolves.toBe("Missing nonce.");
68
66
  });
69
67
 
70
- it("returns a signed token for the current hosted domain", async () => {
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: "blog.example.com",
74
+ host,
75
75
  secret,
76
76
  });
77
77
 
78
78
  const response = await app.request(
79
- "/.well-known/jant-domain-check?nonce=test-nonce",
79
+ `/.well-known/jant-verification?nonce=${nonce}`,
80
80
  );
81
- const body = (await response.json()) as { token: string };
82
- const claims = await verifyHostedDomainCheckToken(secret, body.token);
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(claims).toMatchObject({
87
- aud: "jant-cloud",
88
- domainId: "sdom_custom",
89
- host: "blog.example.com",
90
- iss: "jant-core",
91
- nonce: "test-nonce",
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 { signHostedDomainCheckToken } from "../../lib/hosted-domain-check.js";
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-domain-check", async (c) => {
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 check endpoint");
15
+ throw new NotFoundError("Hosted domain verification endpoint");
16
16
  }
17
17
 
18
18
  if (!c.var.currentSiteDomain) {
19
- throw new NotFoundError("Hosted domain check endpoint");
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.json({ error: "Missing nonce." }, 400);
24
+ return c.text("Missing nonce.", 400);
25
25
  }
26
26
 
27
- const token = await signHostedDomainCheckToken(secret, {
28
- aud: "jant-cloud",
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.json({ token }, 200, {
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: true,
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) => {