@codepress/blog 0.2.1 → 0.3.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.
@@ -1,15 +1,24 @@
1
1
  import {
2
2
  createDraftClient
3
- } from "../chunks/chunk-3WRTL63O.js";
3
+ } from "../chunks/chunk-WEBQTRDI.js";
4
4
  import {
5
5
  BLOG_TOOLBAR_BOTTOM_VAR,
6
6
  DEFAULT_APP_URL,
7
7
  DEFAULT_COLUMN,
8
8
  DEFAULT_HEADER_SELECTOR,
9
9
  DEFAULT_TIMINGS,
10
+ EDITOR_SCOPE_CLASS,
10
11
  designTargetOf,
11
- designUrl
12
- } from "../chunks/chunk-UA243KAQ.js";
12
+ designUrl,
13
+ parsePostSource
14
+ } from "../chunks/chunk-BPAM7T27.js";
15
+ import "../chunks/chunk-HWR3R5MU.js";
16
+ import {
17
+ renderPostBody
18
+ } from "../chunks/chunk-AJVVTSKZ.js";
19
+ import "../chunks/chunk-EICE5ONB.js";
20
+ import "../chunks/chunk-TYX2HJZC.js";
21
+ import "../chunks/chunk-AEQDRBA3.js";
13
22
  import {
14
23
  SLUG_PATTERN
15
24
  } from "../chunks/chunk-XLJBCIXI.js";
@@ -23,7 +32,7 @@ import {
23
32
  useState
24
33
  } from "react";
25
34
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
26
- var BlogEditor = lazy(() => import("../chunks/BlogEditor-EDAPNNP5.js"));
35
+ var BlogEditor = lazy(() => import("../chunks/BlogEditor-IYNL6C7G.js"));
27
36
  function appPageUrl(appUrl, organizationSlug, site, slug, tab) {
28
37
  if (!organizationSlug) return `${appUrl}/dashboard`;
29
38
  const query = new URLSearchParams({ site, post: slug });
@@ -36,6 +45,7 @@ function BlogEditorMount({
36
45
  apiBase,
37
46
  auth,
38
47
  isNew = false,
48
+ startEditing = false,
39
49
  header,
40
50
  column = DEFAULT_COLUMN,
41
51
  appUrl = DEFAULT_APP_URL,
@@ -53,7 +63,7 @@ function BlogEditorMount({
53
63
  );
54
64
  const [capabilities, setCapabilities] = useState(null);
55
65
  const [checked, setChecked] = useState(false);
56
- const [editing, setEditing] = useState(isNew);
66
+ const [editing, setEditing] = useState(isNew || startEditing);
57
67
  const [ask, setAsk] = useState("closed");
58
68
  useEffect(() => {
59
69
  let cancelled = false;
@@ -91,7 +101,7 @@ function BlogEditorMount({
91
101
  "button",
92
102
  {
93
103
  type: "button",
94
- className: "fixed right-6 bottom-6 z-40 rounded-full bg-[#111110] px-5 py-2.5 text-sm font-medium text-white shadow-lg",
104
+ className: `${EDITOR_SCOPE_CLASS} fixed right-6 bottom-6 z-40 rounded-full bg-[#111110] px-5 py-2.5 text-sm font-medium text-white shadow-lg`,
95
105
  "data-testid": "blog-edit-button",
96
106
  onClick: () => changeEditing(true),
97
107
  children: "Edit"
@@ -107,7 +117,7 @@ function BlogEditorMount({
107
117
  fallback: /* @__PURE__ */ jsx(
108
118
  "p",
109
119
  {
110
- className: `${column} pt-10 pb-20 sm:pt-14`,
120
+ className: `${EDITOR_SCOPE_CLASS} ${column} pt-10 pb-20 sm:pt-14`,
111
121
  "data-testid": "blog-editor-loading",
112
122
  children: "Loading the editor\u2026"
113
123
  }
@@ -159,6 +169,7 @@ function NewPostForm({ onStart, prefix = "/blog/" }) {
159
169
  return /* @__PURE__ */ jsxs2(
160
170
  "form",
161
171
  {
172
+ className: EDITOR_SCOPE_CLASS,
162
173
  "data-testid": "blog-new-form",
163
174
  onSubmit: (event) => {
164
175
  event.preventDefault();
@@ -198,14 +209,600 @@ function NewPostForm({ onStart, prefix = "/blog/" }) {
198
209
  }
199
210
  );
200
211
  }
212
+
213
+ // src/editor/host.tsx
214
+ import {
215
+ useCallback,
216
+ useEffect as useEffect2,
217
+ useMemo as useMemo2,
218
+ useRef,
219
+ useState as useState3
220
+ } from "react";
221
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
222
+ var EDIT_PARAM = "cp-edit";
223
+ var CALLBACK_SEGMENT = "__cp/callback";
224
+ var CONSENT_PATH = "/blog-edit/consent";
225
+ var navigation = {
226
+ assign: (url) => window.location.assign(url),
227
+ replace: (url) => window.location.replace(url)
228
+ };
229
+ var EXPIRY_SKEW_MS = 3e4;
230
+ var tokenKey = (site) => `codepress-blog:token:${site}`;
231
+ var handoffKey = (site) => `codepress-blog:handoff:${site}`;
232
+ function storage() {
233
+ try {
234
+ return window.sessionStorage;
235
+ } catch {
236
+ return null;
237
+ }
238
+ }
239
+ function readJson(key) {
240
+ try {
241
+ const raw = storage()?.getItem(key);
242
+ return raw ? JSON.parse(raw) : null;
243
+ } catch {
244
+ return null;
245
+ }
246
+ }
247
+ function writeJson(key, value) {
248
+ try {
249
+ storage()?.setItem(key, JSON.stringify(value));
250
+ } catch {
251
+ }
252
+ }
253
+ function remove(key) {
254
+ try {
255
+ storage()?.removeItem(key);
256
+ } catch {
257
+ }
258
+ }
259
+ function readSiteToken(site, now = Date.now()) {
260
+ const stored = readJson(tokenKey(site));
261
+ if (!stored || typeof stored.token !== "string" || typeof stored.expiresAt !== "number") {
262
+ return null;
263
+ }
264
+ if (stored.expiresAt - EXPIRY_SKEW_MS <= now) {
265
+ remove(tokenKey(site));
266
+ return null;
267
+ }
268
+ return stored;
269
+ }
270
+ function clearSiteToken(site) {
271
+ remove(tokenKey(site));
272
+ }
273
+ function base64Url(bytes) {
274
+ let binary = "";
275
+ for (const byte of bytes) binary += String.fromCharCode(byte);
276
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
277
+ }
278
+ function createVerifier() {
279
+ return base64Url(crypto.getRandomValues(new Uint8Array(32)));
280
+ }
281
+ async function challengeFor(verifier) {
282
+ const digest = await crypto.subtle.digest(
283
+ "SHA-256",
284
+ new TextEncoder().encode(verifier)
285
+ );
286
+ return base64Url(new Uint8Array(digest));
287
+ }
288
+ function normalizedBasePath(basePath) {
289
+ return `/${basePath.replace(/^\/+|\/+$/g, "")}`;
290
+ }
291
+ function callbackPath(basePath) {
292
+ return `${normalizedBasePath(basePath)}/${CALLBACK_SEGMENT}`;
293
+ }
294
+ function safeReturnPath(basePath, value) {
295
+ const base = normalizedBasePath(basePath);
296
+ if (!value || !value.startsWith("/") || value.startsWith("//")) return base;
297
+ try {
298
+ const url = new URL(value, "https://host.invalid");
299
+ if (url.origin !== "https://host.invalid") return base;
300
+ if (url.pathname !== base && !url.pathname.startsWith(`${base}/`)) {
301
+ return base;
302
+ }
303
+ return `${url.pathname}${url.search}${url.hash}`;
304
+ } catch {
305
+ return base;
306
+ }
307
+ }
308
+ function withoutEditParam(path) {
309
+ const url = new URL(path, "https://host.invalid");
310
+ url.searchParams.delete(EDIT_PARAM);
311
+ return `${url.pathname}${url.search}${url.hash}`;
312
+ }
313
+ function consentUrl({
314
+ appUrl,
315
+ site,
316
+ origin,
317
+ basePath,
318
+ challenge
319
+ }) {
320
+ const query = new URLSearchParams({
321
+ site,
322
+ origin,
323
+ return: `${origin}${callbackPath(basePath)}`,
324
+ challenge
325
+ });
326
+ return `${appUrl.replace(/\/+$/, "")}${CONSENT_PATH}?${query.toString()}`;
327
+ }
328
+ async function startHandoff(config, returnTo) {
329
+ const verifier = createVerifier();
330
+ writeJson(handoffKey(config.site), {
331
+ verifier,
332
+ returnTo: safeReturnPath(config.basePath, returnTo)
333
+ });
334
+ navigation.assign(
335
+ consentUrl({
336
+ appUrl: config.appUrl ?? DEFAULT_APP_URL,
337
+ site: config.site,
338
+ origin: window.location.origin,
339
+ basePath: config.basePath,
340
+ challenge: await challengeFor(verifier)
341
+ })
342
+ );
343
+ }
344
+ function siteAuth(site) {
345
+ return { getToken: async () => readSiteToken(site)?.token ?? null };
346
+ }
347
+ function leavingLinkOf(event) {
348
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
349
+ return null;
350
+ }
351
+ const target = event.target;
352
+ const anchor = target instanceof Element ? target.closest("a[href]") : null;
353
+ if (!anchor || anchor.hasAttribute("download")) return null;
354
+ if (anchor.closest('[contenteditable="true"]')) return null;
355
+ if (anchor.target && anchor.target !== "_self") return null;
356
+ const url = new URL(anchor.href, window.location.href);
357
+ if (url.origin !== window.location.origin) return null;
358
+ if (url.pathname === window.location.pathname && url.search === window.location.search) {
359
+ return null;
360
+ }
361
+ return url.href;
362
+ }
363
+ function historyNavigation() {
364
+ const api = window.navigation;
365
+ return api && typeof api.traverseTo === "function" ? api : null;
366
+ }
367
+ function useLeaveThroughEditor(active, controller) {
368
+ useEffect2(() => {
369
+ if (!active) return;
370
+ let leaving = false;
371
+ let settling = false;
372
+ let destination = null;
373
+ const hold = (go) => {
374
+ const editor = controller.current;
375
+ if (leaving || !editor) return false;
376
+ destination = go;
377
+ if (settling) return true;
378
+ settling = true;
379
+ void editor.leave(() => {
380
+ leaving = true;
381
+ const next = destination;
382
+ destination = null;
383
+ next?.();
384
+ }).finally(() => {
385
+ settling = false;
386
+ });
387
+ return true;
388
+ };
389
+ const onClick = (event) => {
390
+ const href = leavingLinkOf(event);
391
+ if (!href) return;
392
+ if (hold(() => navigation.assign(href))) {
393
+ event.preventDefault();
394
+ event.stopPropagation();
395
+ }
396
+ };
397
+ const history = historyNavigation();
398
+ const onNavigate = (event) => {
399
+ const traverse = event;
400
+ if (traverse.navigationType !== "traverse" || !event.cancelable) return;
401
+ const key = traverse.destination.key;
402
+ const replay = () => {
403
+ const result = history?.traverseTo(key);
404
+ result?.committed.catch(() => {
405
+ });
406
+ result?.finished.catch(() => {
407
+ });
408
+ };
409
+ if (hold(replay)) event.preventDefault();
410
+ };
411
+ document.addEventListener("click", onClick, true);
412
+ history?.addEventListener("navigate", onNavigate);
413
+ return () => {
414
+ document.removeEventListener("click", onClick, true);
415
+ history?.removeEventListener("navigate", onNavigate);
416
+ };
417
+ }, [active, controller]);
418
+ }
419
+ var PILL = "fixed right-6 bottom-6 z-40 rounded-full bg-[#111110] px-5 py-2.5 text-sm font-medium text-white shadow-lg";
420
+ var PUBLISHED_STYLE = {
421
+ maxWidth: "var(--cp-blog-page-width, 720px)",
422
+ margin: "0 auto",
423
+ padding: "var(--cp-blog-page-padding, 3rem 1.25rem 5rem)",
424
+ color: "var(--cp-blog-text)",
425
+ fontFamily: "var(--cp-blog-font)"
426
+ };
427
+ function PublishedPost({
428
+ source,
429
+ revalidate,
430
+ href
431
+ }) {
432
+ const parsed = parsePostSource(source);
433
+ const body = parsed.metaText ? source.replace(parsed.metaText, "") : source;
434
+ const title = typeof parsed.meta.title === "string" ? parsed.meta.title : void 0;
435
+ const wait = revalidate ? `within about ${revalidate} second${revalidate === 1 ? "" : "s"}` : "shortly";
436
+ return /* @__PURE__ */ jsxs3(
437
+ "main",
438
+ {
439
+ className: "cp-blog-post",
440
+ style: PUBLISHED_STYLE,
441
+ "data-testid": "blog-host-published",
442
+ children: [
443
+ /* @__PURE__ */ jsxs3(
444
+ "p",
445
+ {
446
+ role: "status",
447
+ className: `${EDITOR_SCOPE_CLASS} rounded-md border border-[#D9D5CC] bg-[#F7F5F0] px-4 py-3 text-sm text-[#111110]`,
448
+ "data-testid": "blog-host-published-note",
449
+ children: [
450
+ "Published. This is the post as CodePress now serves it; the public page catches up ",
451
+ wait,
452
+ ".",
453
+ " ",
454
+ /* @__PURE__ */ jsx3("a", { className: "underline", href, children: "Reload the page" })
455
+ ]
456
+ }
457
+ ),
458
+ title ? /* @__PURE__ */ jsx3("header", { children: /* @__PURE__ */ jsx3("h1", { children: title }) }) : null,
459
+ /* @__PURE__ */ jsx3("article", { className: "cp-blog-prose", children: renderPostBody(body) })
460
+ ]
461
+ }
462
+ );
463
+ }
464
+ function setEditParam(on) {
465
+ const url = new URL(window.location.href);
466
+ if (on) url.searchParams.set(EDIT_PARAM, "");
467
+ else url.searchParams.delete(EDIT_PARAM);
468
+ window.history.replaceState(
469
+ window.history.state,
470
+ "",
471
+ url.href.replace(`${EDIT_PARAM}=`, EDIT_PARAM).replace(/[?&]$/, "")
472
+ );
473
+ }
474
+ function HostEditing({
475
+ site,
476
+ apiBase,
477
+ basePath,
478
+ appUrl = DEFAULT_APP_URL,
479
+ revalidate,
480
+ timings,
481
+ slug,
482
+ children
483
+ }) {
484
+ const [phase, setPhase] = useState3({ kind: "reading" });
485
+ const [expired, setExpired] = useState3(false);
486
+ const controller = useRef(null);
487
+ const auth = useMemo2(() => siteAuth(site), [site]);
488
+ const client = useMemo2(
489
+ () => createDraftClient({ apiBase, auth }),
490
+ [apiBase, auth]
491
+ );
492
+ const config = useMemo2(
493
+ () => ({ site, basePath, appUrl }),
494
+ [site, basePath, appUrl]
495
+ );
496
+ const editing = phase.kind === "editing" || phase.kind === "new-editing" || phase.kind === "new-form";
497
+ const openEditor = useCallback(
498
+ (capabilities) => {
499
+ if (!capabilities?.can_edit) {
500
+ clearSiteToken(site);
501
+ setPhase({ kind: "denied" });
502
+ return;
503
+ }
504
+ setPhase(slug ? { kind: "editing" } : { kind: "new-form" });
505
+ },
506
+ [site, slug]
507
+ );
508
+ useEffect2(() => {
509
+ let cancelled = false;
510
+ const wantsEdit = new URLSearchParams(window.location.search).has(
511
+ EDIT_PARAM
512
+ );
513
+ void client.getCapabilities(site).then((capabilities) => {
514
+ if (cancelled) return;
515
+ if (!capabilities?.host_editing_enabled) {
516
+ setPhase({ kind: "off" });
517
+ return;
518
+ }
519
+ if (!wantsEdit) {
520
+ setPhase({ kind: "idle", canEdit: capabilities.can_edit });
521
+ return;
522
+ }
523
+ if (!readSiteToken(site)) {
524
+ void startHandoff(config, window.location.pathname + `?${EDIT_PARAM}`);
525
+ return;
526
+ }
527
+ openEditor(capabilities);
528
+ });
529
+ return () => {
530
+ cancelled = true;
531
+ };
532
+ }, [client, config, openEditor, site]);
533
+ useEffect2(() => {
534
+ if (!editing) return;
535
+ const stored = readSiteToken(site);
536
+ if (!stored) {
537
+ setExpired(true);
538
+ return;
539
+ }
540
+ setExpired(false);
541
+ const timer = setTimeout(
542
+ () => setExpired(true),
543
+ stored.expiresAt - EXPIRY_SKEW_MS - Date.now()
544
+ );
545
+ return () => clearTimeout(timer);
546
+ }, [editing, site]);
547
+ useLeaveThroughEditor(editing, controller);
548
+ const startEditing = async () => {
549
+ if (readSiteToken(site)) {
550
+ setEditParam(true);
551
+ openEditor(await client.getCapabilities(site));
552
+ return;
553
+ }
554
+ await startHandoff(config, window.location.pathname + `?${EDIT_PARAM}`);
555
+ };
556
+ const stopEditing = () => {
557
+ setEditParam(false);
558
+ setPhase({ kind: "idle", canEdit: true });
559
+ };
560
+ const showPublished = async (published) => {
561
+ const result = await client.getDraft(site, published);
562
+ if ("error" in result) {
563
+ navigation.assign(
564
+ `${normalizedBasePath(basePath)}/${encodeURIComponent(published)}`
565
+ );
566
+ return;
567
+ }
568
+ setEditParam(false);
569
+ setPhase({ kind: "published", slug: published, source: result.draft.html });
570
+ };
571
+ const reader = /* @__PURE__ */ jsx3(
572
+ "div",
573
+ {
574
+ style: {
575
+ display: editing || phase.kind === "published" ? "none" : "contents"
576
+ },
577
+ children
578
+ }
579
+ );
580
+ if (phase.kind === "reading" || phase.kind === "off") return reader;
581
+ if (phase.kind === "published") {
582
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
583
+ reader,
584
+ /* @__PURE__ */ jsx3(
585
+ PublishedPost,
586
+ {
587
+ source: phase.source,
588
+ revalidate,
589
+ href: `${normalizedBasePath(basePath)}/${encodeURIComponent(phase.slug)}`
590
+ }
591
+ )
592
+ ] });
593
+ }
594
+ const expiredBanner = editing && expired ? /* @__PURE__ */ jsxs3(
595
+ "div",
596
+ {
597
+ role: "alert",
598
+ className: `${EDITOR_SCOPE_CLASS} fixed bottom-6 left-1/2 z-50 flex -translate-x-1/2 items-center gap-3 rounded-full bg-[#111110] px-5 py-2.5 text-sm text-white shadow-lg`,
599
+ "data-testid": "blog-host-expired",
600
+ children: [
601
+ "Your editing session has ended.",
602
+ /* @__PURE__ */ jsx3(
603
+ "button",
604
+ {
605
+ type: "button",
606
+ className: "font-medium underline",
607
+ onClick: () => {
608
+ const leave = () => void startHandoff(
609
+ config,
610
+ window.location.pathname + `?${EDIT_PARAM}`
611
+ );
612
+ if (controller.current) void controller.current.leave(leave);
613
+ else leave();
614
+ },
615
+ children: "Sign in again"
616
+ }
617
+ )
618
+ ]
619
+ }
620
+ ) : null;
621
+ const column = "mx-auto w-full max-w-[720px] px-5 sm:px-8";
622
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
623
+ reader,
624
+ phase.kind === "idle" ? /* @__PURE__ */ jsx3(
625
+ "a",
626
+ {
627
+ href: `?${EDIT_PARAM}`,
628
+ className: `${EDITOR_SCOPE_CLASS} ${PILL}`,
629
+ "data-testid": slug ? "blog-host-edit" : "blog-host-new-post",
630
+ onClick: (event) => {
631
+ event.preventDefault();
632
+ void startEditing();
633
+ },
634
+ children: slug ? "Edit" : "New post"
635
+ }
636
+ ) : null,
637
+ phase.kind === "denied" ? /* @__PURE__ */ jsx3(
638
+ "p",
639
+ {
640
+ role: "alert",
641
+ className: `${EDITOR_SCOPE_CLASS} ${PILL}`,
642
+ "data-testid": "blog-host-denied",
643
+ children: "This CodePress account cannot edit this blog."
644
+ }
645
+ ) : null,
646
+ phase.kind === "editing" && slug ? /* @__PURE__ */ jsx3(
647
+ BlogEditorMount,
648
+ {
649
+ site,
650
+ slug,
651
+ apiBase,
652
+ auth,
653
+ startEditing: true,
654
+ appUrl,
655
+ timings,
656
+ controller,
657
+ onEditingChange: (open) => {
658
+ if (!open) stopEditing();
659
+ },
660
+ onPublished: (published) => void showPublished(published)
661
+ }
662
+ ) : null,
663
+ phase.kind === "new-form" ? /* @__PURE__ */ jsx3(
664
+ "div",
665
+ {
666
+ className: `${EDITOR_SCOPE_CLASS} ${column} pt-10 pb-20 sm:pt-14`,
667
+ "data-testid": "blog-host-new",
668
+ children: /* @__PURE__ */ jsx3(
669
+ NewPostForm,
670
+ {
671
+ prefix: `${normalizedBasePath(basePath)}/`,
672
+ onStart: (newSlug) => setPhase({ kind: "new-editing", slug: newSlug })
673
+ }
674
+ )
675
+ }
676
+ ) : null,
677
+ phase.kind === "new-editing" ? /* @__PURE__ */ jsx3(
678
+ BlogEditorMount,
679
+ {
680
+ site,
681
+ slug: phase.slug,
682
+ apiBase,
683
+ auth,
684
+ isNew: true,
685
+ appUrl,
686
+ timings,
687
+ controller,
688
+ onLeave: stopEditing,
689
+ onPublished: (published) => void showPublished(published)
690
+ }
691
+ ) : null,
692
+ expiredBanner
693
+ ] });
694
+ }
695
+ function HostEditorCallback({
696
+ site,
697
+ apiBase,
698
+ basePath
699
+ }) {
700
+ const [state, setState] = useState3("pending");
701
+ const [back, setBack] = useState3(normalizedBasePath(basePath));
702
+ const started = useRef(false);
703
+ useEffect2(() => {
704
+ if (started.current) return;
705
+ started.current = true;
706
+ const params = new URLSearchParams(window.location.search);
707
+ const code = params.get("code");
708
+ const denied = params.has("error");
709
+ window.history.replaceState(null, "", window.location.pathname);
710
+ const root = apiBase.replace(/\/+$/, "");
711
+ const client = createDraftClient({
712
+ apiBase,
713
+ auth: { getToken: async () => null }
714
+ });
715
+ void (async () => {
716
+ const handoff = readJson(handoffKey(site));
717
+ remove(handoffKey(site));
718
+ const returnTo = safeReturnPath(basePath, handoff?.returnTo);
719
+ const withoutEditing = withoutEditParam(returnTo);
720
+ setBack(withoutEditing);
721
+ if (denied) {
722
+ navigation.replace(withoutEditing);
723
+ return;
724
+ }
725
+ const capabilities = await client.getCapabilities(site);
726
+ if (!capabilities) {
727
+ setState("failed");
728
+ return;
729
+ }
730
+ if (!capabilities.host_editing_enabled) {
731
+ if (handoff) navigation.replace(withoutEditing);
732
+ else setState("off");
733
+ return;
734
+ }
735
+ if (!code || !handoff?.verifier) {
736
+ setState("failed");
737
+ return;
738
+ }
739
+ try {
740
+ const response = await fetch(
741
+ `${root}/content-sites/editor-handoff/exchange`,
742
+ {
743
+ method: "POST",
744
+ headers: {
745
+ Accept: "application/json",
746
+ "Content-Type": "application/json"
747
+ },
748
+ body: JSON.stringify({ code, verifier: handoff.verifier })
749
+ }
750
+ );
751
+ if (!response.ok) {
752
+ setState("failed");
753
+ return;
754
+ }
755
+ const body = await response.json();
756
+ if (body.site !== site || typeof body.token !== "string") {
757
+ setState("failed");
758
+ return;
759
+ }
760
+ writeJson(tokenKey(site), {
761
+ token: body.token,
762
+ expiresAt: Date.now() + body.expires_in * 1e3
763
+ });
764
+ navigation.replace(returnTo);
765
+ } catch {
766
+ setState("failed");
767
+ }
768
+ })();
769
+ }, [apiBase, basePath, site]);
770
+ if (state !== "failed") return null;
771
+ return /* @__PURE__ */ jsxs3(
772
+ "main",
773
+ {
774
+ className: `${EDITOR_SCOPE_CLASS} mx-auto w-full max-w-[720px] px-5 pt-10 pb-20 text-[#111110]`,
775
+ "data-testid": "blog-host-callback-failed",
776
+ children: [
777
+ /* @__PURE__ */ jsx3("h1", { className: "text-2xl font-medium", children: "Could not start editing" }),
778
+ /* @__PURE__ */ jsx3("p", { className: "mt-3 text-sm text-[#6B6760]", children: "The sign-in link expired or was already used, or CodePress could not be reached. Go back and press Edit to try again." }),
779
+ /* @__PURE__ */ jsx3("a", { className: "mt-4 inline-block text-sm underline", href: back, children: "Back to the blog" })
780
+ ]
781
+ }
782
+ );
783
+ }
201
784
  export {
202
785
  BLOG_TOOLBAR_BOTTOM_VAR,
203
786
  BlogEditorMount,
787
+ CALLBACK_SEGMENT,
788
+ CONSENT_PATH,
204
789
  DEFAULT_HEADER_SELECTOR,
205
790
  DEFAULT_TIMINGS,
791
+ EDITOR_SCOPE_CLASS,
792
+ EDIT_PARAM,
793
+ HostEditing,
794
+ HostEditorCallback,
206
795
  NewPostForm,
207
796
  appPageUrl,
797
+ callbackPath,
798
+ challengeFor,
799
+ clearSiteToken,
800
+ consentUrl,
208
801
  createDraftClient,
802
+ createVerifier,
209
803
  designUrl,
210
- isValidNewSlug
804
+ isValidNewSlug,
805
+ readSiteToken,
806
+ safeReturnPath,
807
+ startHandoff
211
808
  };