@codepress/blog 0.0.0-stage → 0.1.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.
Files changed (61) hide show
  1. package/README.md +118 -2
  2. package/dist/blog.css +227 -0
  3. package/dist/chunks/BlogEditor-VF3UAJB6.js +2527 -0
  4. package/dist/chunks/chunk-3GCZFCUK.js +51 -0
  5. package/dist/chunks/chunk-3WRTL63O.js +106 -0
  6. package/dist/chunks/chunk-AEQDRBA3.js +128 -0
  7. package/dist/chunks/chunk-AJVVTSKZ.js +90 -0
  8. package/dist/chunks/chunk-EICE5ONB.js +197 -0
  9. package/dist/chunks/chunk-HWR3R5MU.js +48 -0
  10. package/dist/chunks/chunk-QX6276YA.js +0 -0
  11. package/dist/chunks/chunk-TYX2HJZC.js +68 -0
  12. package/dist/chunks/chunk-UA243KAQ.js +104 -0
  13. package/dist/chunks/chunk-WJVZD4UI.js +114 -0
  14. package/dist/chunks/chunk-XLJBCIXI.js +93 -0
  15. package/dist/cli.js +440 -0
  16. package/dist/content-source.d.ts +31 -0
  17. package/dist/editor/BlogEditor.d.ts +37 -0
  18. package/dist/editor/BlogEditorMount.d.ts +64 -0
  19. package/dist/editor/DocumentActions.d.ts +39 -0
  20. package/dist/editor/EditDesignLink.d.ts +30 -0
  21. package/dist/editor/NewPostForm.d.ts +10 -0
  22. package/dist/editor/SlashMenu.d.ts +5 -0
  23. package/dist/editor/Toolbar.d.ts +39 -0
  24. package/dist/editor/blocks.d.ts +5 -0
  25. package/dist/editor/client.d.ts +88 -0
  26. package/dist/editor/client.js +6 -0
  27. package/dist/editor/component-node.d.ts +14 -0
  28. package/dist/editor/config.d.ts +24 -0
  29. package/dist/editor/document.d.ts +47 -0
  30. package/dist/editor/extensions.d.ts +19 -0
  31. package/dist/editor/header.d.ts +20 -0
  32. package/dist/editor/index.d.ts +11 -0
  33. package/dist/editor/index.js +209 -0
  34. package/dist/editor/paste-guard.d.ts +14 -0
  35. package/dist/editor/primitives.d.ts +17 -0
  36. package/dist/editor/props-form.d.ts +17 -0
  37. package/dist/editor/serialize.d.ts +34 -0
  38. package/dist/editor/slash-command.d.ts +21 -0
  39. package/dist/feed.d.ts +4 -0
  40. package/dist/index.d.ts +8 -0
  41. package/dist/index.js +72 -0
  42. package/dist/next/app.d.ts +34 -0
  43. package/dist/next/config.d.ts +44 -0
  44. package/dist/next/index.d.ts +2 -0
  45. package/dist/next/index.js +94 -0
  46. package/dist/next/pages.d.ts +33 -0
  47. package/dist/next/pages.js +167 -0
  48. package/dist/next/routes.d.ts +14 -0
  49. package/dist/next/routes.js +37 -0
  50. package/dist/next/views.d.ts +18 -0
  51. package/dist/react/components.d.ts +18 -0
  52. package/dist/react/index.d.ts +2 -0
  53. package/dist/react/index.js +15 -0
  54. package/dist/react/render.d.ts +7 -0
  55. package/dist/registry.d.ts +95 -0
  56. package/dist/sanitize.d.ts +33 -0
  57. package/dist/schema.d.ts +24 -0
  58. package/dist/seo.d.ts +63 -0
  59. package/dist/sitemap.d.ts +4 -0
  60. package/dist/xml.d.ts +1 -0
  61. package/package.json +92 -4
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
1
+ import {
2
+ buildContentSchema,
3
+ schemaVersion
4
+ } from "./chunks/chunk-HWR3R5MU.js";
5
+ import {
6
+ DROP_WITH_CONTENT,
7
+ sanitizeHtml,
8
+ sanitizeNodes,
9
+ sanitizeUrl
10
+ } from "./chunks/chunk-EICE5ONB.js";
11
+ import {
12
+ escapeXml,
13
+ feedXml,
14
+ sitemapXml
15
+ } from "./chunks/chunk-TYX2HJZC.js";
16
+ import {
17
+ absoluteImage,
18
+ blogPostingJsonLd,
19
+ blogUrl,
20
+ breadcrumbJsonLd,
21
+ createHttpContentSource,
22
+ feedUrl,
23
+ homeUrl,
24
+ jsonLd,
25
+ postUrl
26
+ } from "./chunks/chunk-AEQDRBA3.js";
27
+ import {
28
+ BLOCK_ID_ATTRIBUTE,
29
+ CALLOUT_TYPES,
30
+ COMPONENTS,
31
+ ELEMENTS,
32
+ PROPS_ATTRIBUTE,
33
+ SLUG_PATTERN,
34
+ URL_SCHEMES,
35
+ calloutProps,
36
+ figureProps,
37
+ isComponentTag,
38
+ isElementTag,
39
+ postMetaSchema
40
+ } from "./chunks/chunk-XLJBCIXI.js";
41
+ export {
42
+ BLOCK_ID_ATTRIBUTE,
43
+ CALLOUT_TYPES,
44
+ COMPONENTS,
45
+ DROP_WITH_CONTENT,
46
+ ELEMENTS,
47
+ PROPS_ATTRIBUTE,
48
+ SLUG_PATTERN,
49
+ URL_SCHEMES,
50
+ absoluteImage,
51
+ blogPostingJsonLd,
52
+ blogUrl,
53
+ breadcrumbJsonLd,
54
+ buildContentSchema,
55
+ calloutProps,
56
+ createHttpContentSource,
57
+ escapeXml,
58
+ feedUrl,
59
+ feedXml,
60
+ figureProps,
61
+ homeUrl,
62
+ isComponentTag,
63
+ isElementTag,
64
+ jsonLd,
65
+ postMetaSchema,
66
+ postUrl,
67
+ sanitizeHtml,
68
+ sanitizeNodes,
69
+ sanitizeUrl,
70
+ schemaVersion,
71
+ sitemapXml
72
+ };
@@ -0,0 +1,34 @@
1
+ import type { Metadata } from "next";
2
+ import { type BlogConfig } from "./config.js";
3
+ type SlugParams = {
4
+ params: Promise<{
5
+ slug: string;
6
+ }>;
7
+ };
8
+ /**
9
+ * The App Router blog. Each route file re-exports one member, and declares
10
+ * `export const revalidate = 60` itself (Next.js reads segment config
11
+ * statically):
12
+ *
13
+ * - `app/blog/page.tsx`: `IndexPage`, `generateIndexMetadata`
14
+ * - `app/blog/[slug]/page.tsx`: `PostPage`, `generatePostMetadata`, `generateStaticParams`
15
+ *
16
+ * The feed and sitemap route handlers come from `createAppBlogRoutes`
17
+ * (`@codepress/blog/next/routes`), which carries no React: a route handler
18
+ * cannot bundle `next/link`.
19
+ *
20
+ * A 404 from the API is "no such post"; any other failure throws, so ISR keeps
21
+ * serving the last good page.
22
+ */
23
+ export declare function createAppBlog(config: BlogConfig): {
24
+ blog: import("./config.js").ResolvedBlog;
25
+ IndexPage(): Promise<import("react/jsx-runtime").JSX.Element>;
26
+ PostPage({ params }: SlugParams): Promise<import("react/jsx-runtime").JSX.Element>;
27
+ generateStaticParams(): Promise<{
28
+ slug: string;
29
+ }[]>;
30
+ generateIndexMetadata(): Promise<Metadata>;
31
+ generatePostMetadata({ params }: SlugParams): Promise<Metadata>;
32
+ };
33
+ export type AppBlog = ReturnType<typeof createAppBlog>;
34
+ export {};
@@ -0,0 +1,44 @@
1
+ import { type ContentSource, type PostSummary } from "../content-source.js";
2
+ import type { BlogSite } from "../seo.js";
3
+ export declare const DEFAULT_API_BASE = "https://api.codepress.dev/v1";
4
+ export declare const DEFAULT_BASE_PATH = "/blog";
5
+ export declare const DEFAULT_REVALIDATE_SECONDS = 60;
6
+ /** How a Next.js site mounts its CodePress blog. */
7
+ export interface BlogConfig {
8
+ /** The content site's slug on CodePress. */
9
+ site: string;
10
+ /** The site's public origin, for example `https://acme.com`. Canonical, feed and sitemap links are built from it. */
11
+ baseUrl: string;
12
+ /** Where the blog is mounted. Defaults to `/blog`. */
13
+ basePath?: string;
14
+ /** The CodePress API root including `/v1`. Defaults to `https://api.codepress.dev/v1`. */
15
+ apiBase?: string;
16
+ /** Seconds between ISR regenerations (Pages Router, and the feed and sitemap cache headers). Defaults to 60. */
17
+ revalidate?: number;
18
+ /** The publisher's name. Defaults to the host of `baseUrl`. */
19
+ name?: string;
20
+ /** The blog's title. Defaults to `<name> Blog`. */
21
+ title?: string;
22
+ /** The blog's description. */
23
+ description?: string;
24
+ /** Absolute URL of the share image for posts without an `ogImage`. */
25
+ defaultImage?: string;
26
+ /** Timeout for each API request. Defaults to 10 seconds. */
27
+ timeoutMs?: number;
28
+ }
29
+ export interface ResolvedBlog {
30
+ site: BlogSite;
31
+ /** The mount path, with a leading slash and no trailing slash. */
32
+ basePath: string;
33
+ revalidate: number;
34
+ source: ContentSource;
35
+ }
36
+ export declare function resolveBlog(config: BlogConfig): ResolvedBlog;
37
+ /** True while `next build` prerenders pages. */
38
+ export declare function isBuildPhase(): boolean;
39
+ /**
40
+ * The published posts. `next build` prerenders the index, so an API outage
41
+ * then would fail the site's deploy: the build ships an empty blog instead.
42
+ * At runtime the error propagates, so ISR keeps serving the last good page.
43
+ */
44
+ export declare function listPostsForRender(blog: ResolvedBlog): Promise<PostSummary[]>;
@@ -0,0 +1,2 @@
1
+ export { createAppBlog, type AppBlog } from "./app.js";
2
+ export { DEFAULT_API_BASE, DEFAULT_BASE_PATH, DEFAULT_REVALIDATE_SECONDS, resolveBlog, type BlogConfig, type ResolvedBlog, } from "./config.js";
@@ -0,0 +1,94 @@
1
+ import {
2
+ BlogIndexView,
3
+ BlogPostView,
4
+ shareImage
5
+ } from "../chunks/chunk-WJVZD4UI.js";
6
+ import {
7
+ DEFAULT_API_BASE,
8
+ DEFAULT_BASE_PATH,
9
+ DEFAULT_REVALIDATE_SECONDS,
10
+ listPostsForRender,
11
+ resolveBlog
12
+ } from "../chunks/chunk-3GCZFCUK.js";
13
+ import "../chunks/chunk-AJVVTSKZ.js";
14
+ import "../chunks/chunk-EICE5ONB.js";
15
+ import {
16
+ blogUrl,
17
+ feedUrl,
18
+ postUrl
19
+ } from "../chunks/chunk-AEQDRBA3.js";
20
+ import "../chunks/chunk-XLJBCIXI.js";
21
+
22
+ // src/next/app.tsx
23
+ import { notFound } from "next/navigation.js";
24
+ import { cache } from "react";
25
+ import { jsx } from "react/jsx-runtime";
26
+ function createAppBlog(config) {
27
+ const blog = resolveBlog(config);
28
+ const { site } = blog;
29
+ const getPost = cache((slug) => blog.source.getPost(slug));
30
+ return {
31
+ blog,
32
+ async IndexPage() {
33
+ return /* @__PURE__ */ jsx(BlogIndexView, { blog, posts: await listPostsForRender(blog) });
34
+ },
35
+ async PostPage({ params }) {
36
+ const post = await getPost((await params).slug);
37
+ if (!post) notFound();
38
+ return /* @__PURE__ */ jsx(BlogPostView, { blog, post });
39
+ },
40
+ async generateStaticParams() {
41
+ return (await listPostsForRender(blog)).map(({ slug }) => ({ slug }));
42
+ },
43
+ async generateIndexMetadata() {
44
+ const image = shareImage(site, void 0);
45
+ return {
46
+ title: site.title,
47
+ description: site.description,
48
+ alternates: {
49
+ canonical: blogUrl(site),
50
+ types: { "application/rss+xml": feedUrl(site) }
51
+ },
52
+ openGraph: {
53
+ type: "website",
54
+ url: blogUrl(site),
55
+ title: site.title,
56
+ description: site.description,
57
+ ...image ? { images: [image] } : {}
58
+ }
59
+ };
60
+ },
61
+ async generatePostMetadata({ params }) {
62
+ const post = await getPost((await params).slug);
63
+ if (!post) return {};
64
+ const { slug, meta } = post;
65
+ const image = shareImage(site, meta.ogImage);
66
+ return {
67
+ title: meta.title,
68
+ description: meta.description,
69
+ authors: meta.authors?.map((name) => ({ name })),
70
+ alternates: {
71
+ canonical: postUrl(site, slug),
72
+ types: { "application/rss+xml": feedUrl(site) }
73
+ },
74
+ openGraph: {
75
+ type: "article",
76
+ url: postUrl(site, slug),
77
+ title: meta.title,
78
+ description: meta.description,
79
+ publishedTime: meta.date,
80
+ modifiedTime: meta.updated ?? meta.date,
81
+ ...image ? { images: [image] } : {}
82
+ },
83
+ twitter: { card: image ? "summary_large_image" : "summary" }
84
+ };
85
+ }
86
+ };
87
+ }
88
+ export {
89
+ DEFAULT_API_BASE,
90
+ DEFAULT_BASE_PATH,
91
+ DEFAULT_REVALIDATE_SECONDS,
92
+ createAppBlog,
93
+ resolveBlog
94
+ };
@@ -0,0 +1,33 @@
1
+ import type { GetServerSideProps, GetStaticPaths, GetStaticProps } from "next";
2
+ import type { Post, PostSummary } from "../content-source.js";
3
+ import { type BlogConfig, type ResolvedBlog } from "./config.js";
4
+ export type { BlogConfig } from "./config.js";
5
+ interface IndexProps {
6
+ posts: PostSummary[];
7
+ }
8
+ interface PostProps {
9
+ post: Post;
10
+ }
11
+ /**
12
+ * The Pages Router blog. Each route file re-exports members:
13
+ *
14
+ * - `pages/blog/index.tsx`: `IndexPage`, `getIndexStaticProps` as `getStaticProps`
15
+ * - `pages/blog/[slug].tsx`: `PostPage`, `getPostStaticPaths`, `getPostStaticProps`
16
+ * - `pages/blog/feed.xml.tsx`, `sitemap.xml.tsx`: `getFeedServerSideProps`, `getSitemapServerSideProps`
17
+ *
18
+ * Pages regenerate every `revalidate` seconds (ISR). A 404 from the API is "no
19
+ * such post"; any other failure throws, so ISR keeps the last good page.
20
+ */
21
+ export declare function createPagesBlog(config: BlogConfig): {
22
+ blog: ResolvedBlog;
23
+ IndexPage: ({ posts }: IndexProps) => import("react/jsx-runtime").JSX.Element;
24
+ PostPage: ({ post }: PostProps) => import("react/jsx-runtime").JSX.Element;
25
+ getIndexStaticProps: GetStaticProps<IndexProps>;
26
+ getPostStaticPaths: GetStaticPaths;
27
+ getPostStaticProps: GetStaticProps<PostProps>;
28
+ getFeedServerSideProps: GetServerSideProps;
29
+ getSitemapServerSideProps: GetServerSideProps;
30
+ /** The component for the feed and sitemap routes, whose response `getServerSideProps` writes. */
31
+ EmptyPage: () => null;
32
+ };
33
+ export type PagesBlog = ReturnType<typeof createPagesBlog>;
@@ -0,0 +1,167 @@
1
+ import {
2
+ BlogIndexView,
3
+ BlogPostView,
4
+ indexJsonLd,
5
+ postJsonLd,
6
+ shareImage
7
+ } from "../chunks/chunk-WJVZD4UI.js";
8
+ import {
9
+ isBuildPhase,
10
+ listPostsForRender,
11
+ resolveBlog
12
+ } from "../chunks/chunk-3GCZFCUK.js";
13
+ import "../chunks/chunk-AJVVTSKZ.js";
14
+ import "../chunks/chunk-EICE5ONB.js";
15
+ import {
16
+ feedXml,
17
+ sitemapXml
18
+ } from "../chunks/chunk-TYX2HJZC.js";
19
+ import {
20
+ blogUrl,
21
+ feedUrl,
22
+ postUrl
23
+ } from "../chunks/chunk-AEQDRBA3.js";
24
+ import "../chunks/chunk-XLJBCIXI.js";
25
+
26
+ // src/next/pages.tsx
27
+ import Head from "next/head.js";
28
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
29
+ function PageHead({
30
+ blog,
31
+ title,
32
+ description,
33
+ url,
34
+ image,
35
+ type,
36
+ structuredData
37
+ }) {
38
+ return /* @__PURE__ */ jsxs(Head, { children: [
39
+ /* @__PURE__ */ jsx("title", { children: title }),
40
+ /* @__PURE__ */ jsx("meta", { name: "description", content: description }),
41
+ /* @__PURE__ */ jsx("link", { rel: "canonical", href: url }),
42
+ /* @__PURE__ */ jsx(
43
+ "link",
44
+ {
45
+ rel: "alternate",
46
+ type: "application/rss+xml",
47
+ title: blog.site.title,
48
+ href: feedUrl(blog.site)
49
+ }
50
+ ),
51
+ /* @__PURE__ */ jsx("meta", { property: "og:type", content: type }),
52
+ /* @__PURE__ */ jsx("meta", { property: "og:url", content: url }),
53
+ /* @__PURE__ */ jsx("meta", { property: "og:title", content: title }),
54
+ /* @__PURE__ */ jsx("meta", { property: "og:description", content: description }),
55
+ image ? /* @__PURE__ */ jsx("meta", { property: "og:image", content: image }) : null,
56
+ /* @__PURE__ */ jsx(
57
+ "meta",
58
+ {
59
+ name: "twitter:card",
60
+ content: image ? "summary_large_image" : "summary"
61
+ }
62
+ ),
63
+ structuredData.map((data, index) => /* @__PURE__ */ jsx(
64
+ "script",
65
+ {
66
+ type: "application/ld+json",
67
+ dangerouslySetInnerHTML: { __html: data }
68
+ },
69
+ index
70
+ ))
71
+ ] });
72
+ }
73
+ function createPagesBlog(config) {
74
+ const blog = resolveBlog(config);
75
+ const { site, revalidate } = blog;
76
+ function respond(res, body, contentType) {
77
+ res.setHeader("Content-Type", `${contentType}; charset=utf-8`);
78
+ res.setHeader(
79
+ "Cache-Control",
80
+ `public, s-maxage=${revalidate}, stale-while-revalidate=${revalidate}`
81
+ );
82
+ res.end(body);
83
+ return { props: {} };
84
+ }
85
+ function IndexPage({ posts }) {
86
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
87
+ /* @__PURE__ */ jsx(
88
+ PageHead,
89
+ {
90
+ blog,
91
+ title: site.title,
92
+ description: site.description,
93
+ url: blogUrl(site),
94
+ image: shareImage(site, void 0),
95
+ type: "website",
96
+ structuredData: [indexJsonLd(site, posts)]
97
+ }
98
+ ),
99
+ /* @__PURE__ */ jsx(BlogIndexView, { blog, posts, structuredData: false })
100
+ ] });
101
+ }
102
+ function PostPage({ post }) {
103
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
104
+ /* @__PURE__ */ jsx(
105
+ PageHead,
106
+ {
107
+ blog,
108
+ title: post.meta.title,
109
+ description: post.meta.description,
110
+ url: postUrl(site, post.slug),
111
+ image: shareImage(site, post.meta.ogImage),
112
+ type: "article",
113
+ structuredData: postJsonLd(site, post)
114
+ }
115
+ ),
116
+ /* @__PURE__ */ jsx(BlogPostView, { blog, post, structuredData: false })
117
+ ] });
118
+ }
119
+ const getIndexStaticProps = async () => {
120
+ try {
121
+ return { props: { posts: await blog.source.listPosts() }, revalidate };
122
+ } catch (error) {
123
+ if (!isBuildPhase()) throw error;
124
+ console.error(
125
+ "[blog] prerendering an empty blog, API unavailable:",
126
+ error
127
+ );
128
+ return { props: { posts: [] }, revalidate: 1 };
129
+ }
130
+ };
131
+ const getPostStaticPaths = async () => ({
132
+ paths: (await listPostsForRender(blog)).map(({ slug }) => ({
133
+ params: { slug }
134
+ })),
135
+ fallback: "blocking"
136
+ });
137
+ const getPostStaticProps = async ({ params }) => {
138
+ const post = await blog.source.getPost(String(params?.slug ?? ""));
139
+ if (!post) return { notFound: true, revalidate };
140
+ return { props: { post }, revalidate };
141
+ };
142
+ const getFeedServerSideProps = async ({ res }) => respond(
143
+ res,
144
+ feedXml(site, await blog.source.listPosts()),
145
+ "application/rss+xml"
146
+ );
147
+ const getSitemapServerSideProps = async ({ res }) => respond(
148
+ res,
149
+ sitemapXml(site, await blog.source.listPosts()),
150
+ "application/xml"
151
+ );
152
+ return {
153
+ blog,
154
+ IndexPage,
155
+ PostPage,
156
+ getIndexStaticProps,
157
+ getPostStaticPaths,
158
+ getPostStaticProps,
159
+ getFeedServerSideProps,
160
+ getSitemapServerSideProps,
161
+ /** The component for the feed and sitemap routes, whose response `getServerSideProps` writes. */
162
+ EmptyPage: () => null
163
+ };
164
+ }
165
+ export {
166
+ createPagesBlog
167
+ };
@@ -0,0 +1,14 @@
1
+ import { type BlogConfig } from "./config.js";
2
+ /**
3
+ * The App Router's feed and sitemap route handlers:
4
+ *
5
+ * - `app/blog/feed.xml/route.ts`: `export const GET = routes.feed`
6
+ * - `app/blog/sitemap.xml/route.ts`: `export const GET = routes.sitemap`
7
+ *
8
+ * Each route file declares `export const revalidate = 60` itself. Links are
9
+ * absolute, built from the config's `baseUrl` and `basePath`.
10
+ */
11
+ export declare function createAppBlogRoutes(config: BlogConfig): {
12
+ feed(): Promise<Response>;
13
+ sitemap(): Promise<Response>;
14
+ };
@@ -0,0 +1,37 @@
1
+ import {
2
+ listPostsForRender,
3
+ resolveBlog
4
+ } from "../chunks/chunk-3GCZFCUK.js";
5
+ import {
6
+ feedXml,
7
+ sitemapXml
8
+ } from "../chunks/chunk-TYX2HJZC.js";
9
+ import "../chunks/chunk-AEQDRBA3.js";
10
+ import "../chunks/chunk-XLJBCIXI.js";
11
+
12
+ // src/next/routes.ts
13
+ function xml(body, contentType) {
14
+ return new Response(body, {
15
+ headers: { "Content-Type": `${contentType}; charset=utf-8` }
16
+ });
17
+ }
18
+ function createAppBlogRoutes(config) {
19
+ const blog = resolveBlog(config);
20
+ return {
21
+ async feed() {
22
+ return xml(
23
+ feedXml(blog.site, await listPostsForRender(blog)),
24
+ "application/rss+xml"
25
+ );
26
+ },
27
+ async sitemap() {
28
+ return xml(
29
+ sitemapXml(blog.site, await listPostsForRender(blog)),
30
+ "application/xml"
31
+ );
32
+ }
33
+ };
34
+ }
35
+ export {
36
+ createAppBlogRoutes
37
+ };
@@ -0,0 +1,18 @@
1
+ import type { Post, PostSummary } from "../content-source.js";
2
+ import { type BlogSite } from "../seo.js";
3
+ import type { ResolvedBlog } from "./config.js";
4
+ /** A share image, or nothing when neither the post nor the site has one. */
5
+ export declare function shareImage(site: BlogSite, ogImage: string | undefined): string | undefined;
6
+ export declare function indexJsonLd(site: BlogSite, posts: PostSummary[]): string;
7
+ export declare function postJsonLd(site: BlogSite, post: Post): string[];
8
+ export declare function BlogIndexView({ blog, posts, structuredData, }: {
9
+ blog: ResolvedBlog;
10
+ posts: PostSummary[];
11
+ /** The Pages Router renders JSON-LD in its `<Head>` instead. */
12
+ structuredData?: boolean;
13
+ }): import("react/jsx-runtime").JSX.Element;
14
+ export declare function BlogPostView({ blog, post, structuredData, }: {
15
+ blog: ResolvedBlog;
16
+ post: Post;
17
+ structuredData?: boolean;
18
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import type { ComponentType, ReactNode } from "react";
2
+ import type { z } from "zod";
3
+ import type { ComponentTag, calloutProps, figureProps } from "../registry.js";
4
+ type CalloutProps = z.infer<typeof calloutProps> & {
5
+ children?: ReactNode;
6
+ "data-cp-id"?: string;
7
+ };
8
+ export declare function CpCallout({ type, children, ...rest }: CalloutProps): import("react/jsx-runtime").JSX.Element;
9
+ type FigureProps = z.infer<typeof figureProps> & {
10
+ "data-cp-id"?: string;
11
+ };
12
+ export declare function CpFigure({ src, alt, caption, width, height, ...rest }: FigureProps): import("react/jsx-runtime").JSX.Element;
13
+ /**
14
+ * Each registered component's view. It receives the validated props, plus
15
+ * `children` for block and inline content.
16
+ */
17
+ export declare const COMPONENT_VIEWS: Record<ComponentTag, ComponentType<never>>;
18
+ export {};
@@ -0,0 +1,2 @@
1
+ export { renderPostBody } from "./render.js";
2
+ export { COMPONENT_VIEWS, CpCallout, CpFigure } from "./components.js";
@@ -0,0 +1,15 @@
1
+ import "../chunks/chunk-QX6276YA.js";
2
+ import {
3
+ COMPONENT_VIEWS,
4
+ CpCallout,
5
+ CpFigure,
6
+ renderPostBody
7
+ } from "../chunks/chunk-AJVVTSKZ.js";
8
+ import "../chunks/chunk-EICE5ONB.js";
9
+ import "../chunks/chunk-XLJBCIXI.js";
10
+ export {
11
+ COMPONENT_VIEWS,
12
+ CpCallout,
13
+ CpFigure,
14
+ renderPostBody
15
+ };
@@ -0,0 +1,7 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * Turns post HTML into React. The HTML is always sanitized first, so this is
4
+ * safe to call on content that came from anywhere; registered `cp-*` tags
5
+ * become their React component, everything else is plain markup.
6
+ */
7
+ export declare function renderPostBody(html: string): ReactNode;
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The single source of truth for what a blog post may contain.
4
+ *
5
+ * The sanitizer, the renderer and the `_schema.json` contract are all derived
6
+ * from this file, so a tag added here is allowed, rendered and published to the
7
+ * backend validator together. It has no React in it, so server code (and the
8
+ * backend's bundled schema) can use it; `./react` maps each tag to its view. The backend validates against
9
+ * the schema that is actually deployed, which is what makes "new components
10
+ * only work after the deploy that registers them" true.
11
+ */
12
+ /** Slugs double as file names and URL segments. */
13
+ export declare const SLUG_PATTERN: RegExp;
14
+ /** Attribute every element and component may carry: a stable block id. */
15
+ export declare const BLOCK_ID_ATTRIBUTE = "data-cp-id";
16
+ /** Attribute that carries a component's JSON props. */
17
+ export declare const PROPS_ATTRIBUTE = "data-props";
18
+ export declare const URL_SCHEMES: readonly ["https", "http", "mailto"];
19
+ export interface ElementDefinition {
20
+ /** Attributes allowed on this element, `data-cp-id` included. */
21
+ attributes: readonly string[];
22
+ /** The subset of `attributes` whose value is a URL. */
23
+ urlAttributes?: readonly string[];
24
+ }
25
+ /** Plain HTML elements a post may use. Everything else is not allowed. */
26
+ export declare const ELEMENTS: Record<string, ElementDefinition>;
27
+ /**
28
+ * `"block"` components may contain block children, `"inline"` ones inline
29
+ * children, and `"none"` components are atoms whose props are all their
30
+ * content.
31
+ */
32
+ export type ComponentContent = "block" | "inline" | "none";
33
+ export interface ComponentDefinition {
34
+ attributes: readonly string[];
35
+ content: ComponentContent;
36
+ /** Validates the JSON in `data-props`; emitted as JSON Schema. */
37
+ props: z.ZodType<Record<string, unknown>>;
38
+ }
39
+ export declare const CALLOUT_TYPES: readonly ["info", "warn", "tip"];
40
+ export declare const calloutProps: z.ZodObject<{
41
+ type: z.ZodEnum<{
42
+ info: "info";
43
+ warn: "warn";
44
+ tip: "tip";
45
+ }>;
46
+ }, z.core.$strict>;
47
+ export declare const figureProps: z.ZodObject<{
48
+ src: z.ZodString;
49
+ alt: z.ZodString;
50
+ caption: z.ZodOptional<z.ZodString>;
51
+ width: z.ZodOptional<z.ZodNumber>;
52
+ height: z.ZodOptional<z.ZodNumber>;
53
+ }, z.core.$strict>;
54
+ export declare const COMPONENTS: {
55
+ readonly "cp-callout": {
56
+ readonly attributes: readonly ["data-cp-id", "data-props"];
57
+ readonly content: "block";
58
+ readonly props: z.ZodObject<{
59
+ type: z.ZodEnum<{
60
+ info: "info";
61
+ warn: "warn";
62
+ tip: "tip";
63
+ }>;
64
+ }, z.core.$strict>;
65
+ };
66
+ readonly "cp-figure": {
67
+ readonly attributes: readonly ["data-cp-id", "data-props"];
68
+ readonly content: "none";
69
+ readonly props: z.ZodObject<{
70
+ src: z.ZodString;
71
+ alt: z.ZodString;
72
+ caption: z.ZodOptional<z.ZodString>;
73
+ width: z.ZodOptional<z.ZodNumber>;
74
+ height: z.ZodOptional<z.ZodNumber>;
75
+ }, z.core.$strict>;
76
+ };
77
+ };
78
+ export type ComponentTag = keyof typeof COMPONENTS;
79
+ export declare function isComponentTag(tag: string): tag is ComponentTag;
80
+ export declare function isElementTag(tag: string): boolean;
81
+ /** The metadata block at the top of every post file. */
82
+ export declare const postMetaSchema: z.ZodObject<{
83
+ title: z.ZodString;
84
+ description: z.ZodString;
85
+ date: z.ZodString;
86
+ updated: z.ZodOptional<z.ZodString>;
87
+ authors: z.ZodOptional<z.ZodArray<z.ZodString>>;
88
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
89
+ ogImage: z.ZodOptional<z.ZodString>;
90
+ status: z.ZodOptional<z.ZodEnum<{
91
+ published: "published";
92
+ draft: "draft";
93
+ }>>;
94
+ }, z.core.$loose>;
95
+ export type PostMeta = z.infer<typeof postMetaSchema>;