@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
@@ -0,0 +1,51 @@
1
+ import {
2
+ createHttpContentSource
3
+ } from "./chunk-AEQDRBA3.js";
4
+
5
+ // src/next/config.ts
6
+ var DEFAULT_API_BASE = "https://api.codepress.dev/v1";
7
+ var DEFAULT_BASE_PATH = "/blog";
8
+ var DEFAULT_REVALIDATE_SECONDS = 60;
9
+ function resolveBlog(config) {
10
+ const origin = new URL(config.baseUrl).origin;
11
+ const basePath = `/${(config.basePath ?? DEFAULT_BASE_PATH).replace(/^\/+|\/+$/g, "")}`;
12
+ const name = config.name ?? new URL(origin).host;
13
+ return {
14
+ site: {
15
+ baseUrl: `${origin}${basePath}`,
16
+ homeUrl: origin,
17
+ name,
18
+ title: config.title ?? `${name} Blog`,
19
+ description: config.description ?? `Posts from ${name}.`,
20
+ defaultImage: config.defaultImage ?? ""
21
+ },
22
+ basePath,
23
+ revalidate: config.revalidate ?? DEFAULT_REVALIDATE_SECONDS,
24
+ source: createHttpContentSource({
25
+ apiBase: config.apiBase ?? DEFAULT_API_BASE,
26
+ site: config.site,
27
+ timeoutMs: config.timeoutMs
28
+ })
29
+ };
30
+ }
31
+ function isBuildPhase() {
32
+ return process.env.NEXT_PHASE === "phase-production-build";
33
+ }
34
+ async function listPostsForRender(blog) {
35
+ try {
36
+ return await blog.source.listPosts();
37
+ } catch (error) {
38
+ if (!isBuildPhase()) throw error;
39
+ console.error("[blog] prerendering an empty blog, API unavailable:", error);
40
+ return [];
41
+ }
42
+ }
43
+
44
+ export {
45
+ DEFAULT_API_BASE,
46
+ DEFAULT_BASE_PATH,
47
+ DEFAULT_REVALIDATE_SECONDS,
48
+ resolveBlog,
49
+ isBuildPhase,
50
+ listPostsForRender
51
+ };
@@ -0,0 +1,106 @@
1
+ // src/editor/client.ts
2
+ async function failure(response) {
3
+ try {
4
+ const body = await response.json();
5
+ if (typeof body.detail === "string") return body.detail;
6
+ } catch {
7
+ }
8
+ return `Request failed (${response.status})`;
9
+ }
10
+ var networkMessage = "Could not reach the server.";
11
+ function createDraftClient({
12
+ apiBase,
13
+ auth
14
+ }) {
15
+ const root = apiBase.replace(/\/+$/, "");
16
+ const request = async (method, path, body) => {
17
+ const headers = { Accept: "application/json" };
18
+ if (body !== void 0) headers["Content-Type"] = "application/json";
19
+ const token = await auth.getToken();
20
+ if (token) headers.Authorization = `Bearer ${token}`;
21
+ const response = await fetch(`${root}${path}`, {
22
+ method,
23
+ headers,
24
+ body: body === void 0 ? void 0 : JSON.stringify(body)
25
+ });
26
+ auth.onResponse?.(response);
27
+ return response;
28
+ };
29
+ const draftPath = (site, slug) => `/content-sites/${site}/drafts/${slug}`;
30
+ async function action(send) {
31
+ try {
32
+ const response = await send();
33
+ if (response.ok) return { kind: "ok" };
34
+ if (response.status === 422) {
35
+ const body = await response.json().catch(() => ({}));
36
+ if (body.violations) {
37
+ return {
38
+ kind: "rejected",
39
+ detail: body.detail ?? "The content was rejected.",
40
+ violations: body.violations
41
+ };
42
+ }
43
+ }
44
+ return {
45
+ kind: "error",
46
+ message: await failure(response),
47
+ status: response.status
48
+ };
49
+ } catch {
50
+ return { kind: "error", message: networkMessage };
51
+ }
52
+ }
53
+ return {
54
+ async getCapabilities(site) {
55
+ try {
56
+ const response = await request("GET", `/content-sites/${site}/me`);
57
+ return response.ok ? await response.json() : null;
58
+ } catch {
59
+ return null;
60
+ }
61
+ },
62
+ async getDraft(site, slug) {
63
+ try {
64
+ const response = await request("GET", draftPath(site, slug));
65
+ if (!response.ok) return { error: await failure(response) };
66
+ return { draft: await response.json() };
67
+ } catch {
68
+ return { error: networkMessage };
69
+ }
70
+ },
71
+ async saveDraft(site, slug, html, baseSha) {
72
+ try {
73
+ const response = await request("PUT", draftPath(site, slug), {
74
+ html,
75
+ base_sha: baseSha
76
+ });
77
+ if (response.ok) {
78
+ return { kind: "saved", draft: await response.json() };
79
+ }
80
+ const body = await response.json().catch(() => ({}));
81
+ if (response.status === 409 && body.current) {
82
+ return { kind: "conflict", current: body.current };
83
+ }
84
+ if (response.status === 422 && body.violations) {
85
+ return {
86
+ kind: "rejected",
87
+ detail: body.detail ?? "The content was rejected.",
88
+ violations: body.violations
89
+ };
90
+ }
91
+ return {
92
+ kind: "error",
93
+ message: body.detail ?? `Request failed (${response.status})`
94
+ };
95
+ } catch {
96
+ return { kind: "error", message: networkMessage };
97
+ }
98
+ },
99
+ publishDraft: (site, slug) => action(() => request("POST", `${draftPath(site, slug)}/publish`)),
100
+ discardDraft: (site, slug) => action(() => request("DELETE", draftPath(site, slug)))
101
+ };
102
+ }
103
+
104
+ export {
105
+ createDraftClient
106
+ };
@@ -0,0 +1,128 @@
1
+ import {
2
+ SLUG_PATTERN,
3
+ postMetaSchema
4
+ } from "./chunk-XLJBCIXI.js";
5
+
6
+ // src/seo.ts
7
+ function blogUrl(site) {
8
+ return site.baseUrl.replace(/\/+$/, "");
9
+ }
10
+ function postUrl(site, slug) {
11
+ return `${blogUrl(site)}/${slug}`;
12
+ }
13
+ function feedUrl(site) {
14
+ return `${blogUrl(site)}/feed.xml`;
15
+ }
16
+ function siteOrigin(site) {
17
+ return new URL(site.baseUrl).origin;
18
+ }
19
+ function homeUrl(site) {
20
+ return site.homeUrl ?? siteOrigin(site);
21
+ }
22
+ function jsonLd(value) {
23
+ return JSON.stringify(value).replace(/</g, "\\u003c");
24
+ }
25
+ function absoluteImage(site, ogImage) {
26
+ if (!ogImage) return site.defaultImage;
27
+ return ogImage.startsWith("/") ? `${siteOrigin(site)}${ogImage}` : ogImage;
28
+ }
29
+ function blogPostingJsonLd(site, slug, meta) {
30
+ return {
31
+ "@context": "https://schema.org",
32
+ "@type": "BlogPosting",
33
+ headline: meta.title,
34
+ description: meta.description,
35
+ url: postUrl(site, slug),
36
+ mainEntityOfPage: postUrl(site, slug),
37
+ datePublished: meta.date,
38
+ dateModified: meta.updated ?? meta.date,
39
+ image: absoluteImage(site, meta.ogImage),
40
+ author: (meta.authors?.length ? meta.authors : [site.name]).map((name) => ({
41
+ "@type": "Person",
42
+ name
43
+ })),
44
+ publisher: {
45
+ "@type": "Organization",
46
+ name: site.name,
47
+ url: homeUrl(site)
48
+ },
49
+ ...meta.tags?.length ? { keywords: meta.tags.join(", ") } : {}
50
+ };
51
+ }
52
+ function breadcrumbJsonLd(site, slug, title) {
53
+ return {
54
+ "@context": "https://schema.org",
55
+ "@type": "BreadcrumbList",
56
+ itemListElement: [
57
+ {
58
+ "@type": "ListItem",
59
+ position: 1,
60
+ name: site.name,
61
+ item: homeUrl(site)
62
+ },
63
+ { "@type": "ListItem", position: 2, name: "Blog", item: blogUrl(site) },
64
+ {
65
+ "@type": "ListItem",
66
+ position: 3,
67
+ name: title,
68
+ item: postUrl(site, slug)
69
+ }
70
+ ]
71
+ };
72
+ }
73
+
74
+ // src/content-source.ts
75
+ import { z } from "zod";
76
+ var DEFAULT_TIMEOUT_MS = 1e4;
77
+ var postSchema = z.object({
78
+ slug: z.string(),
79
+ meta: postMetaSchema,
80
+ html: z.string()
81
+ });
82
+ var listSchema = z.object({
83
+ posts: z.array(z.object({ slug: z.string(), meta: postMetaSchema }))
84
+ });
85
+ function createHttpContentSource({
86
+ apiBase,
87
+ site,
88
+ timeoutMs = DEFAULT_TIMEOUT_MS
89
+ }) {
90
+ const siteUrl = `${apiBase.replace(/\/+$/, "")}/content-sites/${encodeURIComponent(site)}`;
91
+ async function fetchJson(path) {
92
+ const response = await fetch(`${siteUrl}${path}`, {
93
+ headers: { Accept: "application/json" },
94
+ signal: AbortSignal.timeout(timeoutMs)
95
+ });
96
+ if (response.status === 404) return null;
97
+ if (!response.ok) {
98
+ throw new Error(`[blog] backend ${path} answered ${response.status}`);
99
+ }
100
+ return response.json();
101
+ }
102
+ return {
103
+ async listPosts() {
104
+ const body = await fetchJson("/posts");
105
+ if (body === null) return [];
106
+ return listSchema.parse(body).posts;
107
+ },
108
+ async getPost(slug) {
109
+ if (!SLUG_PATTERN.test(slug)) return null;
110
+ const body = await fetchJson(`/posts/${slug}`);
111
+ if (body === null) return null;
112
+ const { meta, html } = postSchema.parse(body);
113
+ return { slug, meta, html };
114
+ }
115
+ };
116
+ }
117
+
118
+ export {
119
+ blogUrl,
120
+ postUrl,
121
+ feedUrl,
122
+ homeUrl,
123
+ jsonLd,
124
+ absoluteImage,
125
+ blogPostingJsonLd,
126
+ breadcrumbJsonLd,
127
+ createHttpContentSource
128
+ };
@@ -0,0 +1,90 @@
1
+ import {
2
+ sanitizeNodes
3
+ } from "./chunk-EICE5ONB.js";
4
+ import {
5
+ COMPONENTS,
6
+ PROPS_ATTRIBUTE,
7
+ isComponentTag
8
+ } from "./chunk-XLJBCIXI.js";
9
+
10
+ // src/react/components.tsx
11
+ import { jsx, jsxs } from "react/jsx-runtime";
12
+ var CALLOUT_LABELS = {
13
+ info: "Note",
14
+ warn: "Warning",
15
+ tip: "Tip"
16
+ };
17
+ function CpCallout({ type, children, ...rest }) {
18
+ return /* @__PURE__ */ jsxs(
19
+ "aside",
20
+ {
21
+ ...rest,
22
+ role: "note",
23
+ className: `not-prose cp-callout cp-callout--${type}`,
24
+ children: [
25
+ /* @__PURE__ */ jsx("p", { className: "cp-callout__label", children: CALLOUT_LABELS[type] }),
26
+ children
27
+ ]
28
+ }
29
+ );
30
+ }
31
+ function CpFigure({
32
+ src,
33
+ alt,
34
+ caption,
35
+ width,
36
+ height,
37
+ ...rest
38
+ }) {
39
+ return /* @__PURE__ */ jsxs("figure", { ...rest, className: "cp-figure", children: [
40
+ /* @__PURE__ */ jsx(
41
+ "img",
42
+ {
43
+ src,
44
+ alt,
45
+ width,
46
+ height,
47
+ loading: "lazy",
48
+ className: "cp-figure__img"
49
+ }
50
+ ),
51
+ caption ? /* @__PURE__ */ jsx("figcaption", { className: "cp-figure__caption", children: caption }) : null
52
+ ] });
53
+ }
54
+ var COMPONENT_VIEWS = {
55
+ "cp-callout": CpCallout,
56
+ "cp-figure": CpFigure
57
+ };
58
+
59
+ // src/react/render.tsx
60
+ import {
61
+ domToReact
62
+ } from "html-react-parser";
63
+ import { createElement } from "react";
64
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
65
+ var options = {
66
+ replace(node) {
67
+ if (node.type !== "tag" || !isComponentTag(node.name)) return void 0;
68
+ const definition = COMPONENTS[node.name];
69
+ const parsed = definition.props.safeParse(
70
+ JSON.parse(node.attribs[PROPS_ATTRIBUTE] ?? "{}")
71
+ );
72
+ if (!parsed.success) return /* @__PURE__ */ jsx2(Fragment, {});
73
+ const blockId = node.attribs["data-cp-id"];
74
+ return createElement(
75
+ COMPONENT_VIEWS[node.name],
76
+ { ...parsed.data, ...blockId ? { "data-cp-id": blockId } : {} },
77
+ definition.content === "none" ? void 0 : domToReact(node.children, options)
78
+ );
79
+ }
80
+ };
81
+ function renderPostBody(html) {
82
+ return domToReact(sanitizeNodes(html).nodes, options);
83
+ }
84
+
85
+ export {
86
+ CpCallout,
87
+ CpFigure,
88
+ COMPONENT_VIEWS,
89
+ renderPostBody
90
+ };
@@ -0,0 +1,197 @@
1
+ import {
2
+ BLOCK_ID_ATTRIBUTE,
3
+ COMPONENTS,
4
+ ELEMENTS,
5
+ PROPS_ATTRIBUTE,
6
+ URL_SCHEMES,
7
+ isComponentTag,
8
+ isElementTag
9
+ } from "./chunk-XLJBCIXI.js";
10
+
11
+ // src/sanitize.ts
12
+ import { Element, Text } from "domhandler";
13
+ import render from "dom-serializer";
14
+ import { parseDocument } from "htmlparser2";
15
+ var DROP_WITH_CONTENT = /* @__PURE__ */ new Set([
16
+ "script",
17
+ "style",
18
+ "iframe",
19
+ "frame",
20
+ "frameset",
21
+ "object",
22
+ "embed",
23
+ "applet",
24
+ "noscript",
25
+ "template",
26
+ "svg",
27
+ "math",
28
+ "textarea",
29
+ "select",
30
+ "title",
31
+ "head",
32
+ "audio",
33
+ "video"
34
+ ]);
35
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set(["br", "hr", "img"]);
36
+ var MAX_DEPTH = 64;
37
+ var HAS_CONTROL_CHARACTERS = /[\u0000- \u007f-\u009f]/;
38
+ var SCHEME = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;
39
+ function sanitizeUrl(value) {
40
+ const url = value.trim();
41
+ if (!url || HAS_CONTROL_CHARACTERS.test(url)) return null;
42
+ const scheme = SCHEME.exec(url)?.[1]?.toLowerCase();
43
+ if (scheme) {
44
+ return URL_SCHEMES.includes(scheme) ? url : null;
45
+ }
46
+ if (url.startsWith("#")) return url;
47
+ if (url.startsWith("/") && !/^\/[/\\]/.test(url)) return url;
48
+ return null;
49
+ }
50
+ var ATTRIBUTE_VALUE_PATTERNS = {
51
+ [BLOCK_ID_ATTRIBUTE]: /^[A-Za-z0-9_-]{1,64}$/,
52
+ id: /^[A-Za-z][\w-]{0,99}$/,
53
+ target: /^(_blank|_self)$/,
54
+ loading: /^(lazy|eager)$/,
55
+ width: /^\d{1,5}$/,
56
+ height: /^\d{1,5}$/,
57
+ rel: /^[a-z]+(\s+[a-z]+)*$/i
58
+ };
59
+ function cleanAttributes(tag, attribs, issues) {
60
+ const definition = ELEMENTS[tag];
61
+ const allowed = new Set(definition?.attributes ?? []);
62
+ const urlAttributes = new Set(definition?.urlAttributes ?? []);
63
+ const cleaned = {};
64
+ for (const [name, value] of Object.entries(attribs)) {
65
+ if (!allowed.has(name)) {
66
+ issues.push({
67
+ kind: "attribute",
68
+ tag,
69
+ name,
70
+ message: `<${tag}> does not allow the attribute "${name}"`
71
+ });
72
+ continue;
73
+ }
74
+ if (urlAttributes.has(name)) {
75
+ const url = sanitizeUrl(value);
76
+ if (url === null) {
77
+ issues.push({
78
+ kind: "url",
79
+ tag,
80
+ name,
81
+ message: `<${tag} ${name}> has a URL that is not allowed`
82
+ });
83
+ continue;
84
+ }
85
+ cleaned[name] = url;
86
+ continue;
87
+ }
88
+ const pattern = ATTRIBUTE_VALUE_PATTERNS[name];
89
+ if (pattern && !pattern.test(value)) {
90
+ issues.push({
91
+ kind: "attribute",
92
+ tag,
93
+ name,
94
+ message: `<${tag} ${name}> has an invalid value`
95
+ });
96
+ continue;
97
+ }
98
+ cleaned[name] = value;
99
+ }
100
+ if (tag === "a" && cleaned.target === "_blank") {
101
+ cleaned.rel = "noopener noreferrer";
102
+ }
103
+ return cleaned;
104
+ }
105
+ function sanitizeChildren(children, issues, depth) {
106
+ return children.flatMap((child) => sanitizeNode(child, issues, depth));
107
+ }
108
+ function sanitizeComponent(element, issues, depth) {
109
+ const tag = element.name;
110
+ const definition = COMPONENTS[tag];
111
+ const attribs = {};
112
+ for (const [name, value] of Object.entries(element.attribs)) {
113
+ if (!definition.attributes.includes(name)) {
114
+ issues.push({
115
+ kind: "attribute",
116
+ tag,
117
+ name,
118
+ message: `<${tag}> does not allow the attribute "${name}"`
119
+ });
120
+ } else if (name !== PROPS_ATTRIBUTE) {
121
+ const valid = ATTRIBUTE_VALUE_PATTERNS[name]?.test(value) ?? true;
122
+ if (valid) attribs[name] = value;
123
+ }
124
+ }
125
+ let raw;
126
+ try {
127
+ raw = JSON.parse(element.attribs[PROPS_ATTRIBUTE] ?? "");
128
+ } catch {
129
+ raw = void 0;
130
+ }
131
+ const parsed = definition.props.safeParse(raw);
132
+ if (!parsed.success) {
133
+ issues.push({
134
+ kind: "props",
135
+ tag,
136
+ name: PROPS_ATTRIBUTE,
137
+ message: `<${tag}> has invalid ${PROPS_ATTRIBUTE}: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"} ${issue.message}`).join("; ")}`
138
+ });
139
+ return [];
140
+ }
141
+ attribs[PROPS_ATTRIBUTE] = JSON.stringify(parsed.data);
142
+ const children = definition.content === "none" ? [] : sanitizeChildren(element.children, issues, depth + 1);
143
+ return [new Element(tag, attribs, children)];
144
+ }
145
+ function sanitizeNode(node, issues, depth) {
146
+ if (node.type === "text") return [new Text(node.data)];
147
+ if (!(node instanceof Element)) return [];
148
+ const tag = node.name;
149
+ if (depth > MAX_DEPTH) {
150
+ issues.push({ kind: "element", tag, message: "nested too deeply" });
151
+ return [];
152
+ }
153
+ if (DROP_WITH_CONTENT.has(tag)) {
154
+ issues.push({
155
+ kind: "element",
156
+ tag,
157
+ message: `<${tag}> is not allowed and was removed with its content`
158
+ });
159
+ return [];
160
+ }
161
+ if (isComponentTag(tag)) return sanitizeComponent(node, issues, depth);
162
+ if (tag.startsWith("cp-")) {
163
+ issues.push({
164
+ kind: "element",
165
+ tag,
166
+ message: `<${tag}> is not a registered component`
167
+ });
168
+ return [];
169
+ }
170
+ if (!isElementTag(tag)) {
171
+ issues.push({
172
+ kind: "element",
173
+ tag,
174
+ message: `<${tag}> is not allowed; its children were kept`
175
+ });
176
+ return sanitizeChildren(node.children, issues, depth + 1);
177
+ }
178
+ const attribs = cleanAttributes(tag, node.attribs, issues);
179
+ if (tag === "img" && !attribs.src) return [];
180
+ const children = VOID_ELEMENTS.has(tag) ? [] : sanitizeChildren(node.children, issues, depth + 1);
181
+ return [new Element(tag, attribs, children)];
182
+ }
183
+ function sanitizeNodes(html) {
184
+ const issues = [];
185
+ const nodes = sanitizeChildren(parseDocument(html).children, issues, 0);
186
+ return { nodes, issues };
187
+ }
188
+ function sanitizeHtml(html) {
189
+ return render(sanitizeNodes(html).nodes);
190
+ }
191
+
192
+ export {
193
+ DROP_WITH_CONTENT,
194
+ sanitizeUrl,
195
+ sanitizeNodes,
196
+ sanitizeHtml
197
+ };
@@ -0,0 +1,48 @@
1
+ import {
2
+ COMPONENTS,
3
+ ELEMENTS,
4
+ URL_SCHEMES,
5
+ postMetaSchema
6
+ } from "./chunk-XLJBCIXI.js";
7
+
8
+ // src/schema.ts
9
+ import { z } from "zod";
10
+ var schemaVersion = 1;
11
+ function toJsonSchema(schema) {
12
+ const { $schema: _draft, ...jsonSchema } = z.toJSONSchema(schema, {
13
+ target: "draft-7"
14
+ });
15
+ return jsonSchema;
16
+ }
17
+ function buildContentSchema() {
18
+ return {
19
+ version: schemaVersion,
20
+ elements: Object.fromEntries(
21
+ Object.entries(ELEMENTS).map(([tag, definition]) => [
22
+ tag,
23
+ {
24
+ attributes: [...definition.attributes],
25
+ ...definition.urlAttributes ? { urlAttributes: [...definition.urlAttributes] } : {}
26
+ }
27
+ ])
28
+ ),
29
+ components: Object.fromEntries(
30
+ Object.entries(COMPONENTS).map(([tag, definition]) => [
31
+ tag,
32
+ {
33
+ attributes: [...definition.attributes],
34
+ content: definition.content,
35
+ props: toJsonSchema(definition.props)
36
+ }
37
+ ])
38
+ ),
39
+ urlSchemes: [...URL_SCHEMES],
40
+ relativeUrls: true,
41
+ meta: toJsonSchema(postMetaSchema)
42
+ };
43
+ }
44
+
45
+ export {
46
+ schemaVersion,
47
+ buildContentSchema
48
+ };
File without changes
@@ -0,0 +1,68 @@
1
+ import {
2
+ blogUrl,
3
+ feedUrl,
4
+ postUrl
5
+ } from "./chunk-AEQDRBA3.js";
6
+
7
+ // src/xml.ts
8
+ function escapeXml(value) {
9
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
10
+ }
11
+
12
+ // src/feed.ts
13
+ function feedXml(site, posts) {
14
+ const items = posts.map(({ slug, meta }) => {
15
+ const categories = (meta.tags ?? []).map((tag) => ` <category>${escapeXml(tag)}</category>`).join("\n");
16
+ return ` <item>
17
+ <title>${escapeXml(meta.title)}</title>
18
+ <link>${escapeXml(postUrl(site, slug))}</link>
19
+ <guid isPermaLink="true">${escapeXml(postUrl(site, slug))}</guid>
20
+ <pubDate>${(/* @__PURE__ */ new Date(`${meta.date}T00:00:00Z`)).toUTCString()}</pubDate>
21
+ <description>${escapeXml(meta.description)}</description>${categories ? `
22
+ ${categories}` : ""}
23
+ </item>`;
24
+ }).join("\n");
25
+ const newest = posts[0]?.meta;
26
+ return `<?xml version="1.0" encoding="UTF-8"?>
27
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
28
+ <channel>
29
+ <title>${escapeXml(site.title)}</title>
30
+ <link>${escapeXml(blogUrl(site))}</link>
31
+ <description>${escapeXml(site.description)}</description>
32
+ <language>${escapeXml(site.language ?? "en")}</language>
33
+ <atom:link href="${escapeXml(feedUrl(site))}" rel="self" type="application/rss+xml" />${newest ? `
34
+ <lastBuildDate>${(/* @__PURE__ */ new Date(`${newest.updated ?? newest.date}T00:00:00Z`)).toUTCString()}</lastBuildDate>` : ""}
35
+ ${items}
36
+ </channel>
37
+ </rss>
38
+ `;
39
+ }
40
+
41
+ // src/sitemap.ts
42
+ function sitemapXml(site, posts) {
43
+ const newest = posts[0]?.meta;
44
+ const entries = [
45
+ {
46
+ loc: blogUrl(site),
47
+ lastmod: newest ? newest.updated ?? newest.date : void 0
48
+ },
49
+ ...posts.map(({ slug, meta }) => ({
50
+ loc: postUrl(site, slug),
51
+ lastmod: meta.updated ?? meta.date
52
+ }))
53
+ ];
54
+ const urls = entries.map(
55
+ ({ loc, lastmod }) => ` <url><loc>${escapeXml(loc)}</loc>${lastmod ? `<lastmod>${lastmod}</lastmod>` : ""}</url>`
56
+ ).join("\n");
57
+ return `<?xml version="1.0" encoding="UTF-8"?>
58
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
59
+ ${urls}
60
+ </urlset>
61
+ `;
62
+ }
63
+
64
+ export {
65
+ escapeXml,
66
+ feedXml,
67
+ sitemapXml
68
+ };