@soloworks/smking-next 0.21.1 → 0.21.3
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 +18 -0
- package/bin/install.mjs +0 -0
- package/package.json +7 -7
- package/src/cms-blocks.ts +318 -23
- package/src/components/smking-aeo.tsx +16 -0
- package/src/index.ts +3 -1
- package/src/lib/version.ts +1 -1
- package/src/types.ts +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @soloworks/smking-next
|
|
2
2
|
|
|
3
|
+
## 0.21.3 — 2026-07-03
|
|
4
|
+
|
|
5
|
+
**AEO original-vs-enhanced audits can now fetch the true host HTML.**
|
|
6
|
+
|
|
7
|
+
`<SmkingAEO>` now honors `x-smking-origin-mode: raw` by returning `null`
|
|
8
|
+
before calling the SaaS. This lets Page Zero fetch a customer page's real
|
|
9
|
+
pre-injection HTML for source-change detection, then compare it against the
|
|
10
|
+
served enhanced version. No customer code change; customers already on `^0.21`
|
|
11
|
+
receive it on the next install/update.
|
|
12
|
+
|
|
13
|
+
## 0.21.2 — 2026-07-02
|
|
14
|
+
|
|
15
|
+
**CMS taxonomy page types caught up with the SaaS catalog (types only — no runtime change).**
|
|
16
|
+
|
|
17
|
+
The CMS page model now includes the `taxonomy` content type used by protected
|
|
18
|
+
category and tag index pages. This keeps the published SDK type surface aligned
|
|
19
|
+
with the SaaS block contract after the taxonomy-page normalization work.
|
|
20
|
+
|
|
3
21
|
## 0.21.1 — 2026-06-12
|
|
4
22
|
|
|
5
23
|
**Webhook replay protection (security review M6).**
|
package/bin/install.mjs
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soloworks/smking-next",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.3",
|
|
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",
|
|
@@ -65,6 +65,11 @@
|
|
|
65
65
|
"perplexity",
|
|
66
66
|
"smking"
|
|
67
67
|
],
|
|
68
|
+
"scripts": {
|
|
69
|
+
"typecheck": "tsc --noEmit",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"test:watch": "vitest"
|
|
72
|
+
},
|
|
68
73
|
"peerDependencies": {
|
|
69
74
|
"next": "^15.0.0 || ^16.0.0",
|
|
70
75
|
"react": "^18.0.0 || ^19.0.0"
|
|
@@ -86,10 +91,5 @@
|
|
|
86
91
|
"react-dom": "19.2.4",
|
|
87
92
|
"typescript": "^5",
|
|
88
93
|
"vitest": "^4.1.5"
|
|
89
|
-
},
|
|
90
|
-
"scripts": {
|
|
91
|
-
"typecheck": "tsc --noEmit",
|
|
92
|
-
"test": "vitest run",
|
|
93
|
-
"test:watch": "vitest"
|
|
94
94
|
}
|
|
95
|
-
}
|
|
95
|
+
}
|
package/src/cms-blocks.ts
CHANGED
|
@@ -27,17 +27,61 @@
|
|
|
27
27
|
* slug-prefix derived, tag mode targets `taxonomies.slug`.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
+
export interface YouTubeEmbedOptions {
|
|
31
|
+
autoplay?: boolean;
|
|
32
|
+
ccLangPref?: string;
|
|
33
|
+
ccLoadPolicy?: boolean;
|
|
34
|
+
color?: "red" | "white";
|
|
35
|
+
controls?: boolean;
|
|
36
|
+
disablekb?: boolean;
|
|
37
|
+
enablejsapi?: boolean;
|
|
38
|
+
end?: number;
|
|
39
|
+
fs?: boolean;
|
|
40
|
+
hl?: string;
|
|
41
|
+
ivLoadPolicy?: "show" | "hide";
|
|
42
|
+
list?: string;
|
|
43
|
+
listType?: "playlist" | "user_uploads";
|
|
44
|
+
loop?: boolean;
|
|
45
|
+
origin?: string;
|
|
46
|
+
playlist?: string;
|
|
47
|
+
playsinline?: boolean;
|
|
48
|
+
relatedMode?: "all" | "same-channel";
|
|
49
|
+
start?: number;
|
|
50
|
+
widgetReferrer?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface HeroYouTubeProps extends YouTubeEmbedOptions {
|
|
54
|
+
url: string;
|
|
55
|
+
videoId: string;
|
|
56
|
+
thumbnailUrl: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
30
59
|
export interface HeroProps {
|
|
31
60
|
title: string;
|
|
61
|
+
/** Generic visible meta lines. Legacy drafts may still also carry subtitle. */
|
|
62
|
+
line1?: string;
|
|
63
|
+
line2?: string;
|
|
64
|
+
/** When true, render the backend page publish date. */
|
|
65
|
+
showPublishedAt?: boolean;
|
|
66
|
+
/** ISO or display date injected from page metadata at publish time. */
|
|
67
|
+
publishedAt?: string;
|
|
68
|
+
/** Legacy single-line fallback for SDK Mode B consumers. */
|
|
32
69
|
subtitle?: string;
|
|
33
70
|
image?: { url: string; alt: string };
|
|
71
|
+
/** YouTube hero media. Article body renders the embed; cards / metadata use
|
|
72
|
+
* the thumbnail so list-like pages never mount a player. */
|
|
73
|
+
youtube?: HeroYouTubeProps;
|
|
34
74
|
cta?: { label: string; href: string };
|
|
35
75
|
/** Header layout — omitted = the original centred hero; "start" = the
|
|
36
|
-
* Apple-newsroom-style left-aligned header; "cover" = title-only banner
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
|
|
76
|
+
* Apple-newsroom-style left-aligned header; "cover" = title-only banner over
|
|
77
|
+
* a full-bleed background image with a dark scrim (Apple services index);
|
|
78
|
+
* "cover-plain" = the same banner WITHOUT the scrim (image shown untouched).
|
|
79
|
+
* Visuals ship via bodyHtml; this mirrors the node attr for SDK Mode B
|
|
80
|
+
* (per-block) consumers. */
|
|
81
|
+
align?: "center" | "start" | "cover" | "cover-plain";
|
|
82
|
+
/** Crop ratio for cover heroes and the optional hero image.
|
|
83
|
+
* Omitted = auto, preserving the original image ratio. */
|
|
84
|
+
aspectRatio?: "auto" | "16:9" | "4:3";
|
|
41
85
|
}
|
|
42
86
|
|
|
43
87
|
export interface ArticleProps {
|
|
@@ -72,7 +116,26 @@ export interface NavSnapshotEntry {
|
|
|
72
116
|
featuredImageUrl: string | null;
|
|
73
117
|
/** ISO string. */
|
|
74
118
|
publishedAt: string | null;
|
|
75
|
-
|
|
119
|
+
/** ISO string. Present when the article is manually pinned in CMS feeds. */
|
|
120
|
+
pinnedAt?: string | null;
|
|
121
|
+
contentType: "article" | "landing" | "listing" | "taxonomy" | null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type SearchQuickLinkSource = "category-by-path" | "tag" | "url";
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Optional links shown beside the search trigger on desktop and inside the
|
|
128
|
+
* search panel on small screens. Category/tag links keep their semantic source
|
|
129
|
+
* + path so publish-time materialization can bake the customer's CMS mount
|
|
130
|
+
* prefix into `href`; URL links carry the author's literal `href`.
|
|
131
|
+
*/
|
|
132
|
+
export interface SearchQuickLink {
|
|
133
|
+
label: string;
|
|
134
|
+
source: SearchQuickLinkSource;
|
|
135
|
+
/** Category slug path or flat tag slug. Not used for arbitrary URLs. */
|
|
136
|
+
path?: string;
|
|
137
|
+
/** Materialized href for category/tag links, or the author-entered URL. */
|
|
138
|
+
href?: string;
|
|
76
139
|
}
|
|
77
140
|
|
|
78
141
|
/**
|
|
@@ -88,30 +151,80 @@ export interface SearchProps {
|
|
|
88
151
|
placeholder?: string;
|
|
89
152
|
/** Optional heading above the search input. */
|
|
90
153
|
heading?: string;
|
|
154
|
+
/** Content column vs wide breakout (default wide). Mirrors the media
|
|
155
|
+
* blocks (image/embed). */
|
|
156
|
+
widthMode?: "content" | "wide";
|
|
157
|
+
/** Quick-link cluster alignment beside the search trigger. Defaults left. */
|
|
158
|
+
linkAlign?: "left" | "right";
|
|
159
|
+
/** Quick-link visual treatment. Defaults badge. */
|
|
160
|
+
linkStyle?: "badge" | "plain";
|
|
161
|
+
/** Optional quick links adjacent to search. */
|
|
162
|
+
links?: SearchQuickLink[];
|
|
91
163
|
/** Publish-time materialized snapshot — every published page on the site. */
|
|
92
164
|
snapshot?: NavSnapshotEntry[];
|
|
93
165
|
}
|
|
94
166
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
167
|
+
/** One author-picked source for nav-related-posts. Same shape as
|
|
168
|
+
* `CategoryIndexItem` (a category path OR a tag), reused so the source picker
|
|
169
|
+
* UI stays identical across blocks. */
|
|
170
|
+
export interface RelatedPostsSource {
|
|
171
|
+
/** Slug prefix (category-by-path) or taxonomy slug (tag), per `source`. */
|
|
172
|
+
path: string;
|
|
173
|
+
/** Display name (only used by the editor chip; resolver ignores it). */
|
|
174
|
+
label: string;
|
|
175
|
+
/** Omitted = `category-by-path` (back-compat, mirrors CategoryIndexItem). */
|
|
176
|
+
source?: "category-by-path" | "tag";
|
|
100
177
|
}
|
|
101
178
|
|
|
102
179
|
export interface RelatedPostsProps extends ModuleHeader {
|
|
180
|
+
/** Authoring mode before materialization. Published blocks carry resolved
|
|
181
|
+
* `heading`; SDK consumers can ignore this field. */
|
|
182
|
+
headingMode?: "global" | "custom";
|
|
103
183
|
/** Author-set number of related posts to show (1..50). */
|
|
104
184
|
limit: number;
|
|
105
|
-
/**
|
|
185
|
+
/** Content column vs wide breakout (default content). Mirrors the media
|
|
186
|
+
* blocks. */
|
|
187
|
+
widthMode?: "content" | "wide";
|
|
188
|
+
/** Author-picked sources. Empty/omitted → auto-fill from the CURRENT page's
|
|
189
|
+
* category, derived from its slug at publish time. When set, related posts
|
|
190
|
+
* are the latest across ALL picked sources (OR), newest-first, excluding the
|
|
191
|
+
* current page. */
|
|
192
|
+
sources?: RelatedPostsSource[];
|
|
193
|
+
/** Publish-time materialized snapshot. */
|
|
106
194
|
snapshot?: NavSnapshotEntry[];
|
|
107
195
|
}
|
|
108
196
|
|
|
109
|
-
/** One
|
|
197
|
+
/** One tag attached to the current article, materialized at publish time. */
|
|
198
|
+
export interface ArticleTagSnapshot {
|
|
199
|
+
slug: string;
|
|
200
|
+
name: string;
|
|
201
|
+
/** Virtual tag archive href, e.g. `/blog/tag/tofu-life`. */
|
|
202
|
+
href: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* `article-tags` block — renders the current article's CMS tags as chips.
|
|
207
|
+
* The tags are page metadata, not authored inside the block, so `snapshot`
|
|
208
|
+
* is filled at publish time from `content_taxonomies`.
|
|
209
|
+
*/
|
|
210
|
+
export interface ArticleTagsProps {
|
|
211
|
+
/** Optional section heading. Omitted / empty means no heading. */
|
|
212
|
+
heading?: string;
|
|
213
|
+
/** Publish-time materialized current-article tags. */
|
|
214
|
+
snapshot?: ArticleTagSnapshot[];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** One author-selected category OR tag in a nav-category-index block. */
|
|
110
218
|
export interface CategoryIndexItem {
|
|
111
|
-
/** Slug prefix
|
|
219
|
+
/** Slug prefix (category-by-path) or taxonomy slug (tag), per `source`.
|
|
220
|
+
* e.g. category `blog/seo`, tag `announcements`. */
|
|
112
221
|
path: string;
|
|
113
|
-
/** Display name on the card (slug leaf by default; author-editable). */
|
|
222
|
+
/** Display name on the card (slug leaf / tag name by default; author-editable). */
|
|
114
223
|
label: string;
|
|
224
|
+
/** Source discriminator (mirrors nav-taxonomy-list). Omitted =
|
|
225
|
+
* `category-by-path` — back-compat: pre-tag nodes carry no source. A `tag`
|
|
226
|
+
* card shows that tag's latest post + links to its archive. */
|
|
227
|
+
source?: "category-by-path" | "tag";
|
|
115
228
|
}
|
|
116
229
|
|
|
117
230
|
/**
|
|
@@ -126,15 +239,30 @@ export interface CategoryCardSnapshot {
|
|
|
126
239
|
}
|
|
127
240
|
|
|
128
241
|
export interface CategoryIndexProps extends ModuleHeader {
|
|
242
|
+
/** `auto` = listing categories directly under `autoBasePath` at publish
|
|
243
|
+
* time; `manual` = exactly the picked items below. Omitted = back-compat:
|
|
244
|
+
* items present means manual, otherwise auto. */
|
|
245
|
+
mode?: "auto" | "manual";
|
|
246
|
+
/** Auto mode source. Omitted = category hierarchy, for back-compat. */
|
|
247
|
+
autoSource?: "category-by-path" | "tag";
|
|
248
|
+
/** Auto mode source hierarchy. Empty string means root-level listing
|
|
249
|
+
* categories; any other value is a listing slug whose direct child listing
|
|
250
|
+
* pages should be shown. Omitted falls back to root for new materialization. */
|
|
251
|
+
autoBasePath?: string;
|
|
129
252
|
items: CategoryIndexItem[];
|
|
130
253
|
/** Card layout — omitted/"cards" = category cards (archive links);
|
|
131
254
|
* "featured" = Apple-newsroom services-index tiles where each card IS the
|
|
132
255
|
* category's latest article. Visuals ship via bodyHtml; this mirrors the
|
|
133
256
|
* node attr for SDK Mode B (per-block) consumers. */
|
|
134
257
|
layout?: "cards" | "featured";
|
|
258
|
+
/** Content column vs wide breakout. Omitted = layout-derived default
|
|
259
|
+
* (cards → content, featured → wide); set explicitly to override. */
|
|
260
|
+
widthMode?: "content" | "wide";
|
|
135
261
|
snapshot?: CategoryCardSnapshot[];
|
|
136
262
|
}
|
|
137
263
|
|
|
264
|
+
export type MediaAspectRatio = "auto" | "16:9" | "4:3" | "square";
|
|
265
|
+
|
|
138
266
|
/**
|
|
139
267
|
* `image` block — a single figure (image + optional caption). `widthMode`
|
|
140
268
|
* controls the breakout (content = text column, wide = up to 1024px centred
|
|
@@ -146,6 +274,10 @@ export interface ImageProps {
|
|
|
146
274
|
alt?: string;
|
|
147
275
|
caption?: string;
|
|
148
276
|
widthMode?: "content" | "wide";
|
|
277
|
+
/** Plain image vs soft neutral tonal surface. */
|
|
278
|
+
styleVariant?: "plain" | "tonal";
|
|
279
|
+
/** Omitted / auto preserves the original image ratio. Fixed values crop. */
|
|
280
|
+
aspectRatio?: MediaAspectRatio;
|
|
149
281
|
}
|
|
150
282
|
|
|
151
283
|
/** One slide in a `slideshow` block. */
|
|
@@ -168,17 +300,51 @@ export interface SlideshowProps {
|
|
|
168
300
|
aspectRatio?: "16:9" | "4:3";
|
|
169
301
|
}
|
|
170
302
|
|
|
303
|
+
/** One image in a `collage` block. Width/height are optional natural
|
|
304
|
+
* dimensions saved at authoring time so the auto layout can keep row
|
|
305
|
+
* proportions stable without loading images first. */
|
|
306
|
+
export interface CollageImageProps {
|
|
307
|
+
url: string;
|
|
308
|
+
alt?: string;
|
|
309
|
+
width?: number;
|
|
310
|
+
height?: number;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* `collage` block — a 1–6 image composition. `auto` uses natural image
|
|
315
|
+
* ratios and the wide breakout; `row-grid` keeps each row at one height while
|
|
316
|
+
* allowing different rows to have different proportions. Content width forces
|
|
317
|
+
* `row-grid` at render time.
|
|
318
|
+
*/
|
|
319
|
+
export interface CollageProps {
|
|
320
|
+
images?: CollageImageProps[];
|
|
321
|
+
widthMode?: "content" | "wide";
|
|
322
|
+
mode?: "auto" | "row-grid";
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export type SocialSharePlatform =
|
|
326
|
+
| "threads"
|
|
327
|
+
| "instagram"
|
|
328
|
+
| "facebook"
|
|
329
|
+
| "line"
|
|
330
|
+
| "copy";
|
|
331
|
+
|
|
171
332
|
/**
|
|
172
|
-
* `social-share` block —
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
333
|
+
* `social-share` block — Threads / Instagram / Facebook / LINE share links +
|
|
334
|
+
* copy-link.
|
|
335
|
+
* Platform visibility is site-wide; `shareUrl` is the optional no-JS fallback
|
|
336
|
+
* target (the `<smking-share>` web component overrides every link from
|
|
337
|
+
* window.location at runtime, so the live share always points at the real
|
|
338
|
+
* current page).
|
|
176
339
|
*/
|
|
177
340
|
export interface SocialShareProps {
|
|
178
341
|
shareUrl?: string;
|
|
179
|
-
/**
|
|
180
|
-
*
|
|
342
|
+
/** Global mode reads the site-wide share title. Custom mode reads `label`.
|
|
343
|
+
* Omitted = global; empty resolved title = bare icon row. */
|
|
344
|
+
headingMode?: "global" | "custom";
|
|
181
345
|
label?: string;
|
|
346
|
+
/** Site-wide platform visibility snapshot baked into published output. */
|
|
347
|
+
platforms?: SocialSharePlatform[];
|
|
182
348
|
}
|
|
183
349
|
|
|
184
350
|
export interface NavTaxonomyListProps extends ModuleHeader {
|
|
@@ -196,10 +362,134 @@ export interface NavTaxonomyListProps extends ModuleHeader {
|
|
|
196
362
|
* safety cap. Omitted (not false) when off — back-compat shape. */
|
|
197
363
|
showAll?: boolean;
|
|
198
364
|
layout: NavLayout;
|
|
365
|
+
/** Content column vs wide breakout (default content). Mirrors the media
|
|
366
|
+
* blocks. */
|
|
367
|
+
widthMode?: "content" | "wide";
|
|
199
368
|
/** Publish-time materialized snapshot. */
|
|
200
369
|
snapshot?: NavSnapshotEntry[];
|
|
201
370
|
}
|
|
202
371
|
|
|
372
|
+
/** One button in a `button-group` block. */
|
|
373
|
+
export interface ButtonItemProps {
|
|
374
|
+
label: string;
|
|
375
|
+
href: string;
|
|
376
|
+
/** Apple-style looks: `filled` = primary capsule, `tinted` = soft primary
|
|
377
|
+
* wash, `outline` = bordered. Omitted = filled. */
|
|
378
|
+
variant?: "filled" | "tinted" | "outline";
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* `button-group` block — a row of Apple-style capsule CTAs. Buttons wrap on
|
|
383
|
+
* narrow screens; `align` positions the row. Empty `buttons` is valid (an
|
|
384
|
+
* unconfigured block renders nothing on the customer side).
|
|
385
|
+
*/
|
|
386
|
+
export interface ButtonGroupProps {
|
|
387
|
+
buttons?: ButtonItemProps[];
|
|
388
|
+
align?: "start" | "center";
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* `embed` block — a privacy-friendly YouTube embed (youtube-nocookie
|
|
393
|
+
* iframe). `url` accepts any YouTube form (watch / youtu.be / shorts /
|
|
394
|
+
* embed); the video id is resolved at render time. No url / unrecognised
|
|
395
|
+
* url = renders nothing on the customer side. `widthMode` matches the other
|
|
396
|
+
* media blocks (content column vs wide breakout).
|
|
397
|
+
*/
|
|
398
|
+
export interface EmbedProps extends YouTubeEmbedOptions {
|
|
399
|
+
url?: string;
|
|
400
|
+
caption?: string;
|
|
401
|
+
widthMode?: "content" | "wide";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* `media-text` block — Apple-style feature row: image on one side, RICH
|
|
406
|
+
* EDITABLE text on the other. Unlike the attr-panel blocks, the text is
|
|
407
|
+
* real Plate content (the node's children — headings/paragraphs edited
|
|
408
|
+
* inline); `html` carries its serialized form for Mode B consumers, while
|
|
409
|
+
* Mode A ships the full markup inside bodyHtml as usual.
|
|
410
|
+
*/
|
|
411
|
+
export interface MediaTextProps {
|
|
412
|
+
imageUrl?: string;
|
|
413
|
+
imageAlt?: string;
|
|
414
|
+
/** Which side the IMAGE sits on (md+ screens; stacks on mobile). */
|
|
415
|
+
side?: "left" | "right";
|
|
416
|
+
/** Content column vs wide breakout (default wide). Mirrors the media
|
|
417
|
+
* blocks. */
|
|
418
|
+
widthMode?: "content" | "wide";
|
|
419
|
+
/** Plain row vs soft neutral tonal surface. */
|
|
420
|
+
styleVariant?: "plain" | "tonal";
|
|
421
|
+
/** Omitted / auto preserves the original image ratio. Fixed values crop. */
|
|
422
|
+
aspectRatio?: MediaAspectRatio;
|
|
423
|
+
/** Serialized text-column HTML (class-stripped, same treatment as
|
|
424
|
+
* `article`). */
|
|
425
|
+
html: string;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* One slot in a `latest-news` block — sourced independently from EITHER a
|
|
430
|
+
* specific article (by slug) or a category (shows that category's latest
|
|
431
|
+
* post). The discriminator mirrors `nav-taxonomy-list`'s source pattern.
|
|
432
|
+
*/
|
|
433
|
+
export type LatestNewsSlot =
|
|
434
|
+
| { kind: "article"; slug: string }
|
|
435
|
+
| {
|
|
436
|
+
kind: "category";
|
|
437
|
+
path: string;
|
|
438
|
+
label: string;
|
|
439
|
+
/** Source discriminator (mirrors nav-taxonomy-list). Omitted =
|
|
440
|
+
* `category-by-path` — back-compat. A `tag` slot shows the latest post
|
|
441
|
+
* carrying that tag. */
|
|
442
|
+
source?: "category-by-path" | "tag";
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/** Whole-block source mode for `latest-news`. Omitted = `manual` so old
|
|
446
|
+
* blocks keep their curated slots exactly. */
|
|
447
|
+
export type LatestNewsMode =
|
|
448
|
+
| "manual"
|
|
449
|
+
| "latest"
|
|
450
|
+
| "auto-category"
|
|
451
|
+
| "category"
|
|
452
|
+
| "tag";
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* One materialized `latest-news` card: the resolved article entry (the
|
|
456
|
+
* article itself for an `article` slot, or the category's latest post for a
|
|
457
|
+
* `category` slot) plus an optional eyebrow (the category label for category
|
|
458
|
+
* slots; null for article slots). A null `entry` = the slot's source
|
|
459
|
+
* resolved to nothing and is skipped at render.
|
|
460
|
+
*/
|
|
461
|
+
export interface LatestNewsCardSnapshot {
|
|
462
|
+
eyebrow: string | null;
|
|
463
|
+
entry: NavSnapshotEntry | null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* `latest-news` block — the Apple-newsroom "Latest News" magazine grid.
|
|
468
|
+
* 3–6 author-curated slots; the first is the large feature card, the rest
|
|
469
|
+
* scale down to medium / small tiles (6 = 1 feature + 2 medium + 3 small,
|
|
470
|
+
* down to 3 = 1 feature + 2 medium). Materialized at publish like the nav-*
|
|
471
|
+
* feeds; reuses `NavSnapshotEntry` for each resolved card.
|
|
472
|
+
*/
|
|
473
|
+
export interface LatestNewsProps extends ModuleHeader {
|
|
474
|
+
/** Whole-block source mode. Omitted/manual uses `slots`; other modes resolve
|
|
475
|
+
* cards dynamically at publish time. */
|
|
476
|
+
mode?: LatestNewsMode;
|
|
477
|
+
/** Author-curated slots, 3–6, in display order (first = feature card). */
|
|
478
|
+
slots: LatestNewsSlot[];
|
|
479
|
+
/** Card count for automatic modes. The magazine grid supports 3–6 cards. */
|
|
480
|
+
limit?: number;
|
|
481
|
+
/** Selected category/tag path for `category` / `tag` modes. */
|
|
482
|
+
sourcePath?: string;
|
|
483
|
+
/** Editor-facing label for the selected category/tag; used as the card
|
|
484
|
+
* eyebrow in automatic source modes. */
|
|
485
|
+
sourceLabel?: string;
|
|
486
|
+
/** Content column vs wide breakout (default wide). Mirrors the media
|
|
487
|
+
* blocks. */
|
|
488
|
+
widthMode?: "content" | "wide";
|
|
489
|
+
/** Publish-time materialized snapshot — one card per slot, same order. */
|
|
490
|
+
snapshot?: LatestNewsCardSnapshot[];
|
|
491
|
+
}
|
|
492
|
+
|
|
203
493
|
export type Block =
|
|
204
494
|
| { component: "hero"; id: string; props: HeroProps }
|
|
205
495
|
| { component: "article"; id: string; props: ArticleProps }
|
|
@@ -209,11 +499,16 @@ export type Block =
|
|
|
209
499
|
props: NavTaxonomyListProps;
|
|
210
500
|
}
|
|
211
501
|
| { component: "search"; id: string; props: SearchProps }
|
|
212
|
-
| { component: "nav-recent-posts"; id: string; props: RecentPostsProps }
|
|
213
502
|
| { component: "nav-related-posts"; id: string; props: RelatedPostsProps }
|
|
503
|
+
| { component: "article-tags"; id: string; props: ArticleTagsProps }
|
|
214
504
|
| { component: "nav-category-index"; id: string; props: CategoryIndexProps }
|
|
215
505
|
| { component: "image"; id: string; props: ImageProps }
|
|
216
506
|
| { component: "slideshow"; id: string; props: SlideshowProps }
|
|
217
|
-
| { component: "
|
|
507
|
+
| { component: "collage"; id: string; props: CollageProps }
|
|
508
|
+
| { component: "social-share"; id: string; props: SocialShareProps }
|
|
509
|
+
| { component: "button-group"; id: string; props: ButtonGroupProps }
|
|
510
|
+
| { component: "embed"; id: string; props: EmbedProps }
|
|
511
|
+
| { component: "media-text"; id: string; props: MediaTextProps }
|
|
512
|
+
| { component: "latest-news"; id: string; props: LatestNewsProps };
|
|
218
513
|
|
|
219
514
|
export type BlockComponent = Block["component"];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { headers } from "next/headers";
|
|
2
|
+
|
|
1
3
|
import type { DiscoverParams } from "../types";
|
|
2
4
|
import { getAeoContent } from "../lib/client";
|
|
3
5
|
import { safeJson } from "../lib/safe-json";
|
|
@@ -16,6 +18,18 @@ const SR_ONLY_STYLE: React.CSSProperties = {
|
|
|
16
18
|
|
|
17
19
|
export interface SmkingAEOProps extends DiscoverParams {}
|
|
18
20
|
|
|
21
|
+
async function wantsOriginBypass(): Promise<boolean> {
|
|
22
|
+
try {
|
|
23
|
+
const requestHeaders = await headers();
|
|
24
|
+
return (
|
|
25
|
+
requestHeaders.get("x-smking-origin-mode")?.trim().toLowerCase() ===
|
|
26
|
+
"raw"
|
|
27
|
+
);
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
/**
|
|
20
34
|
* Server Component that injects AEO + SEO content for the current
|
|
21
35
|
* request. Place once in the root layout, inside `<body>`:
|
|
@@ -57,6 +71,8 @@ export interface SmkingAEOProps extends DiscoverParams {}
|
|
|
57
71
|
* arbitrary content fetched anywhere.
|
|
58
72
|
*/
|
|
59
73
|
export async function SmkingAEO(props: SmkingAEOProps) {
|
|
74
|
+
if (await wantsOriginBypass()) return null;
|
|
75
|
+
|
|
60
76
|
const aeo = await getAeoContent(props);
|
|
61
77
|
if (!aeo || aeo.status !== "ready") return null;
|
|
62
78
|
|
package/src/index.ts
CHANGED
|
@@ -25,11 +25,13 @@ export type {
|
|
|
25
25
|
NavLayout,
|
|
26
26
|
NavSnapshotEntry,
|
|
27
27
|
NavTaxonomyListProps,
|
|
28
|
-
RecentPostsProps,
|
|
29
28
|
RelatedPostsProps,
|
|
29
|
+
SearchQuickLink,
|
|
30
|
+
SearchQuickLinkSource,
|
|
30
31
|
SearchProps,
|
|
31
32
|
SeoMeta,
|
|
32
33
|
SlideshowProps,
|
|
33
34
|
SlideshowSlideProps,
|
|
35
|
+
SocialSharePlatform,
|
|
34
36
|
SocialShareProps,
|
|
35
37
|
} from "./types";
|
package/src/lib/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = "0.
|
|
1
|
+
export const SDK_VERSION = "0.21.3";
|
package/src/types.ts
CHANGED
|
@@ -56,7 +56,7 @@ export interface AeoResponse {
|
|
|
56
56
|
|
|
57
57
|
export type CmsStatus = "ready" | "preview" | "pending" | "not_found";
|
|
58
58
|
|
|
59
|
-
export type CmsContentType = "article" | "landing" | "listing";
|
|
59
|
+
export type CmsContentType = "article" | "landing" | "listing" | "taxonomy";
|
|
60
60
|
|
|
61
61
|
// `CmsThemeMode` removed in v0.15.0 — Mode B (`tailwind-prose`) dropped
|
|
62
62
|
// in favour of the canonical Plate-serialized HTML render (Mode A only).
|
|
@@ -85,11 +85,13 @@ export type {
|
|
|
85
85
|
NavLayout,
|
|
86
86
|
NavSnapshotEntry,
|
|
87
87
|
NavTaxonomyListProps,
|
|
88
|
-
RecentPostsProps,
|
|
89
88
|
RelatedPostsProps,
|
|
89
|
+
SearchQuickLink,
|
|
90
|
+
SearchQuickLinkSource,
|
|
90
91
|
SearchProps,
|
|
91
92
|
SlideshowProps,
|
|
92
93
|
SlideshowSlideProps,
|
|
94
|
+
SocialSharePlatform,
|
|
93
95
|
SocialShareProps,
|
|
94
96
|
} from "./cms-blocks";
|
|
95
97
|
|