@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.
- package/README.md +25 -5
- package/dist/chunks/{BlogEditor-EDAPNNP5.js → BlogEditor-IYNL6C7G.js} +166 -219
- package/dist/chunks/chunk-BPAM7T27.js +315 -0
- package/dist/chunks/{chunk-3GCZFCUK.js → chunk-LFSX43CO.js} +14 -3
- package/dist/chunks/{chunk-WJVZD4UI.js → chunk-PPMNLNLD.js} +8 -4
- package/dist/chunks/{chunk-3WRTL63O.js → chunk-WEBQTRDI.js} +10 -7
- package/dist/cli.js +96 -15
- package/dist/editor/BlogEditorMount.d.ts +3 -1
- package/dist/editor/client.d.ts +7 -1
- package/dist/editor/client.js +1 -1
- package/dist/editor/config.d.ts +6 -0
- package/dist/editor/host.d.ts +92 -0
- package/dist/editor/index.d.ts +2 -1
- package/dist/editor/index.js +605 -8
- package/dist/editor.css +3 -0
- package/dist/next/app.d.ts +15 -2
- package/dist/next/config.d.ts +18 -0
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +22 -5
- package/dist/next/pages.d.ts +4 -2
- package/dist/next/pages.js +29 -5
- package/dist/next/routes.js +1 -1
- package/dist/next/views.d.ts +5 -3
- package/package.json +3 -2
- package/dist/chunks/chunk-UA243KAQ.js +0 -104
package/dist/editor/index.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createDraftClient
|
|
3
|
-
} from "../chunks/chunk-
|
|
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
|
-
|
|
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-
|
|
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:
|
|
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
|
};
|