@the-smithy/hearth 0.14.0 → 0.16.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/dist/{chunk-4VLAG4JX.js → chunk-G3UBVAQY.js} +1 -0
- package/dist/{chunk-ZK7X4FDQ.js → chunk-VLFYTREX.js} +18 -0
- package/dist/{client-DCiaDdvu.d.ts → client-CnIniG4N.d.ts} +11 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -1
- package/dist/next.d.ts +39 -4
- package/dist/next.js +76 -5
- package/dist/react.d.ts +14 -1
- package/dist/react.js +17 -6
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +1 -0
- package/dist/webhooks.js +1 -1
- package/package.json +2 -2
|
@@ -29,6 +29,7 @@ function tagsForWebhook(payload, base = "hearth") {
|
|
|
29
29
|
if (payload.kind === "event" || payload.kind === "scheduled") tags.push(`${base}:events`);
|
|
30
30
|
if (payload.kind === "settings") tags.push(`${base}:site`);
|
|
31
31
|
if (payload.kind === "giving") tags.push(`${base}:giving`);
|
|
32
|
+
if (payload.kind === "redirects") tags.push(`${base}:redirects`);
|
|
32
33
|
return tags;
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -34,6 +34,19 @@ async function errorFromResponse(res) {
|
|
|
34
34
|
// src/client.ts
|
|
35
35
|
var DEFAULT_BASE_URL = "https://hearth.thesmithy.io";
|
|
36
36
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
37
|
+
function findRedirect(redirects, path) {
|
|
38
|
+
let p = Array.isArray(path) ? `/${path.join("/")}` : path ?? "/";
|
|
39
|
+
p = p.split(/[?#]/)[0];
|
|
40
|
+
try {
|
|
41
|
+
p = decodeURI(p);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
if (!p.startsWith("/")) p = `/${p}`;
|
|
45
|
+
p = p.replace(/\/{2,}/g, "/");
|
|
46
|
+
if (p.length > 1) p = p.replace(/\/+$/, "");
|
|
47
|
+
p = p.toLowerCase();
|
|
48
|
+
return redirects.find((r) => r.from === p) ?? null;
|
|
49
|
+
}
|
|
37
50
|
function normalizeSlug(slug) {
|
|
38
51
|
const joined = Array.isArray(slug) ? slug.join("/") : slug ?? "";
|
|
39
52
|
const clean = joined.split("/").map((s) => s.trim()).filter(Boolean).join("/").toLowerCase();
|
|
@@ -204,6 +217,10 @@ function createCore(options) {
|
|
|
204
217
|
}
|
|
205
218
|
},
|
|
206
219
|
sitemap: (o = {}) => request("/sitemap", { ...o, resource: "sitemap" }),
|
|
220
|
+
redirects: {
|
|
221
|
+
/** Every redirect on the site (old addresses and where they go now). Empty from Hearth versions without them. */
|
|
222
|
+
list: async (o = {}) => await maybe("/redirects", { ...o, resource: "redirects.list" }) ?? []
|
|
223
|
+
},
|
|
207
224
|
fundraisers: {
|
|
208
225
|
/** Fundraisers with progress (sites with Giving). `category` is "group/category". */
|
|
209
226
|
list: (opts = {}, o = {}) => {
|
|
@@ -279,6 +296,7 @@ export {
|
|
|
279
296
|
HearthError,
|
|
280
297
|
isHearthError,
|
|
281
298
|
DEFAULT_BASE_URL,
|
|
299
|
+
findRedirect,
|
|
282
300
|
normalizeSlug,
|
|
283
301
|
createCore
|
|
284
302
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Collection, CollectionEntry, HearthEvent, Site, PageSummary, Page, HearthEventDetail, RegistrationInput, RegistrationResult, CategoryGroup, SitemapEntry, Fundraiser, FundraiserDetail, DonationInput, DonationCheckout, HearthForm } from '@the-smithy/hearth-schema';
|
|
1
|
+
import { Collection, CollectionEntry, HearthEvent, Site, PageSummary, Page, HearthEventDetail, RegistrationInput, RegistrationResult, CategoryGroup, SitemapEntry, Redirect, Fundraiser, FundraiserDetail, DonationInput, DonationCheckout, HearthForm } from '@the-smithy/hearth-schema';
|
|
2
2
|
|
|
3
3
|
type HearthErrorCode = "unauthorized" | "not_found" | "bad_request" | "feature_disabled" | "rate_limited" | "server_error" | "network" | "timeout" | "invalid_response";
|
|
4
4
|
/** Any failure talking to Hearth. Messages never contain the API key. */
|
|
@@ -101,6 +101,11 @@ type RequestOptions = CallOptions & {
|
|
|
101
101
|
resource?: string;
|
|
102
102
|
};
|
|
103
103
|
/** "home", "about/team", ["about", "team"], "/about/" → "about/team"-style slug; empty means "home". */
|
|
104
|
+
/**
|
|
105
|
+
* The redirect for a requested path, if any: matched like Hearth stores them (lowercase, no trailing slash, no query).
|
|
106
|
+
* `path` is a URL path ("/About/") or a catch-all slug (["about"]).
|
|
107
|
+
*/
|
|
108
|
+
declare function findRedirect(redirects: Redirect[], path: string | string[] | undefined | null): Redirect | null;
|
|
104
109
|
declare function normalizeSlug(slug: string | string[] | undefined | null): string;
|
|
105
110
|
declare function createCore(options: HearthClientOptions): {
|
|
106
111
|
/** The site's address in Hearth. */
|
|
@@ -158,6 +163,10 @@ declare function createCore(options: HearthClientOptions): {
|
|
|
158
163
|
}, o?: CallOptions) => Promise<CollectionPage | EventCollectionPage | null>;
|
|
159
164
|
};
|
|
160
165
|
sitemap: (o?: CallOptions) => Promise<SitemapEntry[]>;
|
|
166
|
+
redirects: {
|
|
167
|
+
/** Every redirect on the site (old addresses and where they go now). Empty from Hearth versions without them. */
|
|
168
|
+
list: (o?: CallOptions) => Promise<Redirect[]>;
|
|
169
|
+
};
|
|
161
170
|
fundraisers: {
|
|
162
171
|
/** Fundraisers with progress (sites with Giving). `category` is "group/category". */
|
|
163
172
|
list: (opts?: {
|
|
@@ -335,4 +344,4 @@ declare function createCore(options: HearthClientOptions): {
|
|
|
335
344
|
};
|
|
336
345
|
type HearthCore = ReturnType<typeof createCore>;
|
|
337
346
|
|
|
338
|
-
export { type CallOptions as C, DEFAULT_BASE_URL as D, type EventCollectionPage as E, type FormResult as F, type HearthCore as H, type NextFetchOptions as N, type PreviewSession as P, type RequestContext as R, type HearthClientOptions as a, type CollectionPage as b, type FormValues as c, HearthError as d, type HearthErrorCode as e, type HearthHooks as f, type RequestOptions as g, isHearthError as i, normalizeSlug as n };
|
|
347
|
+
export { type CallOptions as C, DEFAULT_BASE_URL as D, type EventCollectionPage as E, type FormResult as F, type HearthCore as H, type NextFetchOptions as N, type PreviewSession as P, type RequestContext as R, type HearthClientOptions as a, type CollectionPage as b, type FormValues as c, HearthError as d, type HearthErrorCode as e, type HearthHooks as f, type RequestOptions as g, findRedirect as h, isHearthError as i, normalizeSlug as n };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { H as HearthCore, a as HearthClientOptions } from './client-
|
|
2
|
-
export { C as CallOptions, b as CollectionPage, D as DEFAULT_BASE_URL, E as EventCollectionPage, F as FormResult, c as FormValues, d as HearthError, e as HearthErrorCode, f as HearthHooks, N as NextFetchOptions, P as PreviewSession, R as RequestContext, g as RequestOptions, i as isHearthError, n as normalizeSlug } from './client-
|
|
3
|
-
export { Block, BlockOf, CategoryGroup, CategoryRef, Collection, CollectionEntry, CollectionRef, DonationCheckout, DonationInput, EntryDetails, EventCustomFields, EventRegistration, EventTicket, Fundraiser, FundraiserDetail, HearthEvent, HearthEventDetail, KnownBlock, Page, PageMeta, PageSummary, RegistrationInput, RegistrationResult, ResolvedBlock, Site, SiteSettings, SitemapEntry, UnknownBlock, WebhookPayload, videoEmbedUrl } from '@the-smithy/hearth-schema';
|
|
1
|
+
import { H as HearthCore, a as HearthClientOptions } from './client-CnIniG4N.js';
|
|
2
|
+
export { C as CallOptions, b as CollectionPage, D as DEFAULT_BASE_URL, E as EventCollectionPage, F as FormResult, c as FormValues, d as HearthError, e as HearthErrorCode, f as HearthHooks, N as NextFetchOptions, P as PreviewSession, R as RequestContext, g as RequestOptions, h as findRedirect, i as isHearthError, n as normalizeSlug } from './client-CnIniG4N.js';
|
|
3
|
+
export { Block, BlockOf, CategoryGroup, CategoryRef, Collection, CollectionEntry, CollectionRef, DonationCheckout, DonationInput, EntryDetails, EventCustomFields, EventRegistration, EventTicket, Fundraiser, FundraiserDetail, HearthEvent, HearthEventDetail, KnownBlock, Page, PageMeta, PageSummary, Redirect, RegistrationInput, RegistrationResult, ResolvedBlock, Site, SiteSettings, SitemapEntry, UnknownBlock, WebhookPayload, videoEmbedUrl } from '@the-smithy/hearth-schema';
|
|
4
4
|
|
|
5
5
|
/** A plugin adds resources (and can use the core's `request`) under its own names. */
|
|
6
6
|
type HearthPlugin<T extends object = object> = {
|
package/dist/index.js
CHANGED
|
@@ -2,9 +2,10 @@ import {
|
|
|
2
2
|
DEFAULT_BASE_URL,
|
|
3
3
|
HearthError,
|
|
4
4
|
createCore,
|
|
5
|
+
findRedirect,
|
|
5
6
|
isHearthError,
|
|
6
7
|
normalizeSlug
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VLFYTREX.js";
|
|
8
9
|
|
|
9
10
|
// src/index.ts
|
|
10
11
|
import { videoEmbedUrl } from "@the-smithy/hearth-schema";
|
|
@@ -26,6 +27,7 @@ export {
|
|
|
26
27
|
HearthError,
|
|
27
28
|
createHearthClient,
|
|
28
29
|
definePlugin,
|
|
30
|
+
findRedirect,
|
|
29
31
|
isHearthError,
|
|
30
32
|
normalizeSlug,
|
|
31
33
|
videoEmbedUrl
|
package/dist/next.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import { H as HearthCore, b as CollectionPage, E as EventCollectionPage, F as FormResult, c as FormValues } from './client-
|
|
1
|
+
import { H as HearthCore, b as CollectionPage, E as EventCollectionPage, F as FormResult, c as FormValues } from './client-CnIniG4N.js';
|
|
2
2
|
import * as _the_smithy_hearth_schema from '@the-smithy/hearth-schema';
|
|
3
|
-
import { PageMeta } from '@the-smithy/hearth-schema';
|
|
3
|
+
import { Site, PageMeta } from '@the-smithy/hearth-schema';
|
|
4
4
|
import { Metadata, MetadataRoute } from 'next';
|
|
5
5
|
|
|
6
6
|
/** Holds the preview token while Next.js draft mode is on. */
|
|
7
7
|
declare const PREVIEW_COOKIE = "hearth-preview";
|
|
8
|
+
/**
|
|
9
|
+
* Whether this deploy is a preview or branch deploy (Netlify's CONTEXT, Vercel's VERCEL_ENV), which search engines
|
|
10
|
+
* shouldn't index: only the production deploy is the real site.
|
|
11
|
+
*/
|
|
12
|
+
declare const isNonProductionDeploy: () => boolean;
|
|
13
|
+
/** The site's https:// address from its domain in Hearth, or null before one is set. */
|
|
14
|
+
declare const siteBaseUrl: (site: Pick<Site, "domain">) => string | null;
|
|
15
|
+
/** The page's lang attribute from the site's language in Hearth ("en_US" → "en-US"). */
|
|
16
|
+
declare const siteLang: (site: Pick<Site, "settings">) => string;
|
|
8
17
|
/** Next.js metadata from a page's or event's `meta`. */
|
|
9
18
|
declare function metadataFromMeta(meta: PageMeta): Metadata;
|
|
10
19
|
type HearthNextOptions = {
|
|
@@ -21,7 +30,7 @@ type HearthNextOptions = {
|
|
|
21
30
|
* what changed), metadata, sitemap, the revalidation route, and form submission.
|
|
22
31
|
*/
|
|
23
32
|
declare function createHearthNext(client: HearthCore, { tag, revalidate }?: HearthNextOptions): {
|
|
24
|
-
getSite: () => Promise<
|
|
33
|
+
getSite: () => Promise<Site>;
|
|
25
34
|
getPages: () => Promise<_the_smithy_hearth_schema.PageSummary[]>;
|
|
26
35
|
getPage: (slug?: string | string[] | null) => Promise<_the_smithy_hearth_schema.Page | null>;
|
|
27
36
|
getEvents: (opts?: {
|
|
@@ -209,8 +218,34 @@ declare function createHearthNext(client: HearthCore, { tag, revalidate }?: Hear
|
|
|
209
218
|
exitPreviewHandler(): (request: Request) => Promise<never>;
|
|
210
219
|
/** For `generateMetadata`: the page's title, description, canonical, robots and share tags ({} when not found). */
|
|
211
220
|
generatePageMetadata(slug?: string | string[] | null): Promise<Metadata>;
|
|
221
|
+
/**
|
|
222
|
+
* The root layout's `generateMetadata`: what every page starts from. Full links (metadataBase), the title pattern and
|
|
223
|
+
* default description, the default share image, the site's language, Google and Bing verification, and keeping
|
|
224
|
+
* search engines away from preview deploys and a site hidden in Hearth. Pages from Hearth override what they set.
|
|
225
|
+
* export const generateMetadata = () => hearthNext.siteMetadata();
|
|
226
|
+
*/
|
|
227
|
+
siteMetadata(): Promise<Metadata>;
|
|
228
|
+
/**
|
|
229
|
+
* `export default` from app/robots.ts. Keeps every crawler out until the site has a domain in Hearth, while it's
|
|
230
|
+
* hidden from search engines there, and on preview deploys; otherwise allows all and points at the sitemap.
|
|
231
|
+
*/
|
|
232
|
+
robots(): Promise<MetadataRoute.Robots>;
|
|
233
|
+
/** Every redirect on the site (old addresses and where they go now), cached and refreshed on publish. */
|
|
234
|
+
getRedirects: () => Promise<_the_smithy_hearth_schema.Redirect[]>;
|
|
235
|
+
/**
|
|
236
|
+
* Instead of `notFound()` when a path has no page: follows the site's redirect for it (301, or 307 for a temporary
|
|
237
|
+
* one), else shows the 404. Old addresses keep working after a page is renamed or a site is migrated.
|
|
238
|
+
* if (!page) await hearthNext.redirectOrNotFound(slug);
|
|
239
|
+
*/
|
|
240
|
+
redirectOrNotFound(path: string | string[] | undefined | null): Promise<never>;
|
|
212
241
|
/** `export default sitemap` from app/sitemap.ts. */
|
|
213
242
|
sitemap(): Promise<MetadataRoute.Sitemap>;
|
|
243
|
+
/**
|
|
244
|
+
* Hearth's sitemap plus pages the site's own code serves (an events calendar, a donate page), skipping any
|
|
245
|
+
* Hearth already lists:
|
|
246
|
+
* export default hearthNext.sitemapWith(["/events", "/fundraisers"]);
|
|
247
|
+
*/
|
|
248
|
+
sitemapWith(paths: string[]): () => Promise<MetadataRoute.Sitemap>;
|
|
214
249
|
/**
|
|
215
250
|
* POST handler for the site's revalidation webhook (app/api/revalidate/route.ts):
|
|
216
251
|
* export const POST = hearthNext.revalidateHandler();
|
|
@@ -232,4 +267,4 @@ declare function createHearthNext(client: HearthCore, { tag, revalidate }?: Hear
|
|
|
232
267
|
submitFormAction: (formKey: string) => (_prev: FormResult | null, formData: FormData) => Promise<FormResult>;
|
|
233
268
|
};
|
|
234
269
|
|
|
235
|
-
export { type HearthNextOptions, PREVIEW_COOKIE, createHearthNext, metadataFromMeta };
|
|
270
|
+
export { type HearthNextOptions, PREVIEW_COOKIE, createHearthNext, isNonProductionDeploy, metadataFromMeta, siteBaseUrl, siteLang };
|
package/dist/next.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
findRedirect,
|
|
2
3
|
isHearthError,
|
|
3
4
|
normalizeSlug
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-VLFYTREX.js";
|
|
5
6
|
import {
|
|
6
7
|
parseWebhook,
|
|
7
8
|
tagsForWebhook
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-G3UBVAQY.js";
|
|
9
10
|
|
|
10
11
|
// src/next.ts
|
|
11
12
|
import { revalidateTag } from "next/cache";
|
|
12
13
|
import { cookies, draftMode, headers } from "next/headers";
|
|
13
|
-
import { redirect } from "next/navigation";
|
|
14
|
+
import { notFound, permanentRedirect, redirect } from "next/navigation";
|
|
14
15
|
var PREVIEW_COOKIE = "hearth-preview";
|
|
15
16
|
var DRAFT_COOKIE = "__prerender_bypass";
|
|
16
17
|
async function previewToken() {
|
|
@@ -19,6 +20,13 @@ async function previewToken() {
|
|
|
19
20
|
}
|
|
20
21
|
var clientIp = (h) => h.get("x-nf-client-connection-ip") ?? h.get("x-forwarded-for")?.split(",")[0]?.trim() ?? h.get("x-real-ip");
|
|
21
22
|
var safePath = (path) => path && path.startsWith("/") && !path.startsWith("//") ? path : "/";
|
|
23
|
+
var isNonProductionDeploy = () => {
|
|
24
|
+
const netlify = process.env.CONTEXT;
|
|
25
|
+
const vercel = process.env.VERCEL_ENV;
|
|
26
|
+
return Boolean(netlify && netlify !== "production" || vercel && vercel !== "production");
|
|
27
|
+
};
|
|
28
|
+
var siteBaseUrl = (site) => site.domain ? `https://${site.domain.replace(/^https?:\/\//, "").replace(/\/+$/, "")}` : null;
|
|
29
|
+
var siteLang = (site) => (site.settings.seo.locale ?? "en_US").replace("_", "-");
|
|
22
30
|
function metadataFromMeta(meta) {
|
|
23
31
|
const description = meta.description ?? void 0;
|
|
24
32
|
return {
|
|
@@ -181,13 +189,73 @@ function createHearthNext(client, { tag = "hearth", revalidate = 3600 } = {}) {
|
|
|
181
189
|
/** For `generateMetadata`: the page's title, description, canonical, robots and share tags ({} when not found). */
|
|
182
190
|
async generatePageMetadata(slug) {
|
|
183
191
|
const page = await getPage(slug);
|
|
184
|
-
|
|
192
|
+
if (!page) return {};
|
|
193
|
+
const metadata = metadataFromMeta(page.meta);
|
|
194
|
+
return await previewToken() ? { ...metadata, robots: { index: false, follow: false } } : metadata;
|
|
195
|
+
},
|
|
196
|
+
/**
|
|
197
|
+
* The root layout's `generateMetadata`: what every page starts from. Full links (metadataBase), the title pattern and
|
|
198
|
+
* default description, the default share image, the site's language, Google and Bing verification, and keeping
|
|
199
|
+
* search engines away from preview deploys and a site hidden in Hearth. Pages from Hearth override what they set.
|
|
200
|
+
* export const generateMetadata = () => hearthNext.siteMetadata();
|
|
201
|
+
*/
|
|
202
|
+
async siteMetadata() {
|
|
203
|
+
const site = await client.site.get(tags(`${tag}:site`));
|
|
204
|
+
const seo = site.settings.seo;
|
|
205
|
+
const base = siteBaseUrl(site);
|
|
206
|
+
const hidden = Boolean(seo.noIndex) || isNonProductionDeploy() || !base;
|
|
207
|
+
const image = seo.image?.src ? [{ url: seo.image.src, alt: seo.image.alt || site.name }] : void 0;
|
|
208
|
+
return {
|
|
209
|
+
...base ? { metadataBase: new URL(base) } : {},
|
|
210
|
+
title: { default: site.name, template: seo.titleTemplate?.includes("%s") ? seo.titleTemplate : `%s | ${site.name}` },
|
|
211
|
+
...seo.description ? { description: seo.description } : {},
|
|
212
|
+
...hidden ? { robots: { index: false, follow: false } } : {},
|
|
213
|
+
openGraph: { siteName: site.name, locale: seo.locale ?? "en_US", type: "website", ...seo.description ? { description: seo.description } : {}, ...image ? { images: image } : {} },
|
|
214
|
+
twitter: { card: "summary_large_image", ...seo.twitterHandle ? { site: seo.twitterHandle } : {}, ...image ? { images: image.map((i) => i.url) } : {} },
|
|
215
|
+
...seo.verification?.google || seo.verification?.bing ? { verification: { ...seo.verification.google ? { google: seo.verification.google } : {}, ...seo.verification.bing ? { other: { "msvalidate.01": seo.verification.bing } } : {} } } : {}
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
/**
|
|
219
|
+
* `export default` from app/robots.ts. Keeps every crawler out until the site has a domain in Hearth, while it's
|
|
220
|
+
* hidden from search engines there, and on preview deploys; otherwise allows all and points at the sitemap.
|
|
221
|
+
*/
|
|
222
|
+
async robots() {
|
|
223
|
+
const site = await client.site.get(tags(`${tag}:site`));
|
|
224
|
+
const base = siteBaseUrl(site);
|
|
225
|
+
if (!base || site.settings.seo.noIndex || isNonProductionDeploy()) return { rules: { userAgent: "*", disallow: "/" } };
|
|
226
|
+
return { rules: { userAgent: "*", allow: "/", disallow: "/api/" }, sitemap: `${base}/sitemap.xml` };
|
|
227
|
+
},
|
|
228
|
+
/** Every redirect on the site (old addresses and where they go now), cached and refreshed on publish. */
|
|
229
|
+
getRedirects: () => client.redirects.list(tags(`${tag}:redirects`)),
|
|
230
|
+
/**
|
|
231
|
+
* Instead of `notFound()` when a path has no page: follows the site's redirect for it (301, or 307 for a temporary
|
|
232
|
+
* one), else shows the 404. Old addresses keep working after a page is renamed or a site is migrated.
|
|
233
|
+
* if (!page) await hearthNext.redirectOrNotFound(slug);
|
|
234
|
+
*/
|
|
235
|
+
async redirectOrNotFound(path) {
|
|
236
|
+
const match = findRedirect(await client.redirects.list(tags(`${tag}:redirects`)), path);
|
|
237
|
+
if (match) (match.permanent ? permanentRedirect : redirect)(match.to);
|
|
238
|
+
notFound();
|
|
185
239
|
},
|
|
186
240
|
/** `export default sitemap` from app/sitemap.ts. */
|
|
187
241
|
async sitemap() {
|
|
188
242
|
const entries = await client.sitemap(tags(`${tag}:pages`, `${tag}:events`));
|
|
189
243
|
return entries.map((e) => ({ url: e.url, ...e.lastModified ? { lastModified: e.lastModified } : {}, priority: e.priority }));
|
|
190
244
|
},
|
|
245
|
+
/**
|
|
246
|
+
* Hearth's sitemap plus pages the site's own code serves (an events calendar, a donate page), skipping any
|
|
247
|
+
* Hearth already lists:
|
|
248
|
+
* export default hearthNext.sitemapWith(["/events", "/fundraisers"]);
|
|
249
|
+
*/
|
|
250
|
+
sitemapWith(paths) {
|
|
251
|
+
return async () => {
|
|
252
|
+
const [entries, site] = await Promise.all([client.sitemap(tags(`${tag}:pages`, `${tag}:events`)), client.site.get(tags(`${tag}:site`))]);
|
|
253
|
+
const base = siteBaseUrl(site) ?? "";
|
|
254
|
+
const listed = new Set(entries.map((e) => e.path));
|
|
255
|
+
const extra = site.settings.seo.noIndex ? [] : paths.filter((p) => !listed.has(p)).map((p) => ({ url: `${base}${p}`, priority: 0.6 }));
|
|
256
|
+
return [...entries.map((e) => ({ url: e.url, ...e.lastModified ? { lastModified: e.lastModified } : {}, priority: e.priority })), ...extra];
|
|
257
|
+
};
|
|
258
|
+
},
|
|
191
259
|
/**
|
|
192
260
|
* POST handler for the site's revalidation webhook (app/api/revalidate/route.ts):
|
|
193
261
|
* export const POST = hearthNext.revalidateHandler();
|
|
@@ -221,5 +289,8 @@ function createHearthNext(client, { tag = "hearth", revalidate = 3600 } = {}) {
|
|
|
221
289
|
export {
|
|
222
290
|
PREVIEW_COOKIE,
|
|
223
291
|
createHearthNext,
|
|
224
|
-
|
|
292
|
+
isNonProductionDeploy,
|
|
293
|
+
metadataFromMeta,
|
|
294
|
+
siteBaseUrl,
|
|
295
|
+
siteLang
|
|
225
296
|
};
|
package/dist/react.d.ts
CHANGED
|
@@ -30,6 +30,10 @@ type GuideTheme = {
|
|
|
30
30
|
media?: string;
|
|
31
31
|
/** The "+" on an expandable question. */
|
|
32
32
|
accent?: string;
|
|
33
|
+
/** A step's label ("Step 1": the text-and-image block's eyebrow). */
|
|
34
|
+
eyebrow?: string;
|
|
35
|
+
/** A step's screenshot (the text-and-image block's image, shown full width under the step). */
|
|
36
|
+
screenshot?: string;
|
|
33
37
|
};
|
|
34
38
|
type GuideLinkProps = {
|
|
35
39
|
href: string;
|
|
@@ -47,6 +51,15 @@ type GuideBlocksProps = {
|
|
|
47
51
|
};
|
|
48
52
|
/** Block types a guide can use; anything else is skipped. */
|
|
49
53
|
declare const GUIDE_BLOCK_TYPES: readonly ["hero", "richText", "mediaText", "schedule", "cardGrid", "faq", "gallery", "video", "quote", "stats", "cta"];
|
|
54
|
+
/**
|
|
55
|
+
* The guide's sections that have a heading, in order, with the anchor each one gets: for an "In this guide" list.
|
|
56
|
+
*
|
|
57
|
+
* guideOutline(page.blocks).map((s) => <a href={`#${s.id}`}>{s.title}</a>)
|
|
58
|
+
*/
|
|
59
|
+
declare function guideOutline(blocks: readonly unknown[]): {
|
|
60
|
+
id: string;
|
|
61
|
+
title: string;
|
|
62
|
+
}[];
|
|
50
63
|
/**
|
|
51
64
|
* Render a guide's blocks with your product's styles.
|
|
52
65
|
*
|
|
@@ -95,4 +108,4 @@ declare function HearthEmbed({ block, className }: {
|
|
|
95
108
|
className?: string;
|
|
96
109
|
}): react.JSX.Element;
|
|
97
110
|
|
|
98
|
-
export { type BlockComponents, GUIDE_BLOCK_TYPES, GuideBlocks, type GuideBlocksProps, type GuideLinkProps, type GuideTheme, HearthBlocks, HearthEmbed, JsonLd };
|
|
111
|
+
export { type BlockComponents, GUIDE_BLOCK_TYPES, GuideBlocks, type GuideBlocksProps, type GuideLinkProps, type GuideTheme, HearthBlocks, HearthEmbed, JsonLd, guideOutline };
|
package/dist/react.js
CHANGED
|
@@ -6,6 +6,14 @@ import { EMBED_ALLOW, EMBED_SANDBOX, embedDocument } from "@the-smithy/hearth-sc
|
|
|
6
6
|
import { videoEmbedUrl } from "@the-smithy/hearth-schema";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
var GUIDE_BLOCK_TYPES = ["hero", "richText", "mediaText", "schedule", "cardGrid", "faq", "gallery", "video", "quote", "stats", "cta"];
|
|
9
|
+
var anchor = (text) => text.toLowerCase().replace(/\*/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
10
|
+
var headingOf = (b) => typeof b.data?.heading === "string" && b.data.heading.trim() ? b.data.heading.trim() : null;
|
|
11
|
+
function guideOutline(blocks) {
|
|
12
|
+
return blocks.flatMap((b) => {
|
|
13
|
+
const title = GUIDE_BLOCK_TYPES.includes(b.type) && b.type !== "cta" ? headingOf(b) : null;
|
|
14
|
+
return title ? [{ id: anchor(title), title: title.replace(/\*/g, "") }] : [];
|
|
15
|
+
});
|
|
16
|
+
}
|
|
9
17
|
var stack = (gap) => ({ display: "flex", flexDirection: "column", gap });
|
|
10
18
|
var grid = (min) => ({ display: "grid", gap: "0.75rem", gridTemplateColumns: `repeat(auto-fit, minmax(min(100%, ${min}), 1fr))` });
|
|
11
19
|
var fill = { display: "block", width: "100%", height: "auto" };
|
|
@@ -32,13 +40,14 @@ function GuideBlocks({ blocks, theme = {}, renderMarkdown = plainMarkdown, linkC
|
|
|
32
40
|
return md(d.markdown);
|
|
33
41
|
case "mediaText": {
|
|
34
42
|
const image = d.image;
|
|
35
|
-
return /* @__PURE__ */ jsxs("div", { style:
|
|
36
|
-
/* @__PURE__ */ jsxs("div", { style:
|
|
43
|
+
return /* @__PURE__ */ jsxs("div", { style: stack("0.75rem"), children: [
|
|
44
|
+
/* @__PURE__ */ jsxs("div", { style: stack("0.375rem"), children: [
|
|
45
|
+
str(d.eyebrow) ? /* @__PURE__ */ jsx("p", { className: t.eyebrow, children: str(d.eyebrow) }) : null,
|
|
37
46
|
heading(d.heading),
|
|
38
|
-
md(d.markdown),
|
|
47
|
+
str(d.markdown) ? md(d.markdown) : null,
|
|
39
48
|
d.cta ? /* @__PURE__ */ jsx("p", { children: link(d.cta) }) : null
|
|
40
49
|
] }),
|
|
41
|
-
image ? /* @__PURE__ */ jsx("figure", { style: { margin: 0
|
|
50
|
+
image ? /* @__PURE__ */ jsx("figure", { style: { margin: 0 }, children: img(image, t.screenshot ?? t.media) }) : null
|
|
42
51
|
] });
|
|
43
52
|
}
|
|
44
53
|
case "schedule": {
|
|
@@ -131,7 +140,8 @@ function GuideBlocks({ blocks, theme = {}, renderMarkdown = plainMarkdown, linkC
|
|
|
131
140
|
}
|
|
132
141
|
return /* @__PURE__ */ jsx("div", { className: t.root, style: t.root ? void 0 : stack("2rem"), children: blocks.map((b, i) => {
|
|
133
142
|
const out = render(b);
|
|
134
|
-
|
|
143
|
+
const title = b.type !== "cta" ? headingOf(b) : null;
|
|
144
|
+
return out ? /* @__PURE__ */ jsx("section", { id: title ? anchor(title) : void 0, style: title ? { scrollMarginTop: "1.5rem" } : void 0, children: out }, b.id ?? i) : null;
|
|
135
145
|
}) });
|
|
136
146
|
}
|
|
137
147
|
|
|
@@ -175,5 +185,6 @@ export {
|
|
|
175
185
|
GuideBlocks,
|
|
176
186
|
HearthBlocks,
|
|
177
187
|
HearthEmbed,
|
|
178
|
-
JsonLd
|
|
188
|
+
JsonLd,
|
|
189
|
+
guideOutline
|
|
179
190
|
};
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Site, Page, HearthEvent, HearthEventDetail, SitemapEntry, Collection, CollectionEntry, CategoryGroup, Fundraiser, FundraiserDetail, HearthForm } from '@the-smithy/hearth-schema';
|
|
1
|
+
import { Site, Page, HearthEvent, HearthEventDetail, SitemapEntry, Redirect, Collection, CollectionEntry, CategoryGroup, Fundraiser, FundraiserDetail, HearthForm } from '@the-smithy/hearth-schema';
|
|
2
2
|
|
|
3
3
|
type MockHearthData = {
|
|
4
4
|
site?: Partial<Site>;
|
|
5
5
|
pages?: Page[];
|
|
6
6
|
events?: (HearthEvent | HearthEventDetail)[];
|
|
7
7
|
sitemap?: SitemapEntry[];
|
|
8
|
+
redirects?: Redirect[];
|
|
8
9
|
/** Collections, with their entries (page collections) keyed by collection slug. */
|
|
9
10
|
collections?: Collection[];
|
|
10
11
|
entries?: Record<string, CollectionEntry[]>;
|
package/dist/testing.js
CHANGED
|
@@ -46,6 +46,7 @@ function createMockHearth(data = {}) {
|
|
|
46
46
|
return event ? json({ data: event }) : notFound();
|
|
47
47
|
}
|
|
48
48
|
if (rest === "/sitemap") return json({ data: data.sitemap ?? [] });
|
|
49
|
+
if (rest === "/redirects") return json({ data: data.redirects ?? [] });
|
|
49
50
|
if (rest === "/fundraisers" || rest.startsWith("/fundraisers?")) {
|
|
50
51
|
const status = url.searchParams.get("status") ?? "active";
|
|
51
52
|
return json({ data: (data.fundraisers ?? []).filter((f) => status === "all" || f.status === status) });
|
package/dist/webhooks.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-smithy/hearth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Typed client for the Hearth Delivery API: content, SEO, forms and publish webhooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"llms.md"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@the-smithy/hearth-schema": "^0.
|
|
48
|
+
"@the-smithy/hearth-schema": "^0.16.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"next": ">=15",
|