@tideworthy/tidysites-platform 0.10.12 → 0.10.14

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.14
4
+
5
+ - Editor bar: Edit, Done and Sign in to edit show a spinner and what they are doing until the page comes back.
6
+
7
+ ## 0.10.13
8
+
9
+ - Editor: the request panel opens at once; who is signed in is read when the page loads and the greeting fills in. The middleware keeps only the noindex header; robots.txt and the sitemap for staging are the control plane's.
10
+
3
11
  ## 0.10.12
4
12
 
5
13
  - The staging site is never indexed: every response carries `X-Robots-Tag: noindex, nofollow`, robots.txt disallows everything and the sitemap answers 404. The publish crawl still gets the real robots.txt and sitemap for the live copy.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tideworthy/tidysites-platform",
3
- "version": "0.10.12",
3
+ "version": "0.10.14",
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",
@@ -57,12 +57,23 @@ const htmlClass = !signedIn ? (signInUrl ? "tidy-has-bar tidy-idle" : "") : !act
57
57
  <div class="tidy-bar" id="tidy-bar" role="region" aria-label="Site editing">
58
58
  <span class="tidy-bar-label"><span class="tidy-bar-dot" aria-hidden="true"></span>{label} website<span class="tidy-bar-note">A private copy to see working changes on the website, not publicly accessible.</span></span>
59
59
  <div class="tidy-bar-actions" id="tidy-bar-actions">
60
- {!signedIn && <a class="tidy-btn tidy-btn-primary" href={signInUrl!}>Sign in to edit</a>}
61
- {signedIn && !active && <a class="tidy-btn" href={toggle(true)}>Edit</a>}
62
- {signedIn && active && !editable && <a class="tidy-btn" href={toggle(false)}>Done</a>}
60
+ {!signedIn && <a class="tidy-btn tidy-btn-primary" href={signInUrl!} data-tidy-busy="Signing in">Sign in to edit</a>}
61
+ {signedIn && !active && <a class="tidy-btn" href={toggle(true)} data-tidy-busy="Opening the editor">Edit</a>}
62
+ {signedIn && active && !editable && <a class="tidy-btn" href={toggle(false)} data-tidy-busy="Closing the editor">Done</a>}
63
63
  </div>
64
64
  </div>
65
65
  <script is:inline define:vars={{ htmlClass }}>document.documentElement.className += " " + htmlClass;</script>
66
+ <script is:inline>
67
+ // A bar button that reloads the page shows it is working until the new page arrives.
68
+ document.querySelectorAll("#tidy-bar a[data-tidy-busy]").forEach((a) => {
69
+ a.addEventListener("click", () => {
70
+ if (a.classList.contains("tidy-busy")) return;
71
+ a.classList.add("tidy-busy");
72
+ a.setAttribute("aria-busy", "true");
73
+ a.innerHTML = '<span class="tidy-spinner" aria-hidden="true"></span>' + a.getAttribute("data-tidy-busy");
74
+ });
75
+ });
76
+ </script>
66
77
  {signedIn && <script src="./revisions.ts"></script>}
67
78
  {signedIn && <script src="./request.ts"></script>}
68
79
  </>
@@ -110,6 +121,9 @@ const htmlClass = !signedIn ? (signInUrl ? "tidy-has-bar tidy-idle" : "") : !act
110
121
  .tidy-btn-primary { background: #2b7fd1; border-color: #2b7fd1; color: #fff; }
111
122
  .tidy-btn-primary:hover { background: #2470bd; }
112
123
  .tidy-btn-danger:hover { background: #b3261e; border-color: #b3261e; }
124
+ .tidy-busy { pointer-events: none; opacity: .85; display: inline-flex; align-items: center; gap: 8px; }
125
+ .tidy-spinner { width: 12px; height: 12px; border-radius: 50%; border: 2px solid rgba(255,255,255,.35); border-top-color: #fff; animation: tidy-spin .7s linear infinite; }
126
+ @keyframes tidy-spin { to { transform: rotate(360deg); } }
113
127
  .tidy-btn-go { background: #1f9d55; border-color: #1f9d55; color: #fff; }
114
128
  .tidy-btn-go:hover { background: #1a8a4a; }
115
129
  .tidy-btn-sm { padding: 3px 8px; font-size: 12px; }
@@ -27,16 +27,27 @@ export function mountRequestChange(): void {
27
27
  function close() {
28
28
  panel.hidden = true;
29
29
  panel.replaceChildren();
30
+ greeting = null;
30
31
  }
31
32
 
33
+ // Who is signed in, read once the page is up so the panel opens at once; the greeting fills in when it arrives.
32
34
  let who: { name: string | null } | null = null;
33
35
  const firstName = () => (who?.name ?? "").trim().split(/\s+/)[0] || "";
36
+ let greeting: HTMLElement | null = null;
37
+ const whoReady = fetch(ROUTE, { credentials: "same-origin" })
38
+ .then((r) => (r.ok ? r.json() : null))
39
+ .then((j) => {
40
+ who = (j as { name: string | null } | null) ?? null;
41
+ if (greeting && firstName()) greeting.textContent = `Hi ${firstName()},`;
42
+ })
43
+ .catch(() => undefined);
34
44
 
35
- async function render() {
36
- if (!who) who = (await fetch(ROUTE, { credentials: "same-origin" }).then((r) => (r.ok ? r.json() : null)).catch(() => null)) as { name: string | null } | null;
45
+ function render() {
46
+ void whoReady;
37
47
  const name = firstName();
48
+ greeting = el("h2", { text: name ? `Hi ${name},` : "Hi there," });
38
49
  const head = el("div", { class: "tidy-panel-head" }, [
39
- el("h2", { text: name ? `Hi ${name},` : "Hi there," }),
50
+ greeting,
40
51
  el("p", { text: "We would love to help you make some adjustments to your website. Describe what you would like to see different or new, and we will get a version back to you as soon as possible." })
41
52
  ]);
42
53
  const body = el("textarea", { id: "tidy-change-body", rows: "7", maxlength: "10000", required: "" });
@@ -61,7 +72,7 @@ export function mountRequestChange(): void {
61
72
  sending = false;
62
73
  if (r?.ok && j.ok) {
63
74
  panel.replaceChildren(
64
- el("div", { class: "tidy-panel-head" }, [el("h2", { text: name ? `Thanks, ${name}.` : "Thanks." }), el("p", { text: "We have your request and will get a version back to you as soon as possible." })]),
75
+ el("div", { class: "tidy-panel-head" }, [el("h2", { text: firstName() ? `Thanks, ${firstName()}.` : "Thanks." }), el("p", { text: "We have your request and will get a version back to you as soon as possible." })]),
65
76
  el("div", { class: "tidy-panel-actions" }, [el("button", { type: "button", class: "tidy-btn", text: "Close" })])
66
77
  );
67
78
  panel.querySelector("button")?.addEventListener("click", close);
package/src/middleware.ts CHANGED
@@ -1,36 +1,26 @@
1
1
  /**
2
2
  * Astro middleware registered by tidyPlatform(): the framing policy from
3
- * TIDY_FRAME_ANCESTORS, and no indexing of the staging site. This worker is
4
- * the staging site (the live site is a static copy served by its own small
5
- * worker), so every answer here carries noindex, robots.txt disallows
6
- * everything and the sitemap is gone. The publish crawl identifies itself
7
- * and gets the real robots.txt and sitemap, which is what the live copy
8
- * ships.
3
+ * TIDY_FRAME_ANCESTORS, and noindex on every page this worker serves. This
4
+ * worker is the staging site (the live site is a static copy served by its
5
+ * own small worker); the control plane in front of it answers robots.txt
6
+ * and the sitemap for staging hostnames. The publish crawl identifies
7
+ * itself and is left alone, so the live copy carries no noindex.
9
8
  */
10
9
  import type { MiddlewareHandler } from "astro";
11
10
  import { workerEnv } from "./lib/env";
12
11
  import { applyFramePolicy, frameAncestors } from "./lib/frame";
13
12
 
14
13
  const PUBLISH_CRAWL = "tidysites-publish";
15
- const NOINDEX = "noindex, nofollow";
16
14
 
17
15
  export const onRequest: MiddlewareHandler = async (context, next) => {
18
16
  const crawl = context.request.headers.get("user-agent") === PUBLISH_CRAWL;
19
- if (!crawl) {
20
- const path = context.url.pathname;
21
- if (path === "/robots.txt") {
22
- return new Response("User-agent: *\nDisallow: /\n", { headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store", "x-robots-tag": NOINDEX } });
23
- }
24
- if (path === "/sitemap.xml" || path === "/sitemap-index.xml" || /^\/sitemap-\d+\.xml$/.test(path)) {
25
- return new Response("Not found", { status: 404, headers: { "cache-control": "no-store", "x-robots-tag": NOINDEX } });
26
- }
27
- }
28
17
  const response = await next();
29
18
  const env = await workerEnv();
30
19
  const ancestors = frameAncestors(env?.TIDY_FRAME_ANCESTORS);
20
+ if (ancestors.length === 0 && crawl) return response;
31
21
  const apply = (headers: Headers) => {
32
22
  if (ancestors.length > 0) applyFramePolicy(headers, ancestors);
33
- if (!crawl) headers.set("x-robots-tag", NOINDEX);
23
+ if (!crawl) headers.set("x-robots-tag", "noindex, nofollow");
34
24
  };
35
25
  // Response headers can be immutable (static assets, redirects); rebuild when so.
36
26
  try {
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.10.12";
2
+ export const VERSION = "0.10.14";
3
3
  export const PLUGIN_ID = "tidy-platform";