@lettercamp/cli 0.1.13 → 0.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lettercamp/cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "The repository for the Lettercamp CLI",
5
5
  "keywords": [
6
6
  "lettercamp",
@@ -21,8 +21,8 @@ import { googleAnalyticsScript } from '../lib/analytics';
21
21
  import Header from '../components/HeaderSlot.astro';
22
22
  import Footer from '../components/FooterSlot.astro';
23
23
 
24
- const { site, page, article = false } = Astro.props;
25
- const meta = pageMetadata(site, page, Astro.url, article, basePath);
24
+ const { site, page, article = false, posts = [] } = Astro.props;
25
+ const meta = pageMetadata(site, page, Astro.url, article, basePath, posts);
26
26
  const typography = site.design?.typography ?? {};
27
27
  const fonts = fontResources(typography, article ? 700 : 500);
28
28
  const fontSize = (value, fallback) => typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
@@ -1,10 +1,26 @@
1
1
  // Generated from lettercamp/lib/page-metadata.ts.
2
- export function pageMetadata(site, page, url, article = false, basePath = "") {
2
+ const text = (value) => typeof value === "string" ? value.trim() : "";
3
+ function siteCover(site, posts) {
4
+ const timestamp = (value) => {
5
+ if (value && typeof value === "object" && "timestamp" in value && typeof value.timestamp === "number")
6
+ return value.timestamp;
7
+ const time = value ? new Date(String(value)).valueOf() : NaN;
8
+ return Number.isNaN(time) ? -Infinity : time;
9
+ };
10
+ const post = posts.filter(post => post.draft !== true && (text(post.image) || text(post.ogImage)))
11
+ .sort((a, b) => timestamp(b.date) - timestamp(a.date) || (a.href ?? "").localeCompare(b.href ?? ""))[0];
12
+ return post
13
+ ? { image: text(post.image) || text(post.ogImage), alt: (text(post.image) ? text(post.imageAlt) : text(post.ogImageAlt)) || text(post.title) }
14
+ : { image: text(site.logo), alt: text(site.name) };
15
+ }
16
+ export function pageMetadata(site, page, url, article = false, basePath = "", posts = []) {
3
17
  const seo = site.seo && typeof site.seo === "object" ? site.seo : {};
4
- const text = (value) => typeof value === "string" ? value.trim() : "";
18
+ const homepage = !article && new URL(url).pathname.replace(/\/+$/, "") === basePath.replace(/\/+$/, "");
19
+ // Homepage overrides belong to the blog root, not posts or category pages.
20
+ if (homepage)
21
+ page = { ...seo, ...page };
5
22
  const title = text(page.title) || text(site.name);
6
23
  const pageTitle = title === site.name ? title : `${title} — ${text(site.name)}`;
7
- const homepage = !article && new URL(url).pathname.replace(/\/+$/, "") === basePath.replace(/\/+$/, "");
8
24
  const documentTitle = text(page.metaTitle) || (homepage && text(seo.metaTitle)) || pageTitle;
9
25
  const description = text(page.metaDescription) || text(page.description) || text(site.description);
10
26
  const socialTitle = text(page.ogTitle) || (homepage && (text(seo.ogTitle) || text(seo.metaTitle))) || title;
@@ -21,8 +37,10 @@ export function pageMetadata(site, page, url, article = false, basePath = "") {
21
37
  return "";
22
38
  }
23
39
  };
24
- const imageUrl = resolveUrl(page.ogImage || page.image || seo.ogImage);
25
- const imageAlt = text(page.ogImageAlt) || (page.ogImage || page.image ? text(page.imageAlt) : text(seo.ogImageAlt));
40
+ const explicitImage = text(page.ogImage) || text(page.image) || text(seo.ogImage);
41
+ const fallback = homepage && !explicitImage ? siteCover(site, posts) : undefined;
42
+ const imageUrl = resolveUrl(explicitImage || fallback?.image);
43
+ const imageAlt = text(page.ogImageAlt) || (fallback ? fallback.alt : page.ogImage || page.image ? text(page.imageAlt) : text(seo.ogImageAlt));
26
44
  const twitterImageUrl = resolveUrl(page.twitterImage) || imageUrl;
27
45
  const language = text(page.language) || text(site.language) || "en";
28
46
  let locale = "";
@@ -2,9 +2,10 @@
2
2
  import SiteLayout from '../layouts/SiteLayout.astro';
3
3
  import BlogHome from '../components/BlogHome.astro';
4
4
  import site from '../site.js';
5
+ import { getPosts } from '../lib/posts.js';
5
6
  const title = site.home.title ?? `${site.name} Blog`;
6
7
  ---
7
8
 
8
- <SiteLayout site={site} page={{ title }}>
9
+ <SiteLayout site={site} page={{ title }} posts={getPosts(site.language, site.timeZone)}>
9
10
  <BlogHome site={site} />
10
11
  </SiteLayout>
@@ -1,4 +1,5 @@
1
1
  .post-search-trigger { position: relative; flex: 0 1 240px; min-width: 180px; margin-left: auto; }
2
+ .blog-toolbar:not(:has(> .category-selector:not([hidden]))) > .post-search-trigger { margin-left: 0; }
2
3
  .post-search-trigger svg { position: absolute; left: 13px; top: 50%; width: 16px; height: 16px; transform: translateY(-50%); color: var(--site-text-muted); pointer-events: none; }
3
4
  .post-search-trigger button { width: 100%; height: 36px; padding: 0 14px 0 38px; border: 0; border-radius: 999px; background: var(--site-surface-hover); color: var(--site-text-secondary); font: 400 var(--text-label)/1.5 var(--font-family); text-align: left; cursor: pointer; }
4
5
  .post-search-trigger button:hover { background: var(--site-surface-active); color: var(--site-foreground); }
@@ -73,7 +73,7 @@ html:has(.site-header[data-header-position='fixed']) { scroll-padding-top: 80px;
73
73
  .mobile-menu-toggle svg { transition: none; }
74
74
  }
75
75
  .blog-home { padding-block: 72px 100px; }
76
- .blog-heading { margin-bottom: 18px; }
76
+ .blog-heading { margin-bottom: 40px; }
77
77
  .blog-home:not(:has(.blog-toolbar > :not([hidden], script, dialog))) > .blog-heading { margin-bottom: 64px; }
78
78
  .blog-heading h1 { margin-bottom: 0; }
79
79
  .article-category { margin: 0 0 20px; }
@@ -272,7 +272,7 @@ h1 { font-size: var(--text-title); font-weight: 500; line-height: 1.15; letter-s
272
272
  @media (prefers-reduced-motion: no-preference) { .mobile-navigation[open] .mobile-menu-panel { animation: mobile-menu-enter 180ms ease-out; } }
273
273
  .blog-home { padding-block: 46px 64px; }
274
274
  .blog-home:not(:has(.blog-toolbar > :not([hidden], script, dialog))) > .blog-heading { margin-bottom: 40px; }
275
- .blog-toolbar { margin-bottom: 36px; }
275
+ .blog-heading, .blog-toolbar { margin-bottom: 36px; }
276
276
  .category-selector { gap: 4px 20px; }
277
277
  .post-list { grid-template-columns: 1fr; gap: 48px; }
278
278
  .related-posts { margin-top: 56px; padding-top: 32px; }