@tideworthy/tidysites-platform 0.11.0 → 0.11.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,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.1
4
+
5
+ - `mediaUrl()` in `sections/lib`: a plain URL for an image field from the stored value (its storage key), for avatars and logos; the defaults and the business logo use it. Before this, a stored image with no `src` rendered as an empty circle.
6
+ - The photo slot (`sections/photo`) styles itself anywhere on a page, not only inside package sections, so a theme's own section can place it without a band of empty space.
7
+
3
8
  ## 0.11.0
4
9
 
5
10
  The package carries the template.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tideworthy/tidysites-platform",
3
- "version": "0.11.0",
3
+ "version": "0.11.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",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /** Associations, certifications, awards and seals: from the badge records or written into the section. */
3
- import { entries, list, nodeOf, pic, str } from "../lib";
3
+ import { entries, list, nodeOf, pic, str, mediaUrl } from "../lib";
4
4
  import Head from "./_Head.astro";
5
5
  const n = nodeOf(Astro.props);
6
6
  const fromRecords = str(n.source, "badges from the badges records").startsWith("badges from");
@@ -12,6 +12,6 @@ const items = [...recs, ...inline].filter((b) => b.title);
12
12
  {items.length > 0 && (
13
13
  <section class="ts ts-tight"><div class="ts-wrap">
14
14
  <Head n={n} />
15
- <div class="ts-logos">{items.map((b) => { const Tag = b.url ? "a" : "div"; return <Tag class="ts-logo" href={b.url || undefined} {...(b.id ? { "data-tidy-record": "badges", "data-tidy-record-id": b.id, "data-tidy-record-label": "Badge" } : {})}>{(b.image.image?.src || b.image.url) && <img src={b.image.image?.src ?? b.image.url ?? ""} alt="" />}<span>{b.title}{b.issuer && <span class="ts-muted" style="font-weight:400"> · {b.issuer}</span>}</span></Tag>; })}</div>
15
+ <div class="ts-logos">{items.map((b) => { const Tag = b.url ? "a" : "div"; return <Tag class="ts-logo" href={b.url || undefined} {...(b.id ? { "data-tidy-record": "badges", "data-tidy-record-id": b.id, "data-tidy-record-label": "Badge" } : {})}>{mediaUrl(b.image) && <img src={mediaUrl(b.image) ?? ""} alt="" />}<span>{b.title}{b.issuer && <span class="ts-muted" style="font-weight:400"> · {b.issuer}</span>}</span></Tag>; })}</div>
16
16
  </div></section>
17
17
  )}
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { entries, nodeOf, pic, str } from "../lib";
2
+ import { entries, nodeOf, pic, str, mediaUrl } from "../lib";
3
3
  import Head from "./_Head.astro";
4
4
  const n = nodeOf(Astro.props);
5
5
  let items = await entries("brands", { limit: 40 });
@@ -9,6 +9,6 @@ const names = str(n.variant) === "names";
9
9
  {items.length > 0 && (
10
10
  <section class="ts ts-tight"><div class="ts-wrap">
11
11
  <Head n={n} />
12
- {names ? <p class="ts-lede" style="margin:0">{items.map((b) => str(b.data.title)).join(", ")}</p> : <div class="ts-logos">{items.map((b) => { const p = pic(b.data.logo); const Tag = str(b.data.url) ? "a" : "div"; return <Tag class="ts-logo" href={str(b.data.url) || undefined} data-tidy-record="brands" data-tidy-record-id={b.id} data-tidy-record-label="Brand">{(p.image?.src || p.url) && <img src={p.image?.src ?? p.url ?? ""} alt="" />}<span>{str(b.data.title)}</span></Tag>; })}</div>}
12
+ {names ? <p class="ts-lede" style="margin:0">{items.map((b) => str(b.data.title)).join(", ")}</p> : <div class="ts-logos">{items.map((b) => { const p = pic(b.data.logo); const Tag = str(b.data.url) ? "a" : "div"; return <Tag class="ts-logo" href={str(b.data.url) || undefined} data-tidy-record="brands" data-tidy-record-id={b.id} data-tidy-record-label="Brand">{mediaUrl(p) && <img src={mediaUrl(p) ?? ""} alt="" />}<span>{str(b.data.title)}</span></Tag>; })}</div>}
13
13
  </div></section>
14
14
  )}
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { bool, entries, int, nodeOf, pic, str } from "../lib";
2
+ import { bool, entries, int, nodeOf, pic, str, mediaUrl } from "../lib";
3
3
  import Head from "./_Head.astro";
4
4
  const n = nodeOf(Astro.props);
5
5
  let items = await entries("clients", { limit: int(n.limit, 24) });
@@ -9,6 +9,6 @@ const industries = str(n.variant) === "with industries";
9
9
  {items.length > 0 && (
10
10
  <section class="ts ts-tight"><div class="ts-wrap">
11
11
  <Head n={n} />
12
- <div class="ts-logos" style={str(n.variant) === "logo strip" ? "justify-content:space-between" : ""}>{items.map((c) => { const p = pic(c.data.logo); const Tag = str(c.data.url) ? "a" : "div"; return <Tag class="ts-logo" href={str(c.data.url) || undefined} data-tidy-record="clients" data-tidy-record-id={c.id} data-tidy-record-label="Client">{(p.image?.src || p.url) ? <img src={p.image?.src ?? p.url ?? ""} alt={str(c.data.title)} /> : <span>{str(c.data.title)}</span>}{industries && str(c.data.industry) && <span class="ts-muted" style="font-weight:400">{str(c.data.industry)}</span>}</Tag>; })}</div>
12
+ <div class="ts-logos" style={str(n.variant) === "logo strip" ? "justify-content:space-between" : ""}>{items.map((c) => { const p = pic(c.data.logo); const Tag = str(c.data.url) ? "a" : "div"; return <Tag class="ts-logo" href={str(c.data.url) || undefined} data-tidy-record="clients" data-tidy-record-id={c.id} data-tidy-record-label="Client">{(p.image?.src || p.url) ? <img src={mediaUrl(p) ?? ""} alt={str(c.data.title)} /> : <span>{str(c.data.title)}</span>}{industries && str(c.data.industry) && <span class="ts-muted" style="font-weight:400">{str(c.data.industry)}</span>}</Tag>; })}</div>
13
13
  </div></section>
14
14
  )}
@@ -1,5 +1,5 @@
1
1
  ---
2
- import { bool, entries, getBusiness, lines, nodeOf, pic, str } from "../lib";
2
+ import { bool, entries, getBusiness, lines, nodeOf, pic, str, mediaUrl } from "../lib";
3
3
  import Head from "./_Head.astro";
4
4
  const n = nodeOf(Astro.props);
5
5
  const b = await getBusiness(Astro.request);
@@ -10,7 +10,7 @@ const methods = str(n.payment_methods).split(",").map((s) => s.trim()).filter(Bo
10
10
  <section class="ts ts-band" id="financing"><div class="ts-wrap">
11
11
  <Head n={n} action={bool(n.show_plans) ? { href: "/plans", label: "Membership plans" } : null} />
12
12
  <div class="ts-grid" style="--cols:3">
13
- {providers.map((p) => { const lg = pic(p.data.logo); return <div class="ts-card" data-tidy-record="financing_providers" data-tidy-record-id={p.id} data-tidy-record-label="Financing provider">{(lg.image?.src || lg.url) && <img src={lg.image?.src ?? lg.url ?? ""} alt="" style="max-height:28px;width:auto" />}<h3>{str(p.data.title)}</h3>{str(p.data.rate_line) && <p>{str(p.data.rate_line)}</p>}{str(p.data.note) && <p class="ts-muted" style="font-size:13px">{str(p.data.note)}</p>}{str(p.data.apply_url) && <a href={str(p.data.apply_url)} style="font-weight:700;color:var(--ts-primary);font-size:14px">Check your options</a>}</div>; })}
13
+ {providers.map((p) => { const lg = pic(p.data.logo); return <div class="ts-card" data-tidy-record="financing_providers" data-tidy-record-id={p.id} data-tidy-record-label="Financing provider">{mediaUrl(lg) && <img src={mediaUrl(lg) ?? ""} alt="" style="max-height:28px;width:auto" />}<h3>{str(p.data.title)}</h3>{str(p.data.rate_line) && <p>{str(p.data.rate_line)}</p>}{str(p.data.note) && <p class="ts-muted" style="font-size:13px">{str(p.data.note)}</p>}{str(p.data.apply_url) && <a href={str(p.data.apply_url)} style="font-weight:700;color:var(--ts-primary);font-size:14px">Check your options</a>}</div>; })}
14
14
  {(methods.length > 0 || (bool(n.show_hsa, true) && b.hsaFsaLine)) && <div class="ts-card"><h3>Paying directly</h3>{methods.length > 0 && <p>{methods.join(", ")}</p>}{bool(n.show_hsa, true) && b.hsaFsaLine && <p class="ts-muted" style="font-size:13px">{b.hsaFsaLine}</p>}</div>}
15
15
  </div>
16
16
  </div></section>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /** Insurers accepted, as logos, names or one line, with network status and government programs. */
3
- import { bool, entries, getBusiness, nodeOf, pic, str } from "../lib";
3
+ import { bool, entries, getBusiness, nodeOf, pic, str, mediaUrl } from "../lib";
4
4
  import Head from "./_Head.astro";
5
5
  const n = nodeOf(Astro.props);
6
6
  const b = await getBusiness(Astro.request);
@@ -14,7 +14,7 @@ const groups = bool(n.show_network) && outNet.length ? [{ title: "In network", l
14
14
  <Head n={n} action={str(n.button_label) ? { href: str(n.button_url, "/contact"), label: str(n.button_label) } : null} />
15
15
  {variant === "one line" || !items.length ? <p class="ts-lede" style="margin:0">{b.insuranceLine}</p> : groups.map((g) => (
16
16
  <div>{g.title && <p class="ts-group">{g.title}</p>}
17
- {variant === "logos" ? <div class="ts-logos">{g.list.map((i) => { const p = pic(i.data.logo); return <div class="ts-logo" data-tidy-record="insurers" data-tidy-record-id={i.id} data-tidy-record-label="Insurer">{(p.image?.src || p.url) && <img src={p.image?.src ?? p.url ?? ""} alt="" />}<span>{str(i.data.title)}</span></div>; })}</div> : <div class="ts-chips">{g.list.map((i) => <span class="ts-chip" data-tidy-record="insurers" data-tidy-record-id={i.id} data-tidy-record-label="Insurer">{str(i.data.title)}</span>)}</div>}
17
+ {variant === "logos" ? <div class="ts-logos">{g.list.map((i) => { const p = pic(i.data.logo); return <div class="ts-logo" data-tidy-record="insurers" data-tidy-record-id={i.id} data-tidy-record-label="Insurer">{mediaUrl(p) && <img src={mediaUrl(p) ?? ""} alt="" />}<span>{str(i.data.title)}</span></div>; })}</div> : <div class="ts-chips">{g.list.map((i) => <span class="ts-chip" data-tidy-record="insurers" data-tidy-record-id={i.id} data-tidy-record-label="Insurer">{str(i.data.title)}</span>)}</div>}
18
18
  </div>
19
19
  ))}
20
20
  {bool(n.show_programs, true) && b.governmentPrograms && <p class="ts-muted" style="margin-top:14px;font-size:14px">{b.governmentPrograms}</p>}
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  /** Reviews from the records, with the business's rating and count beside them. */
3
- import { bool, entries, getBusiness, int, nodeOf, pic, str } from "../lib";
3
+ import { bool, entries, getBusiness, int, nodeOf, pic, str, mediaUrl } from "../lib";
4
4
  import Head from "./_Head.astro";
5
5
  const n = nodeOf(Astro.props);
6
6
  const b = await getBusiness(Astro.request);
@@ -34,7 +34,7 @@ const when = (r: { data: Record<string, unknown> }) => { const d = str(r.data.da
34
34
  <article class="ts-card ts-review" data-tidy-record="reviews" data-tidy-record-id={r.id} data-tidy-record-label="Review">
35
35
  <span class="ts-stars" aria-label={`${stars(r.data.rating).length} stars`}>{stars(r.data.rating)}</span>
36
36
  <blockquote>{str(r.data.quote)}</blockquote>
37
- <div class="ts-who">{(ph.image || ph.url) && <span class="ts-avatar"><img src={ph.image?.src ?? ph.url ?? ""} alt="" /></span>}<div><b>{who(r)}</b><span>{[bool(n.show_source, true) ? str(r.data.source) : "", when(r)].filter(Boolean).join(" · ")}</span></div></div>
37
+ <div class="ts-who">{(ph.image || ph.url) && <span class="ts-avatar"><img src={mediaUrl(ph) ?? ""} alt="" /></span>}<div><b>{who(r)}</b><span>{[bool(n.show_source, true) ? str(r.data.source) : "", when(r)].filter(Boolean).join(" · ")}</span></div></div>
38
38
  </article>
39
39
  ); })}
40
40
  </div>
@@ -45,10 +45,10 @@
45
45
  .ts .ts-list{list-style:none;margin:0;padding:0;display:grid;gap:10px}
46
46
  .ts .ts-list li{display:flex;gap:10px;color:var(--ts-ink-2)}
47
47
  .ts .ts-list li::before{content:"✓";color:var(--ts-primary);font-weight:700;flex:none}
48
- .ts .ts-photo{position:relative;overflow:hidden;border-radius:var(--ts-radius);background:color-mix(in srgb,var(--ts-primary) 12%,var(--ts-surface));display:grid;place-items:center;color:var(--ts-ink-3);font-size:13px;text-align:center;padding:12px}
49
- .ts .ts-photo img{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}
50
- .ts .ts-photo-wide{aspect-ratio:16/10}.ts .ts-photo-tall{aspect-ratio:4/5}.ts .ts-photo-square{aspect-ratio:1}.ts .ts-photo-video{aspect-ratio:16/9}
51
- .ts .ts-photo-has{padding:0}
48
+ .ts-photo{--ts-primary:var(--primary,var(--brand-primary,#1e5a46));--ts-surface:var(--surface,#fff);--ts-ink-3:var(--ink-3,#7b877f);--ts-radius:var(--brand-radius,10px);position:relative;overflow:hidden;border-radius:var(--ts-radius);background:color-mix(in srgb,var(--ts-primary) 12%,var(--ts-surface));display:grid;place-items:center;color:var(--ts-ink-3);font-size:13px;text-align:center;padding:12px}
49
+ .ts-photo img{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}
50
+ .ts-photo-wide{aspect-ratio:16/10}.ts-photo-tall{aspect-ratio:4/5}.ts-photo-square{aspect-ratio:1}.ts-photo-video{aspect-ratio:16/9}
51
+ .ts-photo-has{padding:0}
52
52
  .ts .ts-stars{color:var(--ts-accent);letter-spacing:1px;font-size:15px}
53
53
  .ts .ts-avatar{width:40px;height:40px;border-radius:50%;overflow:hidden;background:color-mix(in srgb,var(--ts-primary) 15%,var(--ts-surface));flex:none;display:block}
54
54
  .ts .ts-avatar img{width:100%;height:100%;object-fit:cover}
@@ -28,6 +28,15 @@ export function pic(v: unknown): Pic {
28
28
  return { image: null, url: null };
29
29
  }
30
30
  export const hasPic = (v: unknown): boolean => { const p = pic(v); return Boolean(p.image || p.url); };
31
+ /** A plain URL for an image field, for <img> tags: the stored file (by its storage key), a `src` when one is set, or a plain URL left from an older site. */
32
+ export function mediaUrl(v: unknown): string | null {
33
+ const p = v && typeof v === "object" && "image" in (v as object) && "url" in (v as object) ? (v as Pic) : pic(v);
34
+ if (p.url) return p.url;
35
+ const img = p.image as ({ src?: string; meta?: { storageKey?: string } } | null);
36
+ if (img?.src) return img.src;
37
+ if (img?.meta?.storageKey) return `/_emdash/api/media/file/${img.meta.storageKey}`;
38
+ return null;
39
+ }
31
40
 
32
41
  /** "Label: value" lines into pairs. */
33
42
  export function facts(v: unknown): Array<[string, string]> {
@@ -82,11 +91,9 @@ export interface Business {
82
91
  }
83
92
  const NETWORKS = ["facebook", "instagram", "google", "youtube", "linkedin", "tiktok", "yelp"];
84
93
  function mediaSrc(v: unknown): Business["logo"] {
85
- const p = pic(v);
86
- if (p.image?.src) return { src: p.image.src, alt: p.image.alt, width: p.image.width, height: p.image.height };
87
- if (p.image && (p.image as { meta?: { storageKey?: string } }).meta?.storageKey) return { src: `/_emdash/api/media/file/${(p.image as { meta: { storageKey: string } }).meta.storageKey}`, alt: p.image.alt };
88
- if (p.url) return { src: p.url };
89
- return null;
94
+ const src = mediaUrl(v); if (!src) return null;
95
+ const img = pic(v).image;
96
+ return { src, alt: img?.alt, width: img?.width, height: img?.height };
90
97
  }
91
98
  async function readBusiness(): Promise<Business> {
92
99
  let d: Record<string, unknown> = {}; let id: string | null = null; let siteTitle = "";
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.11.0";
2
+ export const VERSION = "0.11.1";
3
3
  export const PLUGIN_ID = "tidy-platform";