@tideworthy/tidysites-platform 0.6.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0
4
+
5
+ - Editor: the corner pill is a bar across the top of the page. "Staging" (the `label` prop) on the left; Edit on the right while editing is off; Business details, Publish (with the count, only while something is unpublished) and Done while it is on. The bar sets `--tidy-bar` on `<html>` so a template's sticky header can sit below it.
6
+ - Editor: editing is a page edit. Inside the sections a click edits the block or record it lands on and never follows a link; a click on the header or footer (outside the menu) opens Business details.
7
+ - Editor: section and record outlines show only while editing. They used to show on hover for every visitor, live site included.
8
+
9
+ ## 0.6.1
10
+
11
+ - 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.
12
+
3
13
  ## 0.6.0
4
14
 
5
15
  - `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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tideworthy/tidysites-platform",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
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",
@@ -1,16 +1,19 @@
1
1
  ---
2
2
  /**
3
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.
4
+ * renders nothing for a visitor and, for a signed-in editor, a bar across the
5
+ * top of the page: "Edit" while editing is off, the editor's controls while
6
+ * it is on (the `emdash-edit-mode` cookie, set and cleared by /_tidy/edit).
7
+ * Block pages hand it the entry they show, the block schemas the template
8
+ * declares, and the block list as rendered; the template frames each custom
9
+ * block with `data-tidy-block` (its key) and `data-tidy-type`, and wraps the
10
+ * list in `data-tidy-blocks`. A record region (`data-tidy-record`,
11
+ * `data-tidy-record-id`, `data-tidy-record-label`) gets an "Edit details"
12
+ * chip whose form comes from EmDash's manifest. A page without blocks (an
13
+ * article) gets the bar only; EmDash's inline editing handles its prose and
14
+ * its toolbar shows there. While editing is off, EmDash's toolbar is hidden:
15
+ * the bar is the one control. The bar sets `--tidy-bar` on <html> (its
16
+ * height) so a template's sticky header can sit below it.
14
17
  */
15
18
  import type { Block, BlockSchema } from "./ops";
16
19
 
@@ -20,22 +23,31 @@ interface Props {
20
23
  field?: string;
21
24
  blocks?: BlockSchema[];
22
25
  value?: Block[] | null;
23
- /** Records reachable from the pill (the business) or from `[data-tidy-record]` regions on the page. */
26
+ /** Records reachable from the bar (the business) or from `[data-tidy-record]` regions on the page. */
24
27
  records?: Array<{ collection: string; id: string; label: string }>;
28
+ /** What the bar calls this site; "Staging" by default. */
29
+ label?: string;
25
30
  }
26
- const { collection, id, field = "content", blocks, value, records = [] } = Astro.props;
31
+ const { collection, id, field = "content", blocks, value, records = [], label = "Staging" } = Astro.props;
27
32
  const signedIn = Boolean(Astro.session ? await Astro.session.get("user") : null);
28
33
  const active = Astro.cookies.get("emdash-edit-mode")?.value === "true";
29
34
  const here = Astro.url.pathname + Astro.url.search;
30
35
  const toggle = (on: boolean) => `/_tidy/edit?on=${on ? 1 : 0}&to=${encodeURIComponent(here)}`;
31
36
  const editable = active && Boolean(collection && id && blocks && value);
32
37
  const config = editable ? JSON.stringify({ collection, id, field, blocks, value, records, done: toggle(false) }) : null;
38
+ const htmlClass = !signedIn ? "" : !active ? "tidy-bar tidy-idle" : editable ? "tidy-bar" : "tidy-bar tidy-inline";
33
39
  ---
34
- {signedIn && !active && (
35
- <div class="tidy-status tidy-status-idle"><a class="tidy-btn tidy-btn-primary" href={toggle(true)}>Edit</a></div>
36
- )}
37
- {signedIn && active && !editable && (
38
- <div class="tidy-status"><span>Editing</span><a class="tidy-btn" href={toggle(false)}>Done</a></div>
40
+ {signedIn && (
41
+ <>
42
+ <div class="tidy-bar" id="tidy-bar" role="region" aria-label="Site editing">
43
+ <span class="tidy-bar-label">{label}</span>
44
+ <div class="tidy-bar-actions" id="tidy-bar-actions">
45
+ {!active && <a class="tidy-btn tidy-btn-primary" href={toggle(true)}>Edit</a>}
46
+ {active && !editable && <a class="tidy-btn" href={toggle(false)}>Done</a>}
47
+ </div>
48
+ </div>
49
+ <script is:inline define:vars={{ htmlClass }}>document.documentElement.className += " " + htmlClass;</script>
50
+ </>
39
51
  )}
40
52
  {config && (
41
53
  <>
@@ -44,9 +56,18 @@ const config = editable ? JSON.stringify({ collection, id, field, blocks, value,
44
56
  </>
45
57
  )}
46
58
  <style is:global>
47
- .tidy-editing #emdash-toolbar { display: none !important; }
48
- [data-tidy-record] { outline: 2px dashed transparent; outline-offset: 4px; transition: outline-color 120ms; cursor: pointer; }
49
- [data-tidy-record]:hover, [data-tidy-record].tidy-active { outline-color: #2b7fd1; }
59
+ .tidy-editing #emdash-toolbar, .tidy-idle #emdash-toolbar { display: none !important; }
60
+ html.tidy-bar { --tidy-bar: 44px; }
61
+ html.tidy-bar body { padding-top: var(--tidy-bar); }
62
+ .tidy-bar { position: fixed; top: 0; left: 0; right: 0; height: var(--tidy-bar, 44px); z-index: 2147483000; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 16px; box-sizing: border-box; background: #17211c; color: #fff; font: 500 13px/1 system-ui, sans-serif; box-shadow: 0 1px 0 rgba(255,255,255,.08), 0 6px 20px rgba(0,0,0,.18); }
63
+ .tidy-bar-label { opacity: .7; letter-spacing: .04em; text-transform: uppercase; font-size: 11px; }
64
+ .tidy-bar-actions { display: flex; align-items: center; gap: 8px; }
65
+ .tidy-bar-actions a { text-decoration: none; }
66
+ .tidy-bar .tidy-btn { padding: 6px 12px; }
67
+ .tidy-bar-count { opacity: .85; margin-right: 4px; }
68
+ .tidy-bar-count:empty { display: none; }
69
+ .tidy-editing [data-tidy-record] { outline: 2px dashed transparent; outline-offset: 4px; transition: outline-color 120ms; cursor: pointer; }
70
+ .tidy-editing [data-tidy-record]:hover, .tidy-editing [data-tidy-record].tidy-active { outline-color: #2b7fd1; }
50
71
  .tidy-image-preview { display: grid; place-items: center; min-height: 96px; border: 1px solid #d3dcd6; border-radius: 6px; background: #f6f7f5; overflow: hidden; }
51
72
  .tidy-image-preview img { max-width: 100%; max-height: 180px; display: block; }
52
73
  .tidy-image-empty { color: #7b877f; font-size: 13px; padding: 8px; }
@@ -56,10 +77,10 @@ const config = editable ? JSON.stringify({ collection, id, field, blocks, value,
56
77
  .tidy-image-thumb { padding: 0; border: 2px solid transparent; border-radius: 6px; overflow: hidden; background: #fff; cursor: pointer; aspect-ratio: 1; }
57
78
  .tidy-image-thumb:hover { border-color: #2b7fd1; }
58
79
  .tidy-image-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
59
- [data-tidy-block] { position: relative; outline: 2px dashed transparent; outline-offset: -2px; transition: outline-color 120ms; cursor: pointer; }
60
- [data-tidy-block]:hover, [data-tidy-block].tidy-active { outline-color: #2b7fd1; }
80
+ .tidy-editing [data-tidy-block] { position: relative; outline: 2px dashed transparent; outline-offset: -2px; transition: outline-color 120ms; cursor: pointer; }
81
+ .tidy-editing [data-tidy-block]:hover, .tidy-editing [data-tidy-block].tidy-active { outline-color: #2b7fd1; }
61
82
  .tidy-chip { position: absolute; z-index: 2147483000; display: flex; gap: 4px; align-items: center; padding: 4px 6px; background: #17211c; color: #fff; border-radius: 8px; font: 500 13px/1.2 system-ui, sans-serif; box-shadow: 0 6px 20px rgba(0,0,0,.25); }
62
- .tidy-chip[hidden], .tidy-panel[hidden], .tidy-status[hidden] { display: none; }
83
+ .tidy-chip[hidden], .tidy-panel[hidden] { display: none; }
63
84
  .tidy-chip-label { padding: 0 6px 0 4px; opacity: .85; }
64
85
  .tidy-btn { appearance: none; border: 1px solid rgba(255,255,255,.25); background: rgba(255,255,255,.08); color: inherit; border-radius: 6px; padding: 4px 8px; font: inherit; cursor: pointer; }
65
86
  .tidy-btn:hover { background: rgba(255,255,255,.18); }
@@ -89,7 +110,4 @@ const config = editable ? JSON.stringify({ collection, id, field, blocks, value,
89
110
  .tidy-add-item { display: grid; gap: 2px; text-align: left; padding: 10px 12px; border: 1px solid #d3dcd6; border-radius: 8px; background: #fff; cursor: pointer; font: inherit; }
90
111
  .tidy-add-item:hover { border-color: #2b7fd1; background: rgba(43,127,209,.06); }
91
112
  .tidy-add-item span { color: #4a5851; font-size: 13px; }
92
- .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); }
93
- .tidy-status a { text-decoration: none; }
94
- .tidy-status-idle { padding: 4px; }
95
113
  </style>
@@ -97,20 +97,19 @@ export function start(config: Config): void {
97
97
  const pending = new Set<string>(JSON.parse(sessionStorage.getItem(PENDING_KEY) ?? "[]") as string[]);
98
98
  const persistPending = () => sessionStorage.setItem(PENDING_KEY, JSON.stringify([...pending]));
99
99
 
100
- // --- status pill ---------------------------------------------------------
101
- const status = el("div", { class: "tidy-status" });
102
- const statusText = el("span");
100
+ // --- the bar's controls --------------------------------------------------
101
+ const status = document.getElementById("tidy-bar-actions") ?? document.body.appendChild(el("div", { class: "tidy-bar-actions" }));
102
+ const statusText = el("span", { class: "tidy-bar-count" });
103
103
  const bizBtn = el("button", { type: "button", class: "tidy-btn", text: "Business details" });
104
104
  const publishBtn = el("button", { type: "button", class: "tidy-btn tidy-btn-primary", text: "Publish" });
105
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);
107
- document.body.append(status);
106
+ status.replaceChildren(statusText, bizBtn, publishBtn, doneBtn);
108
107
  const business = (config.records ?? []).find((r) => r.collection === "business");
109
108
  bizBtn.hidden = !business;
110
109
  bizBtn.addEventListener("click", () => business && void openRecord(business));
111
110
  function refreshStatus(text?: string) {
112
111
  const n = pending.size;
113
- statusText.textContent = text ?? (n === 0 ? "Editing" : `${n} unpublished change${n === 1 ? "" : "s"}`);
112
+ statusText.textContent = text ?? (n === 0 ? "" : `${n} unpublished change${n === 1 ? "" : "s"}`);
114
113
  publishBtn.hidden = n === 0 && !text?.startsWith("Publish failed");
115
114
  }
116
115
  refreshStatus();
@@ -228,6 +227,38 @@ export function start(config: Config): void {
228
227
  void openRecord({ collection: r.dataset.tidyRecord ?? "", id: r.dataset.tidyRecordId ?? "", label: r.dataset.tidyRecordLabel ?? "" });
229
228
  });
230
229
  }
230
+ // Editing is a page edit: inside the sections a click edits what it lands
231
+ // on and never follows a link; outside them the header and footer show the
232
+ // business record's facts, so a click there opens Business details. The
233
+ // menu keeps walking the site, and the editor's own controls are untouched.
234
+ document.addEventListener(
235
+ "click",
236
+ (e) => {
237
+ const t = e.target as HTMLElement | null;
238
+ if (!t || t.closest(".tidy-bar,.tidy-chip,.tidy-panel,.tidy-add-end,#emdash-toolbar")) return;
239
+ if (t.closest("[data-tidy-blocks]")) {
240
+ if (!t.closest("a,button")) return;
241
+ e.preventDefault();
242
+ e.stopPropagation();
243
+ const record = t.closest<HTMLElement>("[data-tidy-record]");
244
+ const frame = t.closest<HTMLElement>("[data-tidy-block]");
245
+ if (record) {
246
+ showRecordChip(record);
247
+ void openRecord({ collection: record.dataset.tidyRecord ?? "", id: record.dataset.tidyRecordId ?? "", label: record.dataset.tidyRecordLabel ?? "" });
248
+ } else if (frame) {
249
+ showBlockChip(frame);
250
+ openBlock(frame.dataset.tidyBlock ?? "");
251
+ }
252
+ return;
253
+ }
254
+ if (t.closest("main") || t.closest("nav a, header button, input, select, textarea, label")) return;
255
+ if (!business) return;
256
+ e.preventDefault();
257
+ e.stopPropagation();
258
+ void openRecord(business);
259
+ },
260
+ true
261
+ );
231
262
  // The chip sits outside every frame; hovering it must not count as leaving.
232
263
  chip.addEventListener("mouseenter", () => current?.classList.add("tidy-active"));
233
264
  bEdit.addEventListener("click", () => {
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.6.0";
2
+ export const VERSION = "0.7.0";
3
3
  export const PLUGIN_ID = "tidy-platform";