@tideworthy/tidysites-platform 0.5.1 → 0.6.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.1
4
+
5
+ - Editor: EmDash's toolbar is hidden while editing is off, so the Edit pill is the one control on the page. It still shows on article pages in edit mode, where EmDash's inline editing needs it.
6
+
7
+ ## 0.6.0
8
+
9
+ - `GET /_tidy/edit?on=1|0&to=/path`: switches on-site editing on or off for the signed-in editor (the `emdash-edit-mode` cookie) and returns to the page. No bearer; a visitor without a session is sent back unchanged.
10
+ - Editor: mounts itself. The layout renders `<TidyEditor>` on every page; it shows nothing to a visitor, an "Edit" pill to a signed-in editor, and the editor once editing is on. A page without blocks gets the pill only (EmDash's inline editing handles its prose). The pill gains "Done". Every prop is optional now; EmDash's toolbar is hidden only while this editor runs (`html.tidy-editing`).
11
+
3
12
  ## 0.5.1
4
13
 
5
14
  - Editor: a record region nested inside a section (a service card in the services grid) gets its own "Edit details" chip; clicking it edits the record instead of following the card's link, and leaving it hands the chip back to the section.
package/README.md CHANGED
@@ -96,6 +96,10 @@ Mints a one-time sign-in link for a person the platform has already authenticate
96
96
 
97
97
  The browser lands here. Signs the user in (an Astro session, exactly as EmDash's own login does), sets or clears the visual-editing cookie, and redirects with 303 to the requested path. A link can be used once. Expired, reused, or unknown links get a 410 page.
98
98
 
99
+ ### `GET /_tidy/edit?on=1|0&to=/path`
100
+
101
+ No bearer. For the browser session the claim signed in: sets (`on=1`) or clears the `emdash-edit-mode` cookie and answers 303 to `to` (a local path; anything else becomes `/`). Without a signed-in session it answers the same 303 and changes nothing. The editor's pill links here.
102
+
99
103
  ### `GET /_tidy/health`
100
104
 
101
105
  `{ ok, plugin, setupComplete, siteTitle, siteUrl, migrations: { applied, pending }, time }`. `ok` is false and the status is 503 when migrations are pending.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tideworthy/tidysites-platform",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "The Tidysites platform integration for EmDash sites: bootstrap, sessions, maintenance, health, and publish hooks under /_tidy/*.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,6 +25,7 @@
25
25
  "./routes/bootstrap": "./src/routes/bootstrap.ts",
26
26
  "./routes/session": "./src/routes/session.ts",
27
27
  "./routes/session-claim": "./src/routes/session-claim.ts",
28
+ "./routes/edit": "./src/routes/edit.ts",
28
29
  "./routes/health": "./src/routes/health.ts",
29
30
  "./routes/rotate-token": "./src/routes/rotate-token.ts",
30
31
  "./routes/fill": "./src/routes/fill.ts",
@@ -1,31 +1,54 @@
1
1
  ---
2
2
  /**
3
- * The Tideworthy on-site editor for block-built pages. Render it from the
4
- * layout when the page is in edit mode, with the entry the page shows, the
5
- * block schemas the template declares, and the block list as rendered. The
6
- * template frames each custom block with `data-tidy-block` (its key) and
7
- * `data-tidy-type`, and wraps the list in `data-tidy-blocks`. A record region
8
- * (`data-tidy-record`, `data-tidy-record-id`, `data-tidy-record-label`) gets an
9
- * "Edit details" chip whose form comes from EmDash's manifest.
3
+ * The Tideworthy on-site editor. Mount it from the layout on every page; it
4
+ * renders nothing for a visitor, an "Edit" pill for a signed-in editor, and
5
+ * the editor itself once editing is on (the `emdash-edit-mode` cookie, set
6
+ * and cleared by /_tidy/edit from the pill). Block pages hand it the entry
7
+ * they show, the block schemas the template declares, and the block list as
8
+ * rendered; the template frames each custom block with `data-tidy-block`
9
+ * (its key) and `data-tidy-type`, and wraps the list in `data-tidy-blocks`.
10
+ * A record region (`data-tidy-record`, `data-tidy-record-id`,
11
+ * `data-tidy-record-label`) gets an "Edit details" chip whose form comes
12
+ * from EmDash's manifest. A page without blocks (an article) gets the pill
13
+ * only; EmDash's inline editing handles its prose in edit mode. EmDash's own
14
+ * toolbar shows only there: while editing is off the pill is the one control.
10
15
  */
11
16
  import type { Block, BlockSchema } from "./ops";
12
17
 
13
18
  interface Props {
14
- collection: string;
15
- id: string;
19
+ collection?: string;
20
+ id?: string;
16
21
  field?: string;
17
- blocks: BlockSchema[];
18
- value: Block[];
22
+ blocks?: BlockSchema[];
23
+ value?: Block[] | null;
19
24
  /** Records reachable from the pill (the business) or from `[data-tidy-record]` regions on the page. */
20
25
  records?: Array<{ collection: string; id: string; label: string }>;
21
26
  }
22
27
  const { collection, id, field = "content", blocks, value, records = [] } = Astro.props;
23
- const config = JSON.stringify({ collection, id, field, blocks, value, records });
28
+ const signedIn = Boolean(Astro.session ? await Astro.session.get("user") : null);
29
+ const active = Astro.cookies.get("emdash-edit-mode")?.value === "true";
30
+ const here = Astro.url.pathname + Astro.url.search;
31
+ const toggle = (on: boolean) => `/_tidy/edit?on=${on ? 1 : 0}&to=${encodeURIComponent(here)}`;
32
+ const editable = active && Boolean(collection && id && blocks && value);
33
+ const config = editable ? JSON.stringify({ collection, id, field, blocks, value, records, done: toggle(false) }) : null;
24
34
  ---
25
- <script type="application/json" id="tidy-editor-config" set:html={config.replace(/</g, "\\u003c")} />
26
- <script src="./runtime.ts"></script>
35
+ {signedIn && !active && (
36
+ <>
37
+ <div class="tidy-status tidy-status-idle"><a class="tidy-btn tidy-btn-primary" href={toggle(true)}>Edit</a></div>
38
+ <script is:inline>document.documentElement.classList.add("tidy-idle");</script>
39
+ </>
40
+ )}
41
+ {signedIn && active && !editable && (
42
+ <div class="tidy-status"><span>Editing</span><a class="tidy-btn" href={toggle(false)}>Done</a></div>
43
+ )}
44
+ {config && (
45
+ <>
46
+ <script type="application/json" id="tidy-editor-config" set:html={config.replace(/</g, "\\u003c")} />
47
+ <script src="./runtime.ts"></script>
48
+ </>
49
+ )}
27
50
  <style is:global>
28
- #emdash-toolbar { display: none !important; }
51
+ .tidy-editing #emdash-toolbar, .tidy-idle #emdash-toolbar { display: none !important; }
29
52
  [data-tidy-record] { outline: 2px dashed transparent; outline-offset: 4px; transition: outline-color 120ms; cursor: pointer; }
30
53
  [data-tidy-record]:hover, [data-tidy-record].tidy-active { outline-color: #2b7fd1; }
31
54
  .tidy-image-preview { display: grid; place-items: center; min-height: 96px; border: 1px solid #d3dcd6; border-radius: 6px; background: #f6f7f5; overflow: hidden; }
@@ -71,4 +94,6 @@ const config = JSON.stringify({ collection, id, field, blocks, value, records })
71
94
  .tidy-add-item:hover { border-color: #2b7fd1; background: rgba(43,127,209,.06); }
72
95
  .tidy-add-item span { color: #4a5851; font-size: 13px; }
73
96
  .tidy-status { position: fixed; left: 16px; bottom: 16px; z-index: 2147483000; display: flex; gap: 10px; align-items: center; padding: 8px 12px; background: #17211c; color: #fff; border-radius: 999px; font: 500 13px system-ui, sans-serif; box-shadow: 0 6px 20px rgba(0,0,0,.25); }
97
+ .tidy-status a { text-decoration: none; }
98
+ .tidy-status-idle { padding: 4px; }
74
99
  </style>
@@ -32,6 +32,8 @@ interface Config {
32
32
  blocks: BlockSchema[];
33
33
  value: Block[];
34
34
  records?: RecordRef[];
35
+ /** Where "Done" goes: /_tidy/edit switching editing off and returning here. */
36
+ done?: string;
35
37
  }
36
38
 
37
39
  interface ManifestField {
@@ -88,6 +90,7 @@ export function start(config: Config): void {
88
90
  const frames = () => Array.from(document.querySelectorAll<HTMLElement>("[data-tidy-block]"));
89
91
 
90
92
  // EmDash's inline editor is for prose pages; here the sections and records own their fields.
93
+ document.documentElement.classList.add("tidy-editing");
91
94
  document.querySelectorAll("[data-emdash-ref]").forEach((n) => n.removeAttribute("data-emdash-ref"));
92
95
 
93
96
  // --- pending publishes ---------------------------------------------------
@@ -99,7 +102,8 @@ export function start(config: Config): void {
99
102
  const statusText = el("span");
100
103
  const bizBtn = el("button", { type: "button", class: "tidy-btn", text: "Business details" });
101
104
  const publishBtn = el("button", { type: "button", class: "tidy-btn tidy-btn-primary", text: "Publish" });
102
- status.append(statusText, bizBtn, publishBtn);
105
+ const doneBtn = el("a", { class: "tidy-btn", href: config.done ?? `/_tidy/edit?on=0&to=${encodeURIComponent(location.pathname + location.search)}`, text: "Done" });
106
+ status.append(statusText, bizBtn, publishBtn, doneBtn);
103
107
  document.body.append(status);
104
108
  const business = (config.records ?? []).find((r) => r.collection === "business");
105
109
  bizBtn.hidden = !business;
package/src/index.ts CHANGED
@@ -25,6 +25,7 @@ export const PLATFORM_ROUTES = [
25
25
  { pattern: "/_tidy/bootstrap", entrypoint: `${PACKAGE}/routes/bootstrap` },
26
26
  { pattern: "/_tidy/session", entrypoint: `${PACKAGE}/routes/session` },
27
27
  { pattern: "/_tidy/session/claim", entrypoint: `${PACKAGE}/routes/session-claim` },
28
+ { pattern: "/_tidy/edit", entrypoint: `${PACKAGE}/routes/edit` },
28
29
  { pattern: "/_tidy/health", entrypoint: `${PACKAGE}/routes/health` },
29
30
  { pattern: "/_tidy/rotate-token", entrypoint: `${PACKAGE}/routes/rotate-token` },
30
31
  { pattern: "/_tidy/fill", entrypoint: `${PACKAGE}/routes/fill` },
@@ -0,0 +1,26 @@
1
+ /**
2
+ * GET /_tidy/edit?on=1|0&to=/path: switch on-site editing on or off for the
3
+ * signed-in editor and go back to the page. No bearer: the browser calls it
4
+ * from the editor's pill. Anyone without a session is sent back unchanged.
5
+ */
6
+ import type { APIRoute } from "astro";
7
+ import { isSafeLocalPath } from "../lib/http";
8
+ import { methodNotAllowed } from "./_shared";
9
+
10
+ export const prerender = false;
11
+
12
+ const EDIT_COOKIE = "emdash-edit-mode";
13
+
14
+ export const GET: APIRoute = async (context) => {
15
+ const to = context.url.searchParams.get("to");
16
+ const back = isSafeLocalPath(to) ? to : "/";
17
+ const headers = { location: back, "cache-control": "no-store" };
18
+ const user = context.session ? await context.session.get("user") : null;
19
+ if (!user) return new Response(null, { status: 303, headers });
20
+ const secure = context.url.protocol === "https:";
21
+ if (context.url.searchParams.get("on") === "1") context.cookies.set(EDIT_COOKIE, "true", { path: "/", sameSite: "lax", secure });
22
+ else context.cookies.delete(EDIT_COOKIE, { path: "/" });
23
+ return new Response(null, { status: 303, headers });
24
+ };
25
+
26
+ export const ALL = methodNotAllowed("GET");
package/src/version.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Kept in step with package.json by the release checklist in MAINTAINING.md. */
2
- export const VERSION = "0.5.1";
2
+ export const VERSION = "0.6.1";
3
3
  export const PLUGIN_ID = "tidy-platform";