smoodly 0.0.51 → 0.0.53
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 +14 -13
- package/dist/admin/auth.d.ts +3 -11
- package/dist/admin/auth.js +1 -30
- package/dist/admin/client-ops.d.ts +5 -4
- package/dist/admin/client-ops.js +15 -12
- package/dist/admin/delivery-mode.d.ts +1 -1
- package/dist/admin/delivery-mode.js +2 -2
- package/dist/admin/drift.d.ts +0 -2
- package/dist/admin/drift.js +1 -1
- package/dist/admin/editor/EditorView.d.ts +0 -3
- package/dist/admin/editor/EditorView.js +11 -16
- package/dist/admin/editor/FloatingToolbar.d.ts +0 -1
- package/dist/admin/editor/FloatingToolbar.js +1 -1
- package/dist/admin/editor/SeoSettings.d.ts +0 -2
- package/dist/admin/editor/SeoSettings.js +2 -2
- package/dist/admin/editor/canvas-address.d.ts +8 -0
- package/dist/admin/editor/canvas-address.js +18 -2
- package/dist/admin/editor/canvas-diagnosis.d.ts +2 -0
- package/dist/admin/editor/canvas-diagnosis.js +3 -0
- package/dist/admin/editor/inline-dom.d.ts +0 -4
- package/dist/admin/editor/inline-dom.js +1 -1
- package/dist/admin/editor/preview-grant.d.ts +6 -0
- package/dist/admin/editor/preview-grant.js +25 -0
- package/dist/admin/editor/useCanvasDiagnosis.js +2 -2
- package/dist/admin/files.generated.js +1 -1
- package/dist/admin/fixed-nodes.d.ts +0 -6
- package/dist/admin/fixed-nodes.js +1 -81
- package/dist/admin/forms/CropEditor.d.ts +4 -2
- package/dist/admin/forms/CropEditor.js +58 -5
- package/dist/admin/forms/ImagePreview.d.ts +3 -1
- package/dist/admin/forms/ImagePreview.js +9 -3
- package/dist/admin/forms/MediaGrid.js +2 -1
- package/dist/admin/forms/MediaPicker.js +9 -6
- package/dist/admin/forms/MediaWidget.js +11 -9
- package/dist/admin/forms/assets-context.d.ts +7 -3
- package/dist/admin/forms/assets-context.js +5 -3
- package/dist/admin/forms/crop-editor.d.ts +24 -2
- package/dist/admin/forms/crop-editor.js +36 -2
- package/dist/admin/forms/date-picker.d.ts +0 -1
- package/dist/admin/forms/date-picker.js +1 -1
- package/dist/admin/forms/tabs.d.ts +0 -1
- package/dist/admin/forms/tabs.js +1 -1
- package/dist/admin/forms/upload.d.ts +18 -11
- package/dist/admin/forms/upload.js +62 -24
- package/dist/admin/global-items.d.ts +0 -3
- package/dist/admin/global-items.js +0 -54
- package/dist/admin/handler.d.ts +22 -28
- package/dist/admin/handler.js +146 -47
- package/dist/admin/health.d.ts +0 -4
- package/dist/admin/index.d.ts +6 -9
- package/dist/admin/index.js +3 -6
- package/dist/admin/ops.d.ts +40 -32
- package/dist/admin/page-data.d.ts +14 -6
- package/dist/admin/page-data.js +1 -1
- package/dist/admin/session-flow.d.ts +65 -0
- package/dist/admin/session-flow.js +121 -0
- package/dist/admin/shell/AdminApp.d.ts +8 -3
- package/dist/admin/shell/AdminApp.js +37 -58
- package/dist/admin/shell/ContentHealthPage.js +1 -3
- package/dist/admin/shell/GlobalFields.d.ts +0 -3
- package/dist/admin/shell/GlobalFields.js +1 -1
- package/dist/admin/shell/IconRail.js +10 -8
- package/dist/admin/shell/MediaScreen.js +9 -6
- package/dist/admin/shell/MembersPage.d.ts +1 -2
- package/dist/admin/shell/MembersPage.js +19 -90
- package/dist/admin/shell/SettingsView.d.ts +1 -2
- package/dist/admin/shell/SettingsView.js +2 -2
- package/dist/admin/shell/SignedOutView.d.ts +5 -0
- package/dist/admin/shell/SignedOutView.js +9 -0
- package/dist/admin/shell/console-links.d.ts +5 -0
- package/dist/admin/shell/console-links.js +11 -0
- package/dist/admin/shell/members-list.d.ts +1 -14
- package/dist/admin/shell/members-list.js +2 -23
- package/dist/admin/shell/pages-tree.d.ts +0 -2
- package/dist/admin/shell/pages-tree.js +1 -1
- package/dist/admin/shell/presence-socket.d.ts +10 -0
- package/dist/admin/shell/presence-socket.js +191 -0
- package/dist/admin/shell/presence.d.ts +6 -18
- package/dist/admin/shell/presence.js +4 -11
- package/dist/admin/shell/session.d.ts +19 -12
- package/dist/admin/shell/session.js +114 -47
- package/dist/admin/ui/LocaleSwitcher.d.ts +0 -4
- package/dist/admin/ui/LocaleSwitcher.js +2 -2
- package/dist/admin/ui/primitives.d.ts +4 -14
- package/dist/admin/ui/primitives.js +7 -9
- package/dist/admin/ui/theme.js +13 -2
- package/dist/admin/validate.js +10 -1
- package/dist/api-client.d.ts +26 -0
- package/dist/api-client.js +107 -0
- package/dist/assemble.d.ts +4 -41
- package/dist/assemble.js +5 -303
- package/dist/asset-store.d.ts +28 -30
- package/dist/asset-store.js +26 -121
- package/dist/astro/cache.d.ts +1 -1
- package/dist/astro/components/Head.astro +4 -1
- package/dist/astro/components/Image.astro +12 -17
- package/dist/astro/components/Video.astro +5 -2
- package/dist/astro/head.d.ts +3 -0
- package/dist/astro/head.js +24 -0
- package/dist/astro/index.js +61 -27
- package/dist/astro/locale-paths.d.ts +5 -2
- package/dist/astro/locale-paths.js +10 -2
- package/dist/astro/routes/admin.js +9 -7
- package/dist/astro/runtime.d.ts +3 -2
- package/dist/astro/runtime.js +5 -4
- package/dist/astro/server.d.ts +7 -11
- package/dist/astro/server.js +22 -28
- package/dist/astro/shell.d.ts +3 -3
- package/dist/astro/shell.js +1 -1
- package/dist/astro/static-media-node.d.ts +4 -0
- package/dist/astro/static-media-node.js +25 -0
- package/dist/astro/static-media.d.ts +17 -0
- package/dist/astro/static-media.js +55 -0
- package/dist/astro/static.d.ts +5 -10
- package/dist/astro/static.js +13 -29
- package/dist/canvas-marker.d.ts +14 -0
- package/dist/canvas-marker.js +36 -0
- package/dist/cli/api.d.ts +14 -0
- package/dist/cli/api.js +48 -0
- package/dist/cli/app-env.d.ts +2 -0
- package/dist/cli/app-env.js +20 -0
- package/dist/cli/export.d.ts +16 -2
- package/dist/cli/export.js +52 -35
- package/dist/cli/import.d.ts +7 -11
- package/dist/cli/import.js +50 -54
- package/dist/cli/load-config.d.ts +0 -1
- package/dist/cli/load-config.js +1 -1
- package/dist/cli/main.d.ts +4 -12
- package/dist/cli/main.js +26 -72
- package/dist/cli/push.d.ts +6 -0
- package/dist/cli/push.js +31 -0
- package/dist/collections.d.ts +0 -1
- package/dist/collections.js +1 -1
- package/dist/config.d.ts +20 -5
- package/dist/config.js +18 -1
- package/dist/content/content-api.d.ts +23 -23
- package/dist/content/content-api.js +48 -56
- package/dist/content/mime.d.ts +1 -0
- package/dist/content/mime.js +6 -0
- package/dist/css.js +1 -1
- package/dist/deploy-store.d.ts +19 -0
- package/dist/entry-store.d.ts +8 -94
- package/dist/entry-store.js +9 -557
- package/dist/env.d.ts +27 -20
- package/dist/env.js +38 -50
- package/dist/fields.d.ts +11 -3
- package/dist/fields.js +33 -2
- package/dist/global-id.d.ts +2 -0
- package/dist/global-id.js +4 -2
- package/dist/image/SmoodlyImage.d.ts +9 -5
- package/dist/image/SmoodlyImage.js +29 -15
- package/dist/image/SmoodlyVideo.d.ts +1 -1
- package/dist/image/SmoodlyVideo.js +3 -2
- package/dist/image/index.d.ts +2 -3
- package/dist/image/index.js +0 -2
- package/dist/image/markup.d.ts +53 -0
- package/dist/image/markup.js +111 -0
- package/dist/index.d.ts +13 -23
- package/dist/index.js +7 -20
- package/dist/media-cut.d.ts +44 -0
- package/dist/media-cut.js +70 -0
- package/dist/media.d.ts +58 -4
- package/dist/media.js +126 -16
- package/dist/member-store.d.ts +2 -50
- package/dist/member-store.js +0 -56
- package/dist/next/admin-route.d.ts +1 -1
- package/dist/next/admin-route.js +5 -3
- package/dist/next/meta.js +2 -3
- package/dist/next/page-renderer.d.ts +2 -2
- package/dist/next/page-renderer.js +22 -11
- package/dist/next/queries.d.ts +2 -2
- package/dist/path-index.d.ts +0 -24
- package/dist/path-index.js +0 -98
- package/dist/paths.d.ts +1 -1
- package/dist/paths.js +1 -1
- package/dist/push.d.ts +30 -0
- package/dist/push.js +52 -0
- package/dist/read-counter.d.ts +2 -0
- package/dist/read-counter.js +6 -0
- package/dist/reads.d.ts +7 -7
- package/dist/reads.js +3 -0
- package/dist/registry-json.d.ts +11 -0
- package/dist/registry-json.js +81 -0
- package/dist/render.d.ts +0 -1
- package/dist/render.js +1 -1
- package/dist/resolve.d.ts +12 -6
- package/dist/resolve.js +11 -8
- package/dist/revalidate.d.ts +8 -1
- package/dist/revalidate.js +8 -1
- package/dist/richtext/index.d.ts +1 -1
- package/dist/section-wrapper.d.ts +1 -1
- package/dist/section-wrapper.js +0 -9
- package/dist/signed-message.d.ts +26 -0
- package/dist/signed-message.js +103 -0
- package/dist/site.d.ts +59 -18
- package/dist/site.js +21 -252
- package/dist/space-transfer.d.ts +6 -3
- package/dist/space-transfer.js +18 -6
- package/dist/store.d.ts +2 -80
- package/dist/store.js +3 -489
- package/dist/toolset.d.ts +2 -1
- package/dist/toolset.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -15
- package/dist/admin/auth-config.d.ts +0 -6
- package/dist/admin/browser/fetch-op.d.ts +0 -10
- package/dist/admin/browser/fetch-op.js +0 -44
- package/dist/admin/browser/main.d.ts +0 -1
- package/dist/admin/browser/main.js +0 -23
- package/dist/admin/ops-impl.d.ts +0 -51
- package/dist/admin/ops-impl.js +0 -1403
- package/dist/admin/shell/InviteModal.d.ts +0 -6
- package/dist/admin/shell/InviteModal.js +0 -28
- package/dist/admin/shell/LoginView.d.ts +0 -5
- package/dist/admin/shell/LoginView.js +0 -80
- package/dist/admin/shell/SchemaNotice.d.ts +0 -4
- package/dist/admin/shell/SchemaNotice.js +0 -4
- package/dist/admin/shell/SetPasswordView.d.ts +0 -7
- package/dist/admin/shell/SetPasswordView.js +0 -41
- package/dist/admin/shell/presence-realtime.d.ts +0 -3
- package/dist/admin/shell/presence-realtime.js +0 -32
- package/dist/admin/supabase-auth.d.ts +0 -7
- package/dist/admin/supabase-auth.js +0 -30
- package/dist/astro/deploy-deps.d.ts +0 -10
- package/dist/astro/deploy-deps.js +0 -8
- package/dist/astro/image.d.ts +0 -27
- package/dist/astro/image.js +0 -29
- package/dist/bind.d.ts +0 -19
- package/dist/bind.js +0 -34
- package/dist/cli/add-admin.d.ts +0 -11
- package/dist/cli/add-admin.js +0 -25
- package/dist/cli/load-collections.d.ts +0 -2
- package/dist/cli/load-collections.js +0 -27
- package/dist/cli/migrate.d.ts +0 -43
- package/dist/cli/migrate.js +0 -138
- package/dist/cli/rebuild.d.ts +0 -23
- package/dist/cli/rebuild.js +0 -22
- package/dist/cli/space-io.d.ts +0 -41
- package/dist/cli/space-io.js +0 -84
- package/dist/client.d.ts +0 -11
- package/dist/client.js +0 -54
- package/dist/current-space.d.ts +0 -4
- package/dist/current-space.js +0 -9
- package/dist/document-stamp.d.ts +0 -7
- package/dist/document-stamp.js +0 -48
- package/dist/image/loaders.d.ts +0 -15
- package/dist/image/loaders.js +0 -5
- package/dist/image/next-config.d.ts +0 -18
- package/dist/image/next-config.js +0 -32
- package/dist/image/remote-pattern.d.ts +0 -1
- package/dist/image/remote-pattern.js +0 -5
- package/dist/migrations.d.ts +0 -37
- package/dist/migrations.js +0 -73
- package/dist/project.d.ts +0 -88
- package/dist/project.js +0 -231
- package/dist/published.d.ts +0 -142
- package/dist/published.js +0 -164
- package/dist/ref-index.d.ts +0 -19
- package/dist/ref-index.js +0 -0
- package/dist/schema-read.d.ts +0 -23
- package/dist/schema-read.js +0 -50
- package/dist/schema-version.d.ts +0 -19
- package/dist/schema-version.js +0 -34
- package/dist/sql-space.d.ts +0 -44
- package/dist/sql-space.js +0 -289
- package/dist/sql.d.ts +0 -15
- package/dist/sql.js +0 -556
- package/dist/supabase-asset-store.d.ts +0 -21
- package/dist/supabase-asset-store.js +0 -170
- package/dist/supabase-entry-store.d.ts +0 -92
- package/dist/supabase-entry-store.js +0 -824
- package/dist/supabase-member-store.d.ts +0 -21
- package/dist/supabase-member-store.js +0 -96
- package/dist/supabase-path-index.d.ts +0 -30
- package/dist/supabase-path-index.js +0 -138
- package/dist/supabase-published-store.d.ts +0 -56
- package/dist/supabase-published-store.js +0 -314
- package/dist/supabase-store.d.ts +0 -103
- package/dist/supabase-store.js +0 -678
- package/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/migrations/20261009120000_smoodly_v11.sql +0 -49
- /package/dist/{admin/auth-config.js → deploy-store.js} +0 -0
package/README.md
CHANGED
|
@@ -2,35 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
A code-first CMS for Astro and Next.js. Define a component once in code and it
|
|
4
4
|
becomes a typed, editable building block your editors compose, within
|
|
5
|
-
the rails you set. Content
|
|
6
|
-
your own site at `/admin`.
|
|
5
|
+
the rails you set. Content and media live in Smoodly's service, read over
|
|
6
|
+
its API; the admin mounts into your own site at `/admin`.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
10
|
npx create-smoodly-app my-site
|
|
11
11
|
|
|
12
|
-
This creates an Astro or Next.js site with the admin mounted,
|
|
13
|
-
|
|
12
|
+
This creates an Astro or Next.js site with the admin mounted, its content model
|
|
13
|
+
pushed to your space and the starter content seeded.
|
|
14
14
|
|
|
15
15
|
## Environment
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
The installer writes them from the project at smoodly.io:
|
|
18
18
|
|
|
19
19
|
| Variable | What it is |
|
|
20
20
|
| --- | --- |
|
|
21
|
-
| `
|
|
22
|
-
| `SMOODLY_SECRET_KEY` | The
|
|
23
|
-
| `SMOODLY_PUBLISHABLE_KEY` | The
|
|
21
|
+
| `SMOODLY_SPACE_ID` | The space your content lives in (`sp_…`) |
|
|
22
|
+
| `SMOODLY_SECRET_KEY` | The space's secret key (`sk_…`). Server-only. |
|
|
23
|
+
| `SMOODLY_PUBLISHABLE_KEY` | The space's publishable key (`pk_…`) |
|
|
24
|
+
| `SITE_URL` | Your site's own address |
|
|
24
25
|
|
|
25
26
|
## Upgrading
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
Update the package and deploy: a space's schema lives in Smoodly's
|
|
29
|
+
service, which brings it forward itself. The Upgrading guide names each
|
|
30
|
+
version's code changes.
|
|
30
31
|
|
|
31
32
|
## Docs and source
|
|
32
33
|
|
|
33
|
-
Guides: https://github.com/
|
|
34
|
-
Source: https://github.com/
|
|
34
|
+
Guides: https://github.com/smoodly-io/smoodly/tree/main/guides
|
|
35
|
+
Source: https://github.com/smoodly-io/smoodly
|
|
35
36
|
|
|
36
37
|
MIT licensed.
|
package/dist/admin/auth.d.ts
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
/** What the auth project vouches for: the Auth user id and the email the locale rows record as `updated_by` (spec 2026-09-13 history §2). */
|
|
1
|
+
import type { MemberRole } from "../permissions.ts";
|
|
2
|
+
/** Who the pass names: the account id and the email the locale rows record as `updated_by` (spec 2026-09-13 history §2). */
|
|
4
3
|
export type VerifiedUser = {
|
|
5
4
|
userId: string;
|
|
6
5
|
email: string | null;
|
|
7
6
|
};
|
|
8
|
-
/** The
|
|
7
|
+
/** The admitted member: the account plus the normalized role from `members`. */
|
|
9
8
|
export type AdminSession = VerifiedUser & {
|
|
10
9
|
role: MemberRole;
|
|
11
10
|
};
|
|
12
|
-
export type AdminAuth = {
|
|
13
|
-
/** The user behind a session token, or null when the auth project rejects it. */
|
|
14
|
-
verify(token: string): Promise<VerifiedUser | null>;
|
|
15
|
-
/** The stored role of this user in `members` for the current space, or null without a row. */
|
|
16
|
-
memberOf(userId: string): Promise<string | null>;
|
|
17
|
-
};
|
|
18
|
-
export declare function gateAdminOp(auth: AdminAuth, call: AdminOpCall, run: (call: AdminOpCall, session: AdminSession) => Promise<OpResult<unknown>>): Promise<OpResult<unknown>>;
|
package/dist/admin/auth.js
CHANGED
|
@@ -1,30 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// management §2): a session token from the auth project, then a row in
|
|
3
|
-
// `members` read with the write key, whose role rides on the session.
|
|
4
|
-
// One gate around the ops seam — individual ops ask can(). Framework-
|
|
5
|
-
// free; Supabase-backed implementation in supabase-auth.ts.
|
|
6
|
-
import { normalizeRole } from "../permissions.js";
|
|
7
|
-
const unauthorized = (message) => ({ ok: false, code: "unauthorized", message });
|
|
8
|
-
export async function gateAdminOp(auth, call, run) {
|
|
9
|
-
const token = typeof call === "object" && call !== null && typeof call.token === "string"
|
|
10
|
-
? call.token
|
|
11
|
-
: null;
|
|
12
|
-
if (!token)
|
|
13
|
-
return unauthorized("smoodly: sign in to use the admin.");
|
|
14
|
-
let user;
|
|
15
|
-
let stored;
|
|
16
|
-
try {
|
|
17
|
-
user = await auth.verify(token);
|
|
18
|
-
// Marked, so the browser signs itself out instead of showing this on every screen (2026-10-02).
|
|
19
|
-
if (!user)
|
|
20
|
-
return { ok: false, code: "unauthorized", message: "smoodly: your session has expired — sign in again.", expired: true };
|
|
21
|
-
stored = await auth.memberOf(user.userId);
|
|
22
|
-
}
|
|
23
|
-
catch (e) {
|
|
24
|
-
console.error("smoodly: auth check failed", e);
|
|
25
|
-
return { ok: false, code: "internal", message: "smoodly: could not check your session." };
|
|
26
|
-
}
|
|
27
|
-
if (stored === null)
|
|
28
|
-
return unauthorized("smoodly: this account is not an editor of this site.");
|
|
29
|
-
return run(call, { ...user, role: normalizeRole(stored) });
|
|
30
|
-
}
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AdminOpAction, AdminOps } from "./ops.ts";
|
|
2
|
-
/** `onExpired` runs when the
|
|
3
|
-
*
|
|
4
|
-
* and
|
|
5
|
-
|
|
2
|
+
/** `onExpired` runs when the service refuses the pass outright (`expired`: it expired, or its session ended). When it
|
|
3
|
+
* answers true (the session renewed), the call is sent once more with the fresh pass; otherwise the refusal is
|
|
4
|
+
* returned and the session's own path (a redirect to sign in) takes over (spec 2026-10-16 sign-in and the dashboard
|
|
5
|
+
* §2). */
|
|
6
|
+
export declare function makeClientOps(action: AdminOpAction, token?: () => string | null, onExpired?: () => Promise<boolean> | void): AdminOps;
|
package/dist/admin/client-ops.js
CHANGED
|
@@ -2,29 +2,32 @@ const GROUPS = {
|
|
|
2
2
|
pages: ["list", "get", "create", "saveDraft", "publish", "unpublish", "discardDraft", "delete", "rename", "move", "setTemplate", "addLocale", "removeLocale", "listVersions", "restore"],
|
|
3
3
|
entries: ["list", "distinct", "get", "create", "save", "setStatus", "discardDraft", "delete", "move", "addLocale", "removeLocale", "listVersions", "getVersion", "restore"],
|
|
4
4
|
globals: ["list", "get", "save", "setStatus", "discardDraft", "listVersions", "getVersion", "restore"],
|
|
5
|
-
assets: ["list", "get", "
|
|
6
|
-
members: ["list", "me"
|
|
5
|
+
assets: ["list", "get", "update", "delete", "usage"],
|
|
6
|
+
members: ["list", "me"],
|
|
7
7
|
redirects: ["list", "targets", "add", "remove"],
|
|
8
8
|
health: ["scan"],
|
|
9
|
-
schema: ["status"],
|
|
10
9
|
preview: ["enable", "disable"],
|
|
11
10
|
deploy: ["status", "trigger"],
|
|
12
11
|
};
|
|
13
|
-
/** `onExpired` runs when the
|
|
14
|
-
*
|
|
15
|
-
* and
|
|
12
|
+
/** `onExpired` runs when the service refuses the pass outright (`expired`: it expired, or its session ended). When it
|
|
13
|
+
* answers true (the session renewed), the call is sent once more with the fresh pass; otherwise the refusal is
|
|
14
|
+
* returned and the session's own path (a redirect to sign in) takes over (spec 2026-10-16 sign-in and the dashboard
|
|
15
|
+
* §2). */
|
|
16
16
|
export function makeClientOps(action, token, onExpired) {
|
|
17
17
|
const out = {};
|
|
18
|
+
const send = (path, args) => {
|
|
19
|
+
const t = token?.() ?? null;
|
|
20
|
+
return action({ path, args, ...(t ? { token: t } : {}) });
|
|
21
|
+
};
|
|
18
22
|
for (const [group, methods] of Object.entries(GROUPS)) {
|
|
19
23
|
out[group] = {};
|
|
20
24
|
for (const method of methods) {
|
|
21
|
-
out[group][method] = (...args) => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
onExpired?.();
|
|
25
|
+
out[group][method] = async (...args) => {
|
|
26
|
+
const path = `${group}.${method}`;
|
|
27
|
+
const r = await send(path, args);
|
|
28
|
+
if (r.ok || !r.expired)
|
|
26
29
|
return r;
|
|
27
|
-
|
|
30
|
+
return (await onExpired?.()) === true ? send(path, args) : r;
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare function setDelivery(value: "live" | "static"): void;
|
|
2
2
|
export declare const delivery: () => "live" | "static";
|
|
3
|
-
/** A publish succeeded somewhere in the admin
|
|
3
|
+
/** A publish (or an unpublish) succeeded somewhere in the admin. */
|
|
4
4
|
export declare function notePublished(): void;
|
|
5
5
|
export declare function onPublished(listener: () => void): () => void;
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
// Which delivery mode the site runs (spec 2026-10-09 static mode §6): the server names it once, in the
|
|
3
3
|
// page's data, and the shell shows the Deploy tile for a static site. Module state, set by the browser
|
|
4
4
|
// entry before the shell mounts, like the preview base (editor/canvas-address.ts). The publish controls
|
|
5
|
-
// tell the shell when a publish
|
|
5
|
+
// tell the shell when a publish succeeded, so the Deploy tile re-reads.
|
|
6
6
|
let mode = "live";
|
|
7
7
|
const listeners = new Set();
|
|
8
8
|
export function setDelivery(value) {
|
|
9
9
|
mode = value;
|
|
10
10
|
}
|
|
11
11
|
export const delivery = () => mode;
|
|
12
|
-
/** A publish succeeded somewhere in the admin
|
|
12
|
+
/** A publish (or an unpublish) succeeded somewhere in the admin. */
|
|
13
13
|
export function notePublished() {
|
|
14
14
|
for (const listener of listeners)
|
|
15
15
|
listener();
|
package/dist/admin/drift.d.ts
CHANGED
|
@@ -27,8 +27,6 @@ export type Finding = {
|
|
|
27
27
|
};
|
|
28
28
|
};
|
|
29
29
|
export declare const isBroken: (f: Finding) => boolean;
|
|
30
|
-
/** The validator's empty: an empty value is `required`'s question, never drift's. */
|
|
31
|
-
export declare const isEmptyValue: (v: unknown) => boolean;
|
|
32
30
|
/** Whether a stored value is the shape its field kind stores (§2). Empty always fits; a kind
|
|
33
31
|
* this list does not know fits (the custom section's `spans`, whose structure is rowsProblems'). */
|
|
34
32
|
export declare function fits(d: {
|
package/dist/admin/drift.js
CHANGED
|
@@ -12,7 +12,7 @@ export const SEVERITY = {
|
|
|
12
12
|
};
|
|
13
13
|
export const isBroken = (f) => SEVERITY[f.kind] === "broken";
|
|
14
14
|
/** The validator's empty: an empty value is `required`'s question, never drift's. */
|
|
15
|
-
|
|
15
|
+
const isEmptyValue = (v) => v === undefined || v === null || v === "" || (Array.isArray(v) && v.length === 0);
|
|
16
16
|
const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
17
17
|
const isStrings = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
18
18
|
/** Whether a stored value is the shape its field kind stores (§2). Empty always fits; a kind
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { AdminOps } from "../ops.ts";
|
|
2
2
|
import type { AdminRegistry } from "../serialize.ts";
|
|
3
|
-
export declare const withEditorParam: (url: string) => string;
|
|
4
|
-
/** The site URL of a stored version — History's iframe (spec 2026-09-13 history §5); no editor parameter, so no bridge. */
|
|
5
|
-
export declare const withVersionParam: (url: string, versionId: string) => string;
|
|
6
3
|
export declare function EditorView({ ops, registry, id }: {
|
|
7
4
|
ops: AdminOps;
|
|
8
5
|
registry: AdminRegistry;
|
|
@@ -39,7 +39,8 @@ import { tabOf, tabsFor } from "../forms/tabs.js";
|
|
|
39
39
|
import { InspectorFields, valueOf } from "./InspectorFields.js";
|
|
40
40
|
import { allowedSections, canInsert, fillLockedZones, findNode, insertNode, moveNode, moveNodeTo, newNodeId, nodeFromSample, patchNode, patchNodeCss, patchNodePath, patchPage, placementNode, removeNode, zoneOf, clearZone, unsetNodePath, } from "../tree-ops.js";
|
|
41
41
|
import { useDraftAutosave } from "./autosave.js";
|
|
42
|
-
import {
|
|
42
|
+
import { canvasAddress, previewUrl, versionAddress } from "./canvas-address.js";
|
|
43
|
+
import { enableDraft } from "./preview-grant.js";
|
|
43
44
|
import { CanvasOverlay } from "./CanvasOverlay.js";
|
|
44
45
|
import { InlineRichtext } from "./InlineRichtext.js";
|
|
45
46
|
import { InsertPicker } from "./InsertPicker.js";
|
|
@@ -78,9 +79,6 @@ import { usePresence, usePresenceState, useSavedElsewhere, useTrackPresence } fr
|
|
|
78
79
|
import { editorsOf } from "../shell/presence.js";
|
|
79
80
|
import { PresenceLine } from "../ui/PresenceLine.js";
|
|
80
81
|
import { Banner as BannerBox, Breadcrumb, Button, FieldErrorList, IconButton, LinkButton, SaveIndicator, Tabs, TopBar, } from "../ui/primitives.js";
|
|
81
|
-
export const withEditorParam = (url) => (url.includes("?") ? `${url}&smoodly-editor=1` : `${url}?smoodly-editor=1`);
|
|
82
|
-
/** The site URL of a stored version — History's iframe (spec 2026-09-13 history §5); no editor parameter, so no bridge. */
|
|
83
|
-
export const withVersionParam = (url, versionId) => `${url}${url.includes("?") ? "&" : "?"}smoodly-version=${encodeURIComponent(versionId)}`;
|
|
84
82
|
export function EditorView({ ops, registry, id }) {
|
|
85
83
|
const adminHref = useAdminHref();
|
|
86
84
|
const [record, setRecord] = useState(null);
|
|
@@ -200,7 +198,7 @@ export function EditorView({ ops, registry, id }) {
|
|
|
200
198
|
const path = paths[locale] ?? "/";
|
|
201
199
|
const label = path;
|
|
202
200
|
const siteUrl = urls[locale] ?? path;
|
|
203
|
-
const iframeSrc = ready ?
|
|
201
|
+
const iframeSrc = ready ? canvasAddress(siteUrl) : null;
|
|
204
202
|
// Canvas-originated selection: the node is under the cursor already, so
|
|
205
203
|
// this path deliberately skips the scroll every other selection does.
|
|
206
204
|
// (`onCanvasSelect` is declared further down; the handler runs at event time.)
|
|
@@ -240,16 +238,9 @@ export function EditorView({ ops, registry, id }) {
|
|
|
240
238
|
setTouched(false);
|
|
241
239
|
setGlobalDirty(false);
|
|
242
240
|
setGlobalTouched(false);
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
let previewResult;
|
|
247
|
-
try {
|
|
248
|
-
previewResult = await ops.preview.enable();
|
|
249
|
-
}
|
|
250
|
-
catch {
|
|
251
|
-
previewResult = { ok: false, code: "internal", message: "Couldn't reach the server." };
|
|
252
|
-
}
|
|
241
|
+
// The service signs a grant, the site's preview route sets the cookie with it (preview-grant.ts).
|
|
242
|
+
// A rejected call and a refusal at either end land on the same full-pane error, never a hung load.
|
|
243
|
+
const previewResult = await enableDraft(() => ops.preview.enable(), previewUrl());
|
|
253
244
|
if (cancelled)
|
|
254
245
|
return;
|
|
255
246
|
if (!previewResult.ok) {
|
|
@@ -781,6 +772,8 @@ export function EditorView({ ops, registry, id }) {
|
|
|
781
772
|
setGlobalBusy("publish");
|
|
782
773
|
const r = await globalEdit.setStatus(name, "published");
|
|
783
774
|
setGlobalBusy(null);
|
|
775
|
+
if (r.ok)
|
|
776
|
+
notePublished();
|
|
784
777
|
if (!r.ok && !isStale(r))
|
|
785
778
|
setBanner({ text: r.message, fields: r.fields }); // a stale refusal arrives through globalEdit.stale
|
|
786
779
|
};
|
|
@@ -789,6 +782,8 @@ export function EditorView({ ops, registry, id }) {
|
|
|
789
782
|
setGlobalBusy("unpublish");
|
|
790
783
|
const r = await globalEdit.setStatus(name, "draft");
|
|
791
784
|
setGlobalBusy(null);
|
|
785
|
+
if (r.ok)
|
|
786
|
+
notePublished();
|
|
792
787
|
if (!r.ok)
|
|
793
788
|
setBanner({ text: r.message, fields: r.fields });
|
|
794
789
|
};
|
|
@@ -901,7 +896,7 @@ export function EditorView({ ops, registry, id }) {
|
|
|
901
896
|
trailing: registry.locales.supported.length > 1 && (_jsx(LocaleSwitcher, { registry: registry, present: record ? registry.locales.supported.filter((l) => record.locales[l]) : [locale], parentLocales: parentLocales, value: locale, adding: adding, onSwitch: (l) => { void switchLocale(l); }, onAdd: (l) => { void doAddLocale(l); } })),
|
|
902
897
|
},
|
|
903
898
|
{ label },
|
|
904
|
-
] }) }), right: _jsxs(_Fragment, { children: [(touched || saveState === "error" || saveState === "refused") && _jsx(SaveIndicator, { state: saveState, pending: dirty }), ready && (_jsx(LinkButton, { icon: true, href: siteUrl, target: "_blank", rel: "noreferrer", "aria-label": "View live", title: "View live \u2014 opens the site route. While the admin's draft-mode cookie is on, this shows the DRAFT \u2014 open it in a private window to see what visitors see.", children: _jsx(Eye, { size: 16, strokeWidth: 1.5, "aria-hidden": "true" }) })), _jsx(PublishControl, { status: record?.locales[locale] ? localeStatusOf(record.locales[locale]) : null, pending: dirty || saveState !== "saved", busy: pageBusy, flash: published, onPublish: () => { void doPublish(); }, onDiscard: () => { void doDiscard(); }, onUnpublish: () => { void doUnpublish(); }, onHistory: () => { void doHistory(); } })] }) }), target && _jsx(PresenceLine, { names: editorsOf(others, target), noun: "page" }), stale && _jsx(StaleBanner, { info: stale, onReload: loadTheirs }), _jsx(DriftNotice, { draft: findings, live: liveFindings }), history && record?.locales[locale] && (_jsx(HistoryModal, { rows: history, draftId: record.locales[locale].draftVersionId, onClose: () => setHistory(null), onRestore: doRestore, render: (versionId) => _jsx("iframe", { className: "sm-history__frame", title: "Version preview", src:
|
|
899
|
+
] }) }), right: _jsxs(_Fragment, { children: [(touched || saveState === "error" || saveState === "refused") && _jsx(SaveIndicator, { state: saveState, pending: dirty }), ready && (_jsx(LinkButton, { icon: true, href: siteUrl, target: "_blank", rel: "noreferrer", "aria-label": "View live", title: "View live \u2014 opens the site route. While the admin's draft-mode cookie is on, this shows the DRAFT \u2014 open it in a private window to see what visitors see.", children: _jsx(Eye, { size: 16, strokeWidth: 1.5, "aria-hidden": "true" }) })), _jsx(PublishControl, { status: record?.locales[locale] ? localeStatusOf(record.locales[locale]) : null, pending: dirty || saveState !== "saved", busy: pageBusy, flash: published, onPublish: () => { void doPublish(); }, onDiscard: () => { void doDiscard(); }, onUnpublish: () => { void doUnpublish(); }, onHistory: () => { void doHistory(); } })] }) }), target && _jsx(PresenceLine, { names: editorsOf(others, target), noun: "page" }), stale && _jsx(StaleBanner, { info: stale, onReload: loadTheirs }), _jsx(DriftNotice, { draft: findings, live: liveFindings }), history && record?.locales[locale] && (_jsx(HistoryModal, { rows: history, draftId: record.locales[locale].draftVersionId, onClose: () => setHistory(null), onRestore: doRestore, render: (versionId) => _jsx("iframe", { className: "sm-history__frame", title: "Version preview", src: versionAddress(siteUrl, versionId) }) })), _jsxs("div", { className: "sm-editor", inert: stale ? true : undefined, children: [_jsxs("div", { className: "sm-canvas", children: [(banner || diagnosis || crashedLines.length > 0) && (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8, padding: "0 20px 10px" }, children: [banner && (_jsxs(BannerBox, { tone: banner.fields && banner.fields.length > 0 ? "warn" : "error", onDismiss: () => setBanner(null), children: [_jsx("p", { children: banner.text }), _jsx(FieldErrorList, { fields: banner.fields ?? [] })] })), diagnosis && _jsx(CanvasWarning, { cause: diagnosis.cause, path: diagnosis.path }), crashedLines.length > 0 && (_jsx(BannerBox, { tone: "warn", children: crashedLines.map((line, i) => _jsx("p", { children: line }, i)) }))] })), _jsx("div", { className: "sm-canvas__scroll", ref: scrollerRef, onClick: (e) => { if (e.target === e.currentTarget)
|
|
905
900
|
setSelectedId(null); }, children: _jsxs("div", { className: "sm-page", style: { overflow: "hidden" }, children: [iframeSrc ? (_jsx("iframe", { ref: iframeRef, src: iframeSrc, title: "Page canvas", onLoad: onFrameLoad, style: { width: "100%", height: docH, border: 0, display: "block" } })) : (_jsx("div", { className: "sm-canvas__loading", children: "Loading canvas\u2026" })), _jsx(CanvasOverlay, { emptyZones: emptyZones, hoverRect: hoverRect, hoverGlobal: hoverGlobal, selection: selection, fieldHover: fieldHover, editing: editing, plusButtons: plusButtons, onPlus: (p, x, y) => setPicker({ zone: p.zone, index: p.index, x: x - 150, y: y + 12 }), onMove: doMove, onDuplicate: doDuplicate, onRemove: doRemove }), inline.richtext && (() => {
|
|
906
901
|
const { session } = inline.richtext;
|
|
907
902
|
const item = globalItemOf(session.node);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { Allowed } from "../../toolset.ts";
|
|
3
3
|
import { type ActiveState, type ToolbarCommand } from "../forms/RichtextToolbar.tsx";
|
|
4
|
-
export declare const FLOATBAR_W = 340;
|
|
5
4
|
export declare function FloatingToolbar({ x, top, bottom, pageWidth, allowed, active, canInsert, onCommand, popover }: {
|
|
6
5
|
/** The caret's left, top and bottom in overlay coordinates. */
|
|
7
6
|
x: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { RichtextToolbar } from "../forms/RichtextToolbar.js";
|
|
4
|
-
|
|
4
|
+
const FLOATBAR_W = 340;
|
|
5
5
|
const GAP = 10;
|
|
6
6
|
const FLIP_AT = 56;
|
|
7
7
|
export function FloatingToolbar({ x, top, bottom, pageWidth, allowed, active, canInsert, onCommand, popover }) {
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export declare const SEO_TITLE_MAX = 60;
|
|
2
|
-
export declare const SEO_DESCRIPTION_MAX = 160;
|
|
3
1
|
export type SeoKey = "title" | "description" | "ogImage" | "noindex";
|
|
4
2
|
export declare function SeoSettings({ meta, onPatch, titleHint, descriptionHint, imageHint }: {
|
|
5
3
|
meta: Record<string, unknown>;
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { MediaWidget } from "../forms/MediaWidget.js";
|
|
4
4
|
import { Field, Switch } from "../ui/primitives.js";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const SEO_TITLE_MAX = 60;
|
|
6
|
+
const SEO_DESCRIPTION_MAX = 160;
|
|
7
7
|
const str = (v) => (typeof v === "string" ? v : "");
|
|
8
8
|
function Counter({ n, max }) {
|
|
9
9
|
return _jsxs("span", { className: n > max ? "sm-counter--over" : undefined, children: [n, "/", max] });
|
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
export declare function setPreviewBase(value: string): void;
|
|
2
2
|
export declare const draftAddress: (siteUrl: string) => string;
|
|
3
|
+
export declare function setCanvasMarks(value: "path" | "query"): void;
|
|
4
|
+
/** The canvas: the draft with the editor bridge. */
|
|
5
|
+
export declare const canvasAddress: (siteUrl: string) => string;
|
|
6
|
+
/** History's frame (spec 2026-09-13 history §5): a stored version, no bridge. */
|
|
7
|
+
export declare const versionAddress: (siteUrl: string, versionId: string) => string;
|
|
8
|
+
/** The site's preview route, from the page data: where the grant goes. */
|
|
9
|
+
export declare function setPreviewUrl(value: string): void;
|
|
10
|
+
export declare function previewUrl(): string;
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
// Where a page's draft is loaded (DESIGN.md Vision, preview per framework): the server names a base once,
|
|
2
|
-
// in the page's data, and the canvas and the History frame put it before the page's address
|
|
3
|
-
//
|
|
2
|
+
// in the page's data, and the canvas and the History frame put it before the page's address, and says how
|
|
3
|
+
// they mark what they want: in the query, or in the path's last segment where the host strips the query
|
|
4
|
+
// (Next on Vercel; canvas-marker.ts). Module state, set by the browser entry before the shell mounts: the
|
|
5
|
+
// admin is one script on one page.
|
|
6
|
+
import { markCanvasPath } from "../../canvas-marker.js";
|
|
4
7
|
let base = "";
|
|
8
|
+
let marks = "query";
|
|
5
9
|
export function setPreviewBase(value) {
|
|
6
10
|
base = value;
|
|
7
11
|
}
|
|
8
12
|
export const draftAddress = (siteUrl) => base + siteUrl;
|
|
13
|
+
export function setCanvasMarks(value) {
|
|
14
|
+
marks = value;
|
|
15
|
+
}
|
|
16
|
+
const withQuery = (url, key, value) => `${url}${url.includes("?") ? "&" : "?"}${key}=${encodeURIComponent(value)}`;
|
|
17
|
+
/** The canvas: the draft with the editor bridge. */
|
|
18
|
+
export const canvasAddress = (siteUrl) => marks === "path" ? markCanvasPath(draftAddress(siteUrl), { editor: true }) : withQuery(draftAddress(siteUrl), "smoodly-editor", "1");
|
|
19
|
+
/** History's frame (spec 2026-09-13 history §5): a stored version, no bridge. */
|
|
20
|
+
export const versionAddress = (siteUrl, versionId) => marks === "path" ? markCanvasPath(draftAddress(siteUrl), { version: versionId }) : withQuery(draftAddress(siteUrl), "smoodly-version", versionId);
|
|
21
|
+
let url = "";
|
|
22
|
+
/** The site's preview route, from the page data: where the grant goes. */
|
|
23
|
+
export function setPreviewUrl(value) { url = value; }
|
|
24
|
+
export function previewUrl() { return url; }
|
|
@@ -42,6 +42,8 @@ export type CanvasCause = {
|
|
|
42
42
|
export declare function probeOf(requested: string, res: Response): Promise<CanvasProbe>;
|
|
43
43
|
/** The first matching cause, in the spec's order (§4). */
|
|
44
44
|
export declare function diagnoseCanvas(probe: CanvasProbe): CanvasCause;
|
|
45
|
+
/** The page's address from the canvas's: a path-marked canvas (canvas-marker.ts) without its last segment. */
|
|
46
|
+
export declare const pagePathOf: (pathname: string) => string;
|
|
45
47
|
/** The warning's two lines for a cause (§5, verbatim). */
|
|
46
48
|
export declare function canvasMessage(cause: CanvasCause, path: string): {
|
|
47
49
|
editor: string;
|
|
@@ -37,10 +37,13 @@ export function diagnoseCanvas(probe) {
|
|
|
37
37
|
return { kind: "framing", ...framing };
|
|
38
38
|
return { kind: "script" };
|
|
39
39
|
}
|
|
40
|
+
/** The page's address from the canvas's: a path-marked canvas (canvas-marker.ts) without its last segment. */
|
|
41
|
+
export const pagePathOf = (pathname) => pathname.replace(/\/(~|%7[Ee])editor$/, "") || "/";
|
|
40
42
|
/** The redirect's end as a visitor would read it: path + search without the editor parameter, the full URL off-origin. */
|
|
41
43
|
function redirectTarget(url, requested) {
|
|
42
44
|
const u = new URL(url);
|
|
43
45
|
u.searchParams.delete("smoodly-editor");
|
|
46
|
+
u.pathname = pagePathOf(u.pathname);
|
|
44
47
|
return u.origin === new URL(requested).origin ? u.pathname + u.search : u.href;
|
|
45
48
|
}
|
|
46
49
|
/** The header that keeps the canvas iframe out, if any: X-Frame-Options DENY, or a frame-ancestors without the site's own origin. */
|
|
@@ -18,10 +18,6 @@ export declare const isPlainTextHost: (el: {
|
|
|
18
18
|
* refresh reconciles into it; else as new text content. False when the
|
|
19
19
|
* element is not a plain text host. */
|
|
20
20
|
export declare function writeTextInto(el: TextHost, value: string): boolean;
|
|
21
|
-
/** The container's layout the richtext editor root copies (spec §5), so a
|
|
22
|
-
* flex-column or grid container keeps its block spacing while its
|
|
23
|
-
* children are hidden behind the editor. Kebab-cased, for setProperty. */
|
|
24
|
-
export declare const LAYOUT_PROPS: readonly ["display", "flex-direction", "gap", "row-gap", "column-gap", "grid-template-columns", "grid-auto-flow", "align-items", "justify-content"];
|
|
25
21
|
export declare function layoutToCopy(computed: {
|
|
26
22
|
getPropertyValue(name: string): string;
|
|
27
23
|
}): Record<string, string>;
|
|
@@ -25,7 +25,7 @@ export function writeTextInto(el, value) {
|
|
|
25
25
|
/** The container's layout the richtext editor root copies (spec §5), so a
|
|
26
26
|
* flex-column or grid container keeps its block spacing while its
|
|
27
27
|
* children are hidden behind the editor. Kebab-cased, for setProperty. */
|
|
28
|
-
|
|
28
|
+
const LAYOUT_PROPS = [
|
|
29
29
|
"display", "flex-direction", "gap", "row-gap", "column-gap",
|
|
30
30
|
"grid-template-columns", "grid-auto-flow", "align-items", "justify-content",
|
|
31
31
|
];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OpResult, PreviewGrant } from "../ops.ts";
|
|
2
|
+
export declare function postPreviewGrant(url: string, action: "enable" | "disable", grant: PreviewGrant, fetcher?: typeof fetch): Promise<OpResult<null>>;
|
|
3
|
+
/** The editor's load: the operation, then the grant to the site. The operation is a fetch to the service
|
|
4
|
+
* and can reject outright (offline, aborted), not just resolve to a typed error; both land on one typed
|
|
5
|
+
* result. A null grant means the server set the cookie itself: nothing to post. */
|
|
6
|
+
export declare function enableDraft(enable: () => Promise<OpResult<PreviewGrant | null>>, url: string, fetcher?: typeof fetch): Promise<OpResult<null>>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export async function postPreviewGrant(url, action, grant, fetcher = fetch) {
|
|
2
|
+
try {
|
|
3
|
+
const res = await fetcher(url, { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json" }, body: JSON.stringify({ action, grant }) });
|
|
4
|
+
const body = (await res.json());
|
|
5
|
+
return typeof body === "object" && body !== null && typeof body.ok === "boolean" ? body : { ok: false, code: "internal", message: `smoodly: the preview route answered ${res.status} without a result.` };
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return { ok: false, code: "internal", message: "Couldn't reach the site's preview route." };
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** The editor's load: the operation, then the grant to the site. The operation is a fetch to the service
|
|
12
|
+
* and can reject outright (offline, aborted), not just resolve to a typed error; both land on one typed
|
|
13
|
+
* result. A null grant means the server set the cookie itself: nothing to post. */
|
|
14
|
+
export async function enableDraft(enable, url, fetcher = fetch) {
|
|
15
|
+
let result;
|
|
16
|
+
try {
|
|
17
|
+
result = await enable();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { ok: false, code: "internal", message: "Couldn't reach the server." };
|
|
21
|
+
}
|
|
22
|
+
if (!result.ok)
|
|
23
|
+
return result;
|
|
24
|
+
return result.data ? postPreviewGrant(url, "enable", result.data, fetcher) : { ok: true, data: null };
|
|
25
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// along and the server renders what the frame got — and name the cause.
|
|
5
5
|
// A new src, or the bridge speaking after all, drops the answer.
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
|
-
import { diagnoseCanvas, probeOf } from "./canvas-diagnosis.js";
|
|
7
|
+
import { diagnoseCanvas, pagePathOf, probeOf } from "./canvas-diagnosis.js";
|
|
8
8
|
export function useCanvasDiagnosis(iframeSrc, geometry) {
|
|
9
9
|
const [diagnosis, setDiagnosis] = useState(null);
|
|
10
10
|
useEffect(() => {
|
|
@@ -17,7 +17,7 @@ export function useCanvasDiagnosis(iframeSrc, geometry) {
|
|
|
17
17
|
.then((res) => probeOf(requested.href, res))
|
|
18
18
|
.catch(() => ({ kind: "failed" }))
|
|
19
19
|
.then((probe) => { if (live)
|
|
20
|
-
setDiagnosis({ cause: diagnoseCanvas(probe), path: requested.pathname }); });
|
|
20
|
+
setDiagnosis({ cause: diagnoseCanvas(probe), path: pagePathOf(requested.pathname) }); });
|
|
21
21
|
return () => { live = false; };
|
|
22
22
|
}, [iframeSrc, geometry]);
|
|
23
23
|
return diagnosis;
|