@soloworks/smking-next 0.19.0 → 0.20.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 +27 -0
- package/package.json +1 -1
- package/src/components/smking-aeo.tsx +1 -14
- package/src/components/smking-cms.tsx +12 -0
- package/src/lib/safe-json.ts +21 -0
- package/src/types.ts +7 -54
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @soloworks/smking-next
|
|
2
2
|
|
|
3
|
+
## 0.20.1 — 2026-06-03
|
|
4
|
+
|
|
5
|
+
**Chore: removed dead carousel / nav-recent-posts / nav-search block types.**
|
|
6
|
+
|
|
7
|
+
Dropped `NavRecentPostsProps`, `NavSearchIndexEntry`, `NavSearchProps`,
|
|
8
|
+
`CarouselSlideProps`, `CarouselAspectRatio`, and `CarouselProps` from
|
|
9
|
+
`types.ts`. Narrowed the `Block` union to the three active block components
|
|
10
|
+
(`hero`, `article`, `nav-taxonomy-list`). `NavLayout` (layout enum including
|
|
11
|
+
`"carousel"`) is unchanged — that value controls nav rendering style, not a
|
|
12
|
+
separate block type.
|
|
13
|
+
|
|
14
|
+
## 0.20.0 — 2026-06-02
|
|
15
|
+
|
|
16
|
+
**`<SmkingCms>` now emits the structured-data engine's JSON-LD `@graph` inline.**
|
|
17
|
+
|
|
18
|
+
CMS pages get a full multi-entity `@graph` (BlogPosting / Organization /
|
|
19
|
+
WebSite / BreadcrumbList / FAQPage, `@id`-linked) generated by the smking
|
|
20
|
+
structured-data engine and served via `/api/v1/public/page`. `<SmkingCms>`
|
|
21
|
+
inlines it as `<script type="application/ld+json">` — JSON-LD is valid in
|
|
22
|
+
`<body>`, so no head hoisting needed (unlike `<title>`/`<meta>`, which
|
|
23
|
+
React 19 still hoists for you). The `safeJson` escaper (`</` + U+2028/U+2029)
|
|
24
|
+
is extracted into a shared `lib/safe-json.ts` that `<SmkingAEO>` imports too.
|
|
25
|
+
|
|
26
|
+
No customer code change — `<SmkingCms>` picks up the new `jsonLd` field
|
|
27
|
+
automatically once a page is republished under the engine. Bump your
|
|
28
|
+
constraint to `^0.20`.
|
|
29
|
+
|
|
3
30
|
## 0.19.0 — 2026-05-29
|
|
4
31
|
|
|
5
32
|
**CMS base path reported via heartbeat — dashboard "View page" resolves real URLs without per-page visits.**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soloworks/smking-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "AI-native SEO (AEO) for Next.js — auto-inject JSON-LD, FAQ, AI summary, and SEO metadata so AI crawlers (ChatGPT, Perplexity, Google AI) can cite your pages.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/sillyleo/smking/tree/main/packages/smking-next",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DiscoverParams } from "../types";
|
|
2
2
|
import { getAeoContent } from "../lib/client";
|
|
3
|
+
import { safeJson } from "../lib/safe-json";
|
|
3
4
|
|
|
4
5
|
const SR_ONLY_STYLE: React.CSSProperties = {
|
|
5
6
|
position: "absolute",
|
|
@@ -118,17 +119,3 @@ export async function SmkingAEO(props: SmkingAEOProps) {
|
|
|
118
119
|
</>
|
|
119
120
|
);
|
|
120
121
|
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Escape JSON for safe `<script>` embedding. Covers the three values
|
|
124
|
-
* that can break out:
|
|
125
|
-
* - `</` (any `</script>`-like sequence)
|
|
126
|
-
* - U+2028 / U+2029 (treated as line terminators inside a JS string
|
|
127
|
-
* literal, which would corrupt the embedded JSON)
|
|
128
|
-
*/
|
|
129
|
-
function safeJson(value: Record<string, unknown>): string {
|
|
130
|
-
return JSON.stringify(value)
|
|
131
|
-
.replace(/<\//g, "<\\/")
|
|
132
|
-
.replace(/\u2028/g, "\\u2028")
|
|
133
|
-
.replace(/\u2029/g, "\\u2029");
|
|
134
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getCmsPage } from "../lib/cms-client";
|
|
2
|
+
import { safeJson } from "../lib/safe-json";
|
|
2
3
|
import type { CmsParams } from "../types";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -72,6 +73,17 @@ export async function SmkingCms(props: CmsParams) {
|
|
|
72
73
|
<link rel="canonical" href={seo.canonicalUrl} data-smking="cms" />
|
|
73
74
|
)}
|
|
74
75
|
|
|
76
|
+
{/* Engine-precomputed @graph. JSON-LD is valid in <body>, so no
|
|
77
|
+
head hoisting needed (unlike title/meta above). */}
|
|
78
|
+
{data.page.jsonLd && (
|
|
79
|
+
<script
|
|
80
|
+
type="application/ld+json"
|
|
81
|
+
data-smking="cms"
|
|
82
|
+
// eslint-disable-next-line react/no-danger -- escaped via safeJson; engine output is schema-bounded
|
|
83
|
+
dangerouslySetInnerHTML={{ __html: safeJson(data.page.jsonLd) }}
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
|
|
75
87
|
{/* Raw passthrough — bodyHtml is SaaS-emitted Plate output (Tailwind
|
|
76
88
|
utility + slate-* hooks + data-slate-* attrs all intact). No
|
|
77
89
|
article shell, no canonical CSS injection: what the dashboard
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape an object for safe `<script type="application/ld+json">`
|
|
3
|
+
* embedding. Covers the sequences that can break out of / corrupt the
|
|
4
|
+
* script:
|
|
5
|
+
* - `</` (any `</script>`-like sequence)
|
|
6
|
+
* - U+2028 / U+2029 (JS string-literal line terminators)
|
|
7
|
+
*
|
|
8
|
+
* The U+2028 / U+2029 patterns are built via `new RegExp("\\u2028", ...)`
|
|
9
|
+
* (escaped-backslash strings) so this source file stays pure ASCII — no
|
|
10
|
+
* invisible line-separator characters to corrupt on edit.
|
|
11
|
+
*
|
|
12
|
+
* Shared by `<SmkingAEO>` and `<SmkingCms>`. Engine output is
|
|
13
|
+
* schema-bounded, but this guard is mandatory defence-in-depth — never
|
|
14
|
+
* inline JSON-LD without it.
|
|
15
|
+
*/
|
|
16
|
+
export function safeJson(value: Record<string, unknown>): string {
|
|
17
|
+
return JSON.stringify(value)
|
|
18
|
+
.replace(/<\//g, "<\\/")
|
|
19
|
+
.replace(new RegExp("\\u2028", "g"), "\\u2028")
|
|
20
|
+
.replace(new RegExp("\\u2029", "g"), "\\u2029");
|
|
21
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -102,13 +102,6 @@ export interface NavSnapshotEntry {
|
|
|
102
102
|
contentType: "article" | "landing" | "listing" | null;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
export interface NavRecentPostsProps extends ModuleHeader {
|
|
106
|
-
limit: number;
|
|
107
|
-
layout: NavLayout;
|
|
108
|
-
contentType?: "article" | "all";
|
|
109
|
-
/** Publish-time materialized — never fetched live. */
|
|
110
|
-
snapshot?: NavSnapshotEntry[];
|
|
111
|
-
}
|
|
112
105
|
export interface NavTaxonomyListProps extends ModuleHeader {
|
|
113
106
|
/** category-by-path: slug prefix; tag: taxonomies.slug */
|
|
114
107
|
source: "category-by-path" | "tag";
|
|
@@ -119,56 +112,10 @@ export interface NavTaxonomyListProps extends ModuleHeader {
|
|
|
119
112
|
snapshot?: NavSnapshotEntry[];
|
|
120
113
|
}
|
|
121
114
|
|
|
122
|
-
/**
|
|
123
|
-
* Search index entry. Customer SDK does client-side fuzzy filter
|
|
124
|
-
* against this list; clicking a result navigates to the slug.
|
|
125
|
-
*/
|
|
126
|
-
export interface NavSearchIndexEntry {
|
|
127
|
-
slug: string;
|
|
128
|
-
title: string | null;
|
|
129
|
-
excerpt: string | null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export interface NavSearchProps {
|
|
133
|
-
placeholder: string;
|
|
134
|
-
contentType?: "article" | "all";
|
|
135
|
-
/** Publish-time materialized full-site search index. */
|
|
136
|
-
index?: NavSearchIndexEntry[];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export interface CarouselSlideProps {
|
|
140
|
-
imageUrl: string;
|
|
141
|
-
caption: string;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export type CarouselAspectRatio =
|
|
145
|
-
| "16:9"
|
|
146
|
-
| "4:3"
|
|
147
|
-
| "1:1"
|
|
148
|
-
| "3:4"
|
|
149
|
-
| "9:16";
|
|
150
|
-
|
|
151
|
-
export interface CarouselProps {
|
|
152
|
-
slides: CarouselSlideProps[];
|
|
153
|
-
/** Fixed aspect ratio for all slides — image fills via `object-fit: cover`. Default `16:9`. */
|
|
154
|
-
aspectRatio?: CarouselAspectRatio;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
115
|
export type Block =
|
|
158
116
|
| { component: "hero"; id: string; props: HeroProps }
|
|
159
117
|
| { component: "article"; id: string; props: ArticleProps }
|
|
160
|
-
| { component: "
|
|
161
|
-
| {
|
|
162
|
-
component: "nav-recent-posts";
|
|
163
|
-
id: string;
|
|
164
|
-
props: NavRecentPostsProps;
|
|
165
|
-
}
|
|
166
|
-
| {
|
|
167
|
-
component: "nav-taxonomy-list";
|
|
168
|
-
id: string;
|
|
169
|
-
props: NavTaxonomyListProps;
|
|
170
|
-
}
|
|
171
|
-
| { component: "nav-search"; id: string; props: NavSearchProps };
|
|
118
|
+
| { component: "nav-taxonomy-list"; id: string; props: NavTaxonomyListProps };
|
|
172
119
|
|
|
173
120
|
/**
|
|
174
121
|
* A single published CMS page returned by the smking public API
|
|
@@ -191,6 +138,12 @@ export interface CmsPage {
|
|
|
191
138
|
excerpt?: string | null;
|
|
192
139
|
featuredImageUrl?: string | null;
|
|
193
140
|
publishedAt: string | null;
|
|
141
|
+
/**
|
|
142
|
+
* v0.M1+ engine-precomputed schema.org @graph. `<SmkingCms>` inlines it
|
|
143
|
+
* as `<script type="application/ld+json">`. Null until the page is
|
|
144
|
+
* republished under the structured-data engine.
|
|
145
|
+
*/
|
|
146
|
+
jsonLd?: Record<string, unknown> | null;
|
|
194
147
|
}
|
|
195
148
|
|
|
196
149
|
/**
|