@sonordev/site-kit 8.2.0 → 8.4.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/AGENTS.md +13 -1
- package/CHANGELOG.md +12 -0
- package/README.md +9 -5
- package/agent-manifest.json +53 -8
- package/dist/{AnalyticsProvider-6TAA3XYB.js → AnalyticsProvider-D6GWEJSB.js} +6 -6
- package/dist/{ArticleFaqSection-3Q26DP7P.js → ArticleFaqSection-DEWE5B4Q.js} +1 -1
- package/dist/{ArticleViewTracker-H4N7YCLC.js → ArticleViewTracker-YZ76HM64.js} +17 -6
- package/dist/{BlocksPopup-FJT5YQD3.js → BlocksPopup-UJXZ4AXG.js} +6 -6
- package/dist/{ChatWidget-THQHHTAA.js → ChatWidget-H4EOSH5I.js} +6 -6
- package/dist/{FileField-OZXXJGUM.js → FileField-CKBYGCNT.js} +4 -4
- package/dist/{FormSpotlight-MLDJS246.js → FormSpotlight-CGF5ZE6S.js} +1 -1
- package/dist/{FormStage-6RVY7M3R.js → FormStage-PQPUPQWW.js} +1 -1
- package/dist/{ManagedForm-3VV54RVH.js → ManagedForm-YOEZ7CFS.js} +7 -7
- package/dist/{ManagedNewsletterForm-RTJDGMGG.js → ManagedNewsletterForm-EUWJ2HCA.js} +25 -11
- package/dist/{SignalCore-T7ZOCA44.js → SignalCore-JZSU7A44.js} +14 -8
- package/dist/SiteChat-OAZH34W7.js +6 -0
- package/dist/SiteDesignReporter-IWBMZJYU.js +12 -0
- package/dist/{SitePopups-K6ADAWFK.js → SitePopups-MLAT35UN.js} +6 -6
- package/dist/SitemapSync-6KQLKFO2.js +9 -0
- package/dist/_client/booking-widget.js +6 -6
- package/dist/_client/testimonial-section.js +3 -3
- package/dist/affiliates/index.js +4 -4
- package/dist/analytics/index.js +6 -6
- package/dist/analytics/publication-cta.d.ts +3 -0
- package/dist/{article-styles-GFXJJDLE.js → article-styles-AOOVVKXH.js} +7 -5
- package/dist/article-tables-6XHVULPU.js +2 -0
- package/dist/articles/Article.d.ts +2 -89
- package/dist/articles/ArticleFAQ.d.ts +2 -41
- package/dist/articles/ArticleFaqSection.d.ts +2 -2
- package/dist/articles/ArticleList.d.ts +2 -79
- package/dist/articles/ArticleViewTracker.d.ts +2 -15
- package/dist/articles/AuthorCard.d.ts +2 -10
- package/dist/articles/AuthorPage.d.ts +2 -8
- package/dist/articles/ClusterLandingPage.d.ts +2 -40
- package/dist/articles/ClusterNavigation.d.ts +2 -37
- package/dist/articles/ManagedNewsletterForm.d.ts +3 -12
- package/dist/articles/NewsletterShell.d.ts +2 -17
- package/dist/articles/NewsletterWidget.d.ts +2 -30
- package/dist/articles/PublicationLayout.d.ts +2 -67
- package/dist/articles/PublicationSidebar.d.ts +2 -36
- package/dist/articles/RelatedPosts.d.ts +2 -22
- package/dist/articles/ServiceCallout.d.ts +2 -56
- package/dist/articles/TableOfContents.d.ts +2 -6
- package/dist/articles/article-photos.d.ts +2 -9
- package/dist/articles/article-styles.d.ts +2 -21
- package/dist/articles/article-tables.d.ts +2 -12
- package/dist/articles/artwork.d.ts +2 -13
- package/dist/articles/author-schema.d.ts +2 -72
- package/dist/articles/author-social.d.ts +2 -14
- package/dist/articles/excerpt.d.ts +2 -17
- package/dist/articles/index.d.ts +2 -79
- package/dist/articles/index.js +16 -11
- package/dist/articles/news-sitemap.d.ts +2 -46
- package/dist/articles/processArticleHtml.d.ts +2 -34
- package/dist/articles/public-reads.d.ts +2 -26
- package/dist/articles/reading-time.d.ts +2 -9
- package/dist/articles/routes.d.ts +2 -52
- package/dist/articles/server-core.d.ts +2 -461
- package/dist/articles/server-ui.d.ts +2 -30
- package/dist/articles/server-ui.js +72 -62
- package/dist/articles/server.d.ts +2 -461
- package/dist/articles/server.js +14 -5
- package/dist/articles/types.d.ts +2 -316
- package/dist/articles/widget-styles.d.ts +2 -8
- package/dist/chat/index.js +8 -8
- package/dist/chunk-34ZYJTQR.js +1 -0
- package/dist/{chunk-727R7C5R.js → chunk-3VSFOD2Z.js} +2 -2
- package/dist/{chunk-JC2VS2SB.js → chunk-44GVU5UH.js} +1 -1
- package/dist/{chunk-WYZ4OUON.js → chunk-4HYIDQJJ.js} +1 -1
- package/dist/{chunk-WMVK77ND.js → chunk-4K735O3J.js} +1 -1
- package/dist/{chunk-L5UOHS6E.js → chunk-5D7ZISUP.js} +1 -1
- package/dist/{chunk-Q72M5KBS.js → chunk-5S7YTOCB.js} +1 -1
- package/dist/{chunk-WSNG2K3T.js → chunk-5TDZHIG6.js} +3 -3
- package/dist/{chunk-6CJGZFOU.js → chunk-A4MPHIVP.js} +1 -1
- package/dist/chunk-B7T2SFUF.js +1 -0
- package/dist/{chunk-FV2VD7WM.js → chunk-BC7JMY4Z.js} +3 -3
- package/dist/chunk-BLCHQIWR.js +319 -0
- package/dist/{chunk-NNEIBXGU.js → chunk-BLENFNBW.js} +2 -2
- package/dist/{chunk-NFRJTOTB.js → chunk-BSQP6PRA.js} +1 -1
- package/dist/{chunk-GKGSXUVQ.js → chunk-BVB5FBAA.js} +1 -1
- package/dist/{chunk-WACWV5EP.js → chunk-CE7VPAEH.js} +3 -3
- package/dist/{chunk-GYF2ULVO.js → chunk-DJAYFHVF.js} +1 -1
- package/dist/{chunk-WBC5B3JD.js → chunk-DKKYZLKR.js} +3 -3
- package/dist/{chunk-CAH4Y4PY.js → chunk-DLM4FFNH.js} +6 -2
- package/dist/{chunk-575L73NT.js → chunk-DS6LECIX.js} +1 -1
- package/dist/{chunk-Y3INC4QJ.js → chunk-E4PTMORJ.js} +1 -1
- package/dist/chunk-H72ZFO3F.js +1 -0
- package/dist/{chunk-WK3N3Y7V.js → chunk-HH2GOZTO.js} +2 -2
- package/dist/{chunk-JBEUZJS3.js → chunk-ITY4UZEJ.js} +3 -3
- package/dist/{chunk-GWP2VBFH.js → chunk-J4KX2D3Q.js} +3 -3
- package/dist/{chunk-57K4GW3D.js → chunk-JCSU6EJI.js} +1 -1
- package/dist/{chunk-SC4MZGWZ.js → chunk-JHTPHA2D.js} +6 -3
- package/dist/{chunk-YBAFUM3Z.js → chunk-JV5YWTJN.js} +13 -4
- package/dist/{chunk-I6QFPSUU.js → chunk-KASKB7UK.js} +11 -10
- package/dist/{chunk-SMYX3AAO.js → chunk-LEGMPX6N.js} +1 -1
- package/dist/{chunk-ZM2FNWIQ.js → chunk-LPVEBQVL.js} +1 -1
- package/dist/{chunk-HUBMKTPW.js → chunk-M5SF7NCC.js} +1 -1
- package/dist/{chunk-EBILJHUF.js → chunk-MTV5LYXN.js} +31 -2
- package/dist/{chunk-5ZCY36TY.js → chunk-MZX4EN6Z.js} +1 -1
- package/dist/{chunk-7FSB2EXS.js → chunk-OKITZFML.js} +1 -1
- package/dist/{chunk-H4R2GPAB.js → chunk-PEI6DRSK.js} +2 -2
- package/dist/{chunk-FVXFLVXF.js → chunk-QD73RO67.js} +3 -3
- package/dist/{chunk-66G47P5C.js → chunk-QKOVP7WO.js} +2 -2
- package/dist/{chunk-3EU6S5YC.js → chunk-RBNHRZGD.js} +1 -1
- package/dist/{chunk-XWIRU6UT.js → chunk-S6QDNXDK.js} +2 -2
- package/dist/{chunk-FQUGGPBM.js → chunk-TY6WNQO3.js} +124 -41
- package/dist/{chunk-QXMIKDOL.js → chunk-UVYUTBT4.js} +14 -4
- package/dist/{chunk-3G7YABRA.js → chunk-W7OPEMGJ.js} +4 -1
- package/dist/{chunk-JIH32RH2.js → chunk-XA7Q2VQA.js} +1 -1
- package/dist/{chunk-X7VKRHFF.js → chunk-XENW66OF.js} +1 -1
- package/dist/chunk-XI2XM7UG.js +14 -0
- package/dist/{chunk-BQZPHICL.js → chunk-YM5KMNTU.js} +1 -1
- package/dist/client/index.js +4 -16
- package/dist/fleet/index.js +6 -6
- package/dist/forms/index.js +9 -325
- package/dist/forms/server.js +2 -2
- package/dist/forms/types.d.ts +5 -0
- package/dist/forms/useForm.d.ts +4 -0
- package/dist/images/index.js +4 -4
- package/dist/index.js +1 -1
- package/dist/layout/client.js +9 -9
- package/dist/layout/index.js +10 -10
- package/dist/llms/index.js +8 -8
- package/dist/maps/index.js +4 -4
- package/dist/mcp/sonor.d.ts +46 -3
- package/dist/mcp/sonor.js +51 -29
- package/dist/mcp/transport.d.ts +1 -1
- package/dist/press-kit/articles/Article.d.ts +89 -0
- package/dist/press-kit/articles/ArticleFAQ.d.ts +41 -0
- package/dist/press-kit/articles/ArticleFaqSection.d.ts +2 -0
- package/dist/press-kit/articles/ArticleList.d.ts +79 -0
- package/dist/press-kit/articles/ArticleViewTracker.d.ts +15 -0
- package/dist/press-kit/articles/AuthorCard.d.ts +10 -0
- package/dist/press-kit/articles/AuthorPage.d.ts +8 -0
- package/dist/press-kit/articles/ClusterLandingPage.d.ts +40 -0
- package/dist/press-kit/articles/ClusterNavigation.d.ts +37 -0
- package/dist/press-kit/articles/ManagedNewsletterForm.d.ts +12 -0
- package/dist/press-kit/articles/NewsletterShell.d.ts +17 -0
- package/dist/press-kit/articles/NewsletterWidget.d.ts +30 -0
- package/dist/press-kit/articles/PublicationLayout.d.ts +67 -0
- package/dist/press-kit/articles/PublicationSidebar.d.ts +36 -0
- package/dist/press-kit/articles/RelatedPosts.d.ts +22 -0
- package/dist/press-kit/articles/ServiceCallout.d.ts +56 -0
- package/dist/press-kit/articles/TableOfContents.d.ts +6 -0
- package/dist/press-kit/articles/article-photos.d.ts +9 -0
- package/dist/press-kit/articles/article-styles.d.ts +21 -0
- package/dist/press-kit/articles/article-tables.d.ts +12 -0
- package/dist/press-kit/articles/artwork.d.ts +16 -0
- package/dist/press-kit/articles/author-schema.d.ts +72 -0
- package/dist/press-kit/articles/author-social.d.ts +14 -0
- package/dist/press-kit/articles/excerpt.d.ts +17 -0
- package/dist/press-kit/articles/index.d.ts +80 -0
- package/dist/press-kit/articles/news-sitemap.d.ts +40 -0
- package/dist/press-kit/articles/processArticleHtml.d.ts +34 -0
- package/dist/press-kit/articles/public-reads.d.ts +42 -0
- package/dist/press-kit/articles/publication-definition.d.ts +41 -0
- package/dist/press-kit/articles/publication-reads.d.ts +54 -0
- package/dist/press-kit/articles/reading-time.d.ts +9 -0
- package/dist/press-kit/articles/routes.d.ts +52 -0
- package/dist/press-kit/articles/server-core.d.ts +479 -0
- package/dist/press-kit/articles/server-ui.d.ts +31 -0
- package/dist/press-kit/articles/types.d.ts +339 -0
- package/dist/press-kit/articles/widget-styles.d.ts +8 -0
- package/dist/reputation/index.js +3 -3
- package/dist/reputation/server.js +2 -2
- package/dist/revalidate/index.js +2 -2
- package/dist/runtime/index.js +5 -4
- package/dist/seo/client.js +6 -6
- package/dist/seo/index.js +7 -7
- package/dist/seo/llms.js +8 -8
- package/dist/seo/register-sitemap-cli.js +1 -1
- package/dist/seo/sitemap.js +6 -6
- package/dist/server/index.js +3 -2
- package/dist/shared/build-entries.d.ts +2 -1
- package/dist/shared/clientApiConfig.d.ts +2 -0
- package/dist/shared/import-specifiers.d.ts +1 -1
- package/dist/shared/index.d.ts +6 -0
- package/dist/shared/index.js +8 -0
- package/dist/shared/reporting-gate.d.ts +2 -0
- package/dist/shared/version.d.ts +1 -1
- package/dist/signal/index.js +2 -2
- package/dist/sitemap/index.js +6 -6
- package/dist/sync/index.js +6 -6
- package/dist/website/images.js +4 -4
- package/dist/website/index.js +7 -7
- package/dist/website/popups.js +6 -6
- package/dist/{writeLLMsTxt-DGKLYXIW.js → writeLLMsTxt-N4Y4YC2H.js} +5 -5
- package/docs/MIGRATING-TO-PRESS-KIT.md +83 -0
- package/docs.json +145 -29
- package/package.json +15 -5
- package/src/articles/README.md +5 -360
- package/src/mcp/README.md +16 -0
- package/src/og/README.md +1 -1
- package/src/runtime/README.md +4 -0
- package/dist/SiteChat-JSHPGAE3.js +0 -6
- package/dist/SiteDesignReporter-L2JBAKKK.js +0 -12
- package/dist/SitemapSync-VV2YSMIW.js +0 -9
- package/dist/article-tables-AXSZOGNW.js +0 -2
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/press-kit/articles - Article Sidebar Component
|
|
3
|
+
*
|
|
4
|
+
* A reusable sidebar for article pages with categories, recent posts,
|
|
5
|
+
* tags, newsletter signup, and custom widgets.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { type PublicationRoutingOptions } from './routes';
|
|
9
|
+
export interface PublicationSidebarProps {
|
|
10
|
+
/** Sonor API URL */
|
|
11
|
+
apiUrl?: string;
|
|
12
|
+
/** Project API key */
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
/** Sub-site host for multi-site projects. Omit to resolve from NEXT_PUBLIC_SITE_URL. */
|
|
15
|
+
site?: string;
|
|
16
|
+
/** Show categories widget */
|
|
17
|
+
showCategories?: boolean;
|
|
18
|
+
/** Show recent posts widget */
|
|
19
|
+
showRecentPosts?: boolean;
|
|
20
|
+
/** Number of recent posts to show */
|
|
21
|
+
recentPostsCount?: number;
|
|
22
|
+
/** Show tags widget */
|
|
23
|
+
showTags?: boolean;
|
|
24
|
+
/** Show search widget */
|
|
25
|
+
showSearch?: boolean;
|
|
26
|
+
/** Base URL for article links */
|
|
27
|
+
basePath?: string;
|
|
28
|
+
routing?: PublicationRoutingOptions;
|
|
29
|
+
/** Custom class name */
|
|
30
|
+
className?: string;
|
|
31
|
+
/** Current category filter (for highlighting) */
|
|
32
|
+
currentCategory?: string;
|
|
33
|
+
/** Additional widgets to render */
|
|
34
|
+
children?: React.ReactNode;
|
|
35
|
+
}
|
|
36
|
+
export declare function PublicationSidebar({ apiUrl, apiKey, site, showCategories, showRecentPosts, recentPostsCount, showTags, showSearch, basePath, routing, className, currentCategory, children, }: PublicationSidebarProps): Promise<React.JSX.Element | null>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/press-kit/articles - Related articles component
|
|
3
|
+
*
|
|
4
|
+
* Fetches and displays related articles via Sonor API
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import type { Article } from './types';
|
|
8
|
+
import { type PublicationRoutingOptions } from './routes';
|
|
9
|
+
interface RelatedPostsServerProps {
|
|
10
|
+
apiUrl?: string;
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
currentPostId: string;
|
|
13
|
+
limit?: number;
|
|
14
|
+
/** Sub-site host for multi-site projects. Omit to resolve from NEXT_PUBLIC_SITE_URL. */
|
|
15
|
+
site?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
basePath?: string;
|
|
18
|
+
routing?: PublicationRoutingOptions;
|
|
19
|
+
renderItem?: (post: Article) => React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
export declare function RelatedPosts({ apiUrl, apiKey, currentPostId, limit, site, className, basePath, routing, renderItem, }: RelatedPostsServerProps): Promise<React.JSX.Element | null>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/press-kit/articles - Service Callout / CTA Component
|
|
3
|
+
*
|
|
4
|
+
* Renders a promotional call-to-action section within articles.
|
|
5
|
+
* Automatically generated by Signal AI during article creation, linking
|
|
6
|
+
* article content to the business's relevant services.
|
|
7
|
+
*
|
|
8
|
+
* Two variants:
|
|
9
|
+
* - 'featured' (default): Full-width gradient banner with button
|
|
10
|
+
* - 'inline': Compact card with icon accent
|
|
11
|
+
*
|
|
12
|
+
* Uses site-kit brand tokens (--sk-primary, --sk-bg, etc.) for theming.
|
|
13
|
+
* Supports full Tailwind override via `styles` prop.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <ServiceCallout
|
|
18
|
+
* title="Ready to build your MVP?"
|
|
19
|
+
* description="Our team specializes in lean SaaS builds."
|
|
20
|
+
* cta="Get Started"
|
|
21
|
+
* url="/services/application-development"
|
|
22
|
+
* />
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import React from 'react';
|
|
26
|
+
export interface ServiceCalloutProps {
|
|
27
|
+
title: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
cta?: string;
|
|
30
|
+
url: string;
|
|
31
|
+
variant?: 'featured' | 'inline';
|
|
32
|
+
/** Override default styles with Tailwind classes */
|
|
33
|
+
styles?: {
|
|
34
|
+
wrapper?: string;
|
|
35
|
+
title?: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
button?: string;
|
|
38
|
+
};
|
|
39
|
+
/** Strip all default styles (BYO Tailwind) */
|
|
40
|
+
unstyled?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare function ServiceCallout({ title, description, cta, url, variant, styles, unstyled, }: ServiceCalloutProps): React.JSX.Element;
|
|
43
|
+
/**
|
|
44
|
+
* Renders all service callouts for an article.
|
|
45
|
+
* First callout uses 'featured' variant, subsequent use 'inline'.
|
|
46
|
+
*/
|
|
47
|
+
export declare function ServiceCallouts({ callouts, styles, unstyled, }: {
|
|
48
|
+
callouts: Array<{
|
|
49
|
+
title: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
cta?: string;
|
|
52
|
+
url: string;
|
|
53
|
+
}>;
|
|
54
|
+
styles?: ServiceCalloutProps['styles'];
|
|
55
|
+
unstyled?: boolean;
|
|
56
|
+
}): React.JSX.Element | null;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/press-kit/articles - Table of Contents Component
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import type { TableOfContentsProps } from './types';
|
|
6
|
+
export declare function TableOfContents({ content, className, maxDepth, }: TableOfContentsProps): React.JSX.Element | null;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ArticleEditorialPhoto } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Inserts approved public photo snapshots at explicitly authored placements.
|
|
4
|
+
* Missing/duplicate headings, unmatched sections and photos already in the body
|
|
5
|
+
* are skipped. Never changes the author's paragraphs, headings or other markup.
|
|
6
|
+
* This is a layout transform, not a sanitizer for the original article HTML.
|
|
7
|
+
*/
|
|
8
|
+
export declare function insertArticlePhotos(html: string, photos?: ArticleEditorialPhoto[] | null): string;
|
|
9
|
+
export declare const articlePhotoCss = "\n.sk-article-photo { margin: 2em 0; max-width: 100%; }\n.sk-article-photo img { display: block; width: 100%; max-width: 100%; height: auto; border-radius: var(--sk-radius, 0.75rem); }\n.sk-article-photo figcaption { margin-top: 0.65em; color: var(--sk-text-secondary, inherit); font-size: 0.875em; line-height: 1.5; overflow-wrap: anywhere; }\n";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoped CSS for Article and ArticleList default rendering.
|
|
3
|
+
*
|
|
4
|
+
* Injected via `<style>` in the server component output so consumers
|
|
5
|
+
* get a polished article with zero configuration.
|
|
6
|
+
*
|
|
7
|
+
* All colours use `--sk-*` CSS custom properties with `color-mix()` fallbacks
|
|
8
|
+
* that adapt to both light and dark host sites. The host can set:
|
|
9
|
+
*
|
|
10
|
+
* --sk-primary Brand accent (links, highlights)
|
|
11
|
+
* --sk-bg Page background
|
|
12
|
+
* --sk-bg-elevated Card / elevated surface background
|
|
13
|
+
* --sk-text-primary Main text
|
|
14
|
+
* --sk-text-secondary Subdued text
|
|
15
|
+
* --sk-text-tertiary Faint text / meta
|
|
16
|
+
* --sk-surface-border Subtle border color
|
|
17
|
+
*
|
|
18
|
+
* If none are set the defaults produce a clean neutral light-mode look.
|
|
19
|
+
*/
|
|
20
|
+
export declare const articleCss: string;
|
|
21
|
+
export declare const articleListCss: string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-rendered table scrolling. Keep the author's table markup intact so
|
|
3
|
+
* captions, header associations, links and table semantics survive unchanged.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Single source of truth for default article table wrappers. Safe to call twice;
|
|
7
|
+
* already wrapped tables and HTML examples are left alone. This is a layout
|
|
8
|
+
* transform, not an HTML sanitizer: content still follows the host's trust policy.
|
|
9
|
+
*/
|
|
10
|
+
export declare function wrapArticleTables(html: string): string;
|
|
11
|
+
/** Native overflow only when content needs it; small tables retain their layout. */
|
|
12
|
+
export declare const articleTableCss: string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
import type { Article } from './types';
|
|
3
|
+
export interface ArticleArtwork {
|
|
4
|
+
src: string | undefined;
|
|
5
|
+
alt: string;
|
|
6
|
+
composed: boolean;
|
|
7
|
+
}
|
|
8
|
+
type ArtworkSource = Pick<Article, 'title' | 'featured_image' | 'featured_image_alt' | 'editorial_image' | 'editorial_image_alt'>;
|
|
9
|
+
/** Single source of truth for article artwork versus complete promotional cards. */
|
|
10
|
+
export declare function resolveArticleArtwork(post: ArtworkSource, placement?: 'article' | 'card'): ArticleArtwork;
|
|
11
|
+
/** Preserve generated text and graphics across every stock card surface. */
|
|
12
|
+
export declare function articleArtworkImageStyle(artwork: ArticleArtwork): CSSProperties | undefined;
|
|
13
|
+
/** Search/discovery artwork is the representative photograph; social cards stay available separately. */
|
|
14
|
+
export declare function resolveRepresentativeImage(post: Partial<Pick<Article, 'title' | 'representative_image' | 'editorial_image' | 'editorial_image_alt' | 'editorial_image_width' | 'editorial_image_height' | 'featured_image' | 'featured_image_alt' | 'featured_image_width' | 'featured_image_height'>>): import('./types').ArticleImage | undefined;
|
|
15
|
+
export declare function articleImageSchema(post: Partial<Article>): object | string | undefined;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/press-kit/articles — Author JSON-LD (pure)
|
|
3
|
+
*
|
|
4
|
+
* Split out of `./server-core` on purpose. Everything here is a pure function:
|
|
5
|
+
* it maps an `ArticleAuthor` to schema objects and touches no env, no config,
|
|
6
|
+
* and no network. It only ever lived in `server-core` because the other author
|
|
7
|
+
* helpers there (`getAuthorBySlug`, `getAuthorPosts`) do fetch.
|
|
8
|
+
*
|
|
9
|
+
* `AuthorPage` is exported from the client-stamped `@sonordev/press-kit/articles`
|
|
10
|
+
* barrel (see `./index.ts`), so importing these from `server-core` dragged that
|
|
11
|
+
* whole server module — its `x-api-key` fetches included — into the browser
|
|
12
|
+
* bundle. Keeping the pure half here means `AuthorPage` can stay in the barrel
|
|
13
|
+
* without pulling a single line of server code with it.
|
|
14
|
+
*
|
|
15
|
+
* Anything added here must stay pure. If it needs an API key or a fetch, it
|
|
16
|
+
* belongs in `./server-core`, not in this file.
|
|
17
|
+
*
|
|
18
|
+
* Identity model (single source of truth for every author node the kit emits):
|
|
19
|
+
* - The author page is a `ProfilePage` whose `mainEntity` is the `Person`
|
|
20
|
+
* (Google's profile-page markup for author pages).
|
|
21
|
+
* - The `Person` carries one `@id` everywhere it appears (`authorEntityId`):
|
|
22
|
+
* the row's `entity_id` when the person has a cross-site identity (e.g.
|
|
23
|
+
* https://example.com/#person), else `<author profile URL>#person`.
|
|
24
|
+
* - Article bylines (`generateArticleAuthorNode`) reference that same `@id`,
|
|
25
|
+
* so an article, its author page and the person's own site describe one
|
|
26
|
+
* person, which is what a Knowledge Panel is built from.
|
|
27
|
+
*/
|
|
28
|
+
import type { ArticleAuthor } from './types';
|
|
29
|
+
import { type PublicationRoutingOptions } from './routes';
|
|
30
|
+
export type AuthorSchemaOptions = PublicationRoutingOptions & {
|
|
31
|
+
siteUrl?: string;
|
|
32
|
+
siteName?: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Single source of truth for an author's Person `@id`.
|
|
36
|
+
*
|
|
37
|
+
* The row's `entity_id` (one id shared by every site that describes the
|
|
38
|
+
* person) wins. Otherwise the Person lives at `<profile URL>#person`. A
|
|
39
|
+
* relative profile URL gives no `@id`: an id must be absolute to mean the
|
|
40
|
+
* same thing on every page.
|
|
41
|
+
*/
|
|
42
|
+
export declare function authorEntityId(author: Pick<ArticleAuthor, 'entity_id'>, profileUrl?: string): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* The bare `Person` for an author, with no `@context`, for embedding in a
|
|
45
|
+
* larger graph (an Organization's `founder`, an `@graph` array). Author pages
|
|
46
|
+
* want `generateAuthorSchema`, which wraps this in a `ProfilePage`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function generateAuthorPersonSchema(author: ArticleAuthor, options?: AuthorSchemaOptions): Record<string, unknown>;
|
|
49
|
+
/**
|
|
50
|
+
* JSON-LD for an author page: a `ProfilePage` whose `mainEntity` is the
|
|
51
|
+
* author's `Person`. Render it once per author page (`AuthorPage` does, unless
|
|
52
|
+
* its `jsonLd` prop is false).
|
|
53
|
+
*/
|
|
54
|
+
export declare function generateAuthorSchema(author: ArticleAuthor, options?: AuthorSchemaOptions): object;
|
|
55
|
+
/**
|
|
56
|
+
* The `author` node for article JSON-LD. Single source of truth for bylines:
|
|
57
|
+
* generateArticleSchema, generateClusterArticleSchema and stored schema_json
|
|
58
|
+
* (via withArticleAuthorIdentity) all route through here.
|
|
59
|
+
*
|
|
60
|
+
* Links to an author page only when one is known to exist: the row's
|
|
61
|
+
* `author_page_url`, or the site declaring `authorPages` in its routing. A
|
|
62
|
+
* bare string byline stays a named Person.
|
|
63
|
+
*/
|
|
64
|
+
export declare function generateArticleAuthorNode(author: ArticleAuthor | string | null | undefined, options?: AuthorSchemaOptions): Record<string, unknown> | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Attach the author's identity to an article node from stored schema_json
|
|
67
|
+
* (built by Signal at publish time, frozen in the row). Adds `@id` and `url`
|
|
68
|
+
* from generateArticleAuthorNode when the stored byline is a Person with the
|
|
69
|
+
* same name and lacks them; stored values always win and nothing else in the
|
|
70
|
+
* node changes. Non-article nodes pass through untouched.
|
|
71
|
+
*/
|
|
72
|
+
export declare function withArticleAuthorIdentity(node: unknown, author: ArticleAuthor | string | null | undefined, options?: AuthorSchemaOptions): unknown;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ArticleAuthor } from './types';
|
|
2
|
+
export interface AuthorSocialLink {
|
|
3
|
+
label: string;
|
|
4
|
+
href: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Single source of truth for author social display.
|
|
8
|
+
*
|
|
9
|
+
* The real blog_authors columns are linkedin_url / twitter_url / website_url
|
|
10
|
+
* (E-E-A-T fields); `social_links` / `website` are the legacy shape that no
|
|
11
|
+
* API response ever carried. Real columns win; legacy handles get their host
|
|
12
|
+
* prefixed unless they are already full URLs.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getAuthorSocialLinks(author: Pick<ArticleAuthor, 'linkedin_url' | 'twitter_url' | 'website_url' | 'website' | 'social_links'>): AuthorSocialLink[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excerpt sanitizing for article metadata, schema and feeds.
|
|
3
|
+
*
|
|
4
|
+
* Pure (no `server-only` chain) so it is unit-testable and safe to reuse.
|
|
5
|
+
* Source of truth for turning a stored excerpt into display-safe prose.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Turn a stored excerpt into something safe to put in a meta description,
|
|
9
|
+
* a schema `description`, or an RSS `<description>`.
|
|
10
|
+
*
|
|
11
|
+
* The AI article pipeline stores content as JSON sections
|
|
12
|
+
* (`{"heading": "## ...", "content": "## ...\n\nIf you're asking **how**..."}`)
|
|
13
|
+
* and that value can ship verbatim into <meta name="description">, leaving a
|
|
14
|
+
* SERP snippet of raw JSON with markdown asterisks and literal \n. Parse the
|
|
15
|
+
* sections out, strip markdown, and cap at a sentence boundary.
|
|
16
|
+
*/
|
|
17
|
+
export declare function sanitizeExcerpt(raw: string | undefined | null, maxLength?: number): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sonordev/press-kit/articles
|
|
3
|
+
*
|
|
4
|
+
* Complete article integration for Sonor-managed content.
|
|
5
|
+
* Create posts in the Sonor dashboard, automatically embedded in your site.
|
|
6
|
+
*
|
|
7
|
+
* Async server components (PublicationLayout, ArticleList, Article, PublicationSidebar,
|
|
8
|
+
* RelatedPosts, the *Page helpers) come from `articles/server-ui`. This entry is
|
|
9
|
+
* 'use client'-stamped, so importing them from here hands Next an async
|
|
10
|
+
* component across a client boundary and the route 500s in production.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* // Publication index page — both from server-ui; this barrel is for client components only
|
|
15
|
+
* import { ArticleList, PublicationLayout } from '@sonordev/press-kit/articles/server-ui'
|
|
16
|
+
*
|
|
17
|
+
* export default function PublicationPage() {
|
|
18
|
+
* return (
|
|
19
|
+
* <PublicationLayout hero={{ title: 'Blog', subtitle: 'Latest articles' }}>
|
|
20
|
+
* <ArticleList showCategoryFilter showPagination />
|
|
21
|
+
* </PublicationLayout>
|
|
22
|
+
* )
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* // Single post page: UI from server-ui, data/metadata from server
|
|
29
|
+
* import { Article } from '@sonordev/press-kit/articles/server-ui'
|
|
30
|
+
* import { generateArticleMetadata, generateArticleStaticParams } from '@sonordev/press-kit/articles/server'
|
|
31
|
+
*
|
|
32
|
+
* export const generateStaticParams = generateArticleStaticParams
|
|
33
|
+
* export async function generateMetadata({ params }) {
|
|
34
|
+
* return generateArticleMetadata(params.slug, { siteName: 'My Site' })
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* export default function Post({ params }) {
|
|
38
|
+
* return <Article slug={params.slug} showRelated showToc />
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export type { PublicationDefinition } from './publication-definition';
|
|
43
|
+
export type { ArticleServerProps, ArticleStyles } from './Article';
|
|
44
|
+
export type { ArticleListServerProps, ArticleListStyles } from './ArticleList';
|
|
45
|
+
export type { PublicationSidebarProps } from './PublicationSidebar';
|
|
46
|
+
export { NewsletterWidget } from './NewsletterWidget';
|
|
47
|
+
export type { NewsletterWidgetProps } from './NewsletterWidget';
|
|
48
|
+
export type { PublicationLayoutProps, PublicationPageProps, ArticlePageProps, CategoryPageProps } from './PublicationLayout';
|
|
49
|
+
export { AuthorCard } from './AuthorCard';
|
|
50
|
+
export { AuthorPage } from './AuthorPage';
|
|
51
|
+
export { TableOfContents } from './TableOfContents';
|
|
52
|
+
export { ServiceCallout, ServiceCallouts } from './ServiceCallout';
|
|
53
|
+
export type { ServiceCalloutProps } from './ServiceCallout';
|
|
54
|
+
export { ArticleFAQ } from './ArticleFAQ';
|
|
55
|
+
export type { ArticleEditorialPhoto } from './types';
|
|
56
|
+
export type { ArticleFAQProps, FaqItem } from './ArticleFAQ';
|
|
57
|
+
export { ClusterNavigation } from './ClusterNavigation';
|
|
58
|
+
export type { ClusterNavigationProps } from './ClusterNavigation';
|
|
59
|
+
/**
|
|
60
|
+
* `ClusterLandingPage` is deliberately NOT re-exported here — import it from
|
|
61
|
+
* `@sonordev/press-kit/articles/server-ui`.
|
|
62
|
+
*
|
|
63
|
+
* This barrel is stamped `'use client'` at build time (it is listed in
|
|
64
|
+
* `CLIENT_ENTRIES`, `src/shared/build-entries.ts`), so every module it reaches
|
|
65
|
+
* is client code by declaration. `ClusterLandingPage` is an async component that
|
|
66
|
+
* awaits `getTopicCluster` from `./server-core`, so exporting it from here put
|
|
67
|
+
* that module's `x-api-key` fetch into the browser bundle (confirmed in a real
|
|
68
|
+
* site's build, in `.next/static/chunks/`). It also could never have
|
|
69
|
+
* worked as a client component: async components are server-only.
|
|
70
|
+
*
|
|
71
|
+
* The type stays — `export type` is erased at build time and reaches no runtime
|
|
72
|
+
* module, so it keeps `import type { ClusterLandingPageProps }` working for
|
|
73
|
+
* existing consumers.
|
|
74
|
+
*/
|
|
75
|
+
export type { ClusterLandingPageProps } from './ClusterLandingPage';
|
|
76
|
+
export * from './types';
|
|
77
|
+
export { articleCategorySlug, createPublicationRoutes, resolveArticleUrl, resolvePublicationUrl } from './routes';
|
|
78
|
+
export type { ArticleRoutePost, PublicationRoutingOptions } from './routes';
|
|
79
|
+
export { resolveArticleArtwork } from './artwork';
|
|
80
|
+
export { addExternalLinkTargets, linkClassificationHost, normalizeSiteHost, resolvePublicationSiteUrl, } from './processArticleHtml';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Article } from './types';
|
|
2
|
+
import { type PublicationRoutingOptions, type ArticleRoutePost } from './routes';
|
|
3
|
+
/** Google only reads articles from the last two days in a News sitemap. */
|
|
4
|
+
export declare const NEWS_SITEMAP_WINDOW_MS: number;
|
|
5
|
+
/** Google's cap per News sitemap. */
|
|
6
|
+
export declare const NEWS_SITEMAP_MAX_URLS = 1000;
|
|
7
|
+
/**
|
|
8
|
+
* Items a feed carries by default. Feeds hold each post's full HTML, so a
|
|
9
|
+
* 100-post feed ran past half a megabyte, which is where some readers and
|
|
10
|
+
* aggregators stop fetching. Readers poll often; 20 recent items is the
|
|
11
|
+
* common convention, and older posts stay in the sitemap.
|
|
12
|
+
*/
|
|
13
|
+
export declare const FEED_DEFAULT_ITEMS = 20;
|
|
14
|
+
export interface NewsSitemapPost extends ArticleRoutePost {
|
|
15
|
+
title: string;
|
|
16
|
+
published_at?: string | null;
|
|
17
|
+
representative_image?: Article['representative_image'];
|
|
18
|
+
editorial_image?: string;
|
|
19
|
+
featured_image?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface NewsSitemapOptions extends PublicationRoutingOptions {
|
|
22
|
+
siteUrl: string;
|
|
23
|
+
/** The publication name, exactly as it appears on the articles (e.g. "The Journal by Example Co."). */
|
|
24
|
+
publicationName: string;
|
|
25
|
+
/** ISO 639 language code. Defaults to "en". */
|
|
26
|
+
language?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function escapeXml(text: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Wrap HTML in CDATA. A literal `]]>` inside the post would close the section
|
|
31
|
+
* early and break the whole feed, so it's split across two sections.
|
|
32
|
+
*/
|
|
33
|
+
export declare function cdata(html: string): string;
|
|
34
|
+
/** The posts a News sitemap may list: published in the window, newest first. */
|
|
35
|
+
export declare function recentNewsPosts<T extends NewsSitemapPost>(posts: T[], now?: Date): T[];
|
|
36
|
+
/**
|
|
37
|
+
* Build a Google News sitemap. With no post in the window it's a valid, empty
|
|
38
|
+
* urlset, which Google accepts; it fills again on the next publish.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildNewsSitemapXml(posts: NewsSitemapPost[], options: NewsSitemapOptions, now?: Date): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-process Sonor/article HTML for safe, consistent link behavior.
|
|
3
|
+
* Server-safe string transforms only (no DOM).
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The host `addExternalLinkTargets` compares links against to decide whether a
|
|
7
|
+
* link is internal (same tab) or external (`target="_blank"`). Lowercased,
|
|
8
|
+
* leading `www.` stripped, so `www.example.com` and `example.com` count as the
|
|
9
|
+
* same site; returns '' for anything unparseable.
|
|
10
|
+
*
|
|
11
|
+
* NOT the multi-site host normalizer. That is `normalizeSiteHost` in
|
|
12
|
+
* `sites/normalize`, which keeps `www.` because `www.example.com` and
|
|
13
|
+
* `example.com` can be different sub-sites of one Sonor project. This used to
|
|
14
|
+
* share that name, so the two were easy to swap by accident.
|
|
15
|
+
*/
|
|
16
|
+
export declare function linkClassificationHost(siteUrl: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated Renamed to {@link linkClassificationHost} (same behavior: strips
|
|
19
|
+
* `www.`). It shared a name with the multi-site `normalizeSiteHost` from
|
|
20
|
+
* `sites/normalize`, which keeps `www.`. Kept so existing imports from
|
|
21
|
+
* `@sonordev/press-kit/articles` still work.
|
|
22
|
+
*/
|
|
23
|
+
export declare const normalizeSiteHost: typeof linkClassificationHost;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve canonical site URL for link classification.
|
|
26
|
+
* Pass `explicit` from props when env vars are not set (e.g. non-Vercel hosts).
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolvePublicationSiteUrl(explicit?: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Add `target="_blank"` and `rel="noopener noreferrer"` to external http(s) links.
|
|
31
|
+
* Same registrable host (with or without `www`) is treated as internal.
|
|
32
|
+
* Skips anchors that already set `target`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function addExternalLinkTargets(html: string, siteUrl: string): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ArticleCategory, Article } from './types';
|
|
2
|
+
export interface ArticleReadConfig {
|
|
3
|
+
apiUrl: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
/** Sub-site host override. Omit to resolve from NEXT_PUBLIC_SITE_URL. */
|
|
6
|
+
site?: string;
|
|
7
|
+
}
|
|
8
|
+
/** A failed upstream read must never masquerade as a missing article. */
|
|
9
|
+
export declare class ArticleUnavailableError extends Error {
|
|
10
|
+
readonly status?: number | undefined;
|
|
11
|
+
readonly code = "ARTICLE_UNAVAILABLE";
|
|
12
|
+
constructor(status?: number | undefined);
|
|
13
|
+
}
|
|
14
|
+
export type ArticleReadResult<T> = {
|
|
15
|
+
state: 'ready';
|
|
16
|
+
value: T;
|
|
17
|
+
} | {
|
|
18
|
+
state: 'not_found';
|
|
19
|
+
} | {
|
|
20
|
+
state: 'unavailable';
|
|
21
|
+
status?: number;
|
|
22
|
+
};
|
|
23
|
+
export declare function readArticleBySlug({ apiUrl, apiKey, site }: ArticleReadConfig, slug: string): Promise<ArticleReadResult<Article>>;
|
|
24
|
+
/** Returns null only on a confirmed 404; outages propagate to the route error boundary. */
|
|
25
|
+
export declare function fetchArticleBySlug(config: ArticleReadConfig, slug: string): Promise<Article | null>;
|
|
26
|
+
/** GET /public/articles/categories — [] on any failure. */
|
|
27
|
+
export declare function fetchArticleCategories({ apiUrl, apiKey, site }: ArticleReadConfig): Promise<ArticleCategory[]>;
|
|
28
|
+
/**
|
|
29
|
+
* POST /public/articles/related — [] on any failure.
|
|
30
|
+
*
|
|
31
|
+
* `/public/articles/*`, not `/api/public/articles/*`: public-article.controller is one of
|
|
32
|
+
* only two public controllers whose @Controller() omits the `api/` segment.
|
|
33
|
+
* The body is snake_case `current_post_id` (the controller looks the post up
|
|
34
|
+
* by id — there is no slug path), and `site` rides in the body because this is
|
|
35
|
+
* the one article read that isn't a GET.
|
|
36
|
+
*
|
|
37
|
+
* `revalidate` is optional so <RelatedPosts>, which never set a fetch cache
|
|
38
|
+
* lifetime, keeps rendering exactly as it did.
|
|
39
|
+
*/
|
|
40
|
+
export declare function fetchRelatedArticles({ apiUrl, apiKey, site }: ArticleReadConfig, currentPostId: string, limit: number, cache?: {
|
|
41
|
+
revalidate?: number;
|
|
42
|
+
}): Promise<Article[]>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** One portable, tenant-authored launch definition. Contains no credential or content. */
|
|
2
|
+
export interface PublicationDefinition {
|
|
3
|
+
version: 1;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
siteUrl: string;
|
|
7
|
+
basePath: string;
|
|
8
|
+
style: 'newsroom' | 'journal' | 'briefing';
|
|
9
|
+
sections: Array<{
|
|
10
|
+
slug: string;
|
|
11
|
+
title: string;
|
|
12
|
+
}>;
|
|
13
|
+
newsletterFormId: string;
|
|
14
|
+
brand: {
|
|
15
|
+
primary: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export declare const PUBLICATION_STARTERS: {
|
|
19
|
+
readonly newsroom: {
|
|
20
|
+
readonly label: 'Newsroom';
|
|
21
|
+
readonly description: 'A strong masthead and a visual grid for frequent reporting.';
|
|
22
|
+
readonly columns: 3;
|
|
23
|
+
readonly maxWidth: 1280;
|
|
24
|
+
readonly serif: false;
|
|
25
|
+
};
|
|
26
|
+
readonly journal: {
|
|
27
|
+
readonly label: 'Journal';
|
|
28
|
+
readonly description: 'A quieter serif masthead and roomy stories for expert essays.';
|
|
29
|
+
readonly columns: 2;
|
|
30
|
+
readonly maxWidth: 1040;
|
|
31
|
+
readonly serif: true;
|
|
32
|
+
};
|
|
33
|
+
readonly briefing: {
|
|
34
|
+
readonly label: 'Briefing';
|
|
35
|
+
readonly description: 'A compact single-column reading list for useful updates.';
|
|
36
|
+
readonly columns: 1;
|
|
37
|
+
readonly maxWidth: 820;
|
|
38
|
+
readonly serif: false;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
export declare function definePublication(input: unknown): PublicationDefinition;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import type { Article } from './types';
|
|
3
|
+
import type { ArticleReadResult } from './public-reads';
|
|
4
|
+
export interface PublicationIssue {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
subject: string;
|
|
8
|
+
sentAt: string;
|
|
9
|
+
window?: {
|
|
10
|
+
start?: string;
|
|
11
|
+
end?: string;
|
|
12
|
+
};
|
|
13
|
+
articles: Array<{
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
url: string;
|
|
17
|
+
}>;
|
|
18
|
+
/** Immutable, public-safe sent snapshot; no recipient tokens or private fields. */
|
|
19
|
+
html?: string;
|
|
20
|
+
text?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface PublicationIssueList {
|
|
23
|
+
issues: PublicationIssue[];
|
|
24
|
+
nextCursor?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function getPublicationIssues(options?: {
|
|
27
|
+
site?: string;
|
|
28
|
+
limit?: number;
|
|
29
|
+
cursor?: string;
|
|
30
|
+
}): Promise<ArticleReadResult<PublicationIssueList>>;
|
|
31
|
+
export declare function getPublicationIssue(id: string, options?: {
|
|
32
|
+
site?: string;
|
|
33
|
+
}): Promise<ArticleReadResult<PublicationIssue>>;
|
|
34
|
+
export interface ArticlePreview {
|
|
35
|
+
post: Article;
|
|
36
|
+
revision: string;
|
|
37
|
+
expiresAt: string;
|
|
38
|
+
site: string;
|
|
39
|
+
}
|
|
40
|
+
export declare const PUBLICATION_PREVIEW_COOKIE = "_sk_press_preview";
|
|
41
|
+
export declare const PUBLICATION_PREVIEW_HEADERS: {
|
|
42
|
+
'Cache-Control': string;
|
|
43
|
+
'X-Robots-Tag': string;
|
|
44
|
+
'Referrer-Policy': string;
|
|
45
|
+
};
|
|
46
|
+
/** Server-to-server exchange. Never render or log the token; every read rechecks its exact revision. */
|
|
47
|
+
export declare function getArticlePreview(token: string, options: {
|
|
48
|
+
site: string;
|
|
49
|
+
}): Promise<ArticleReadResult<ArticlePreview>>;
|
|
50
|
+
/** CMS entry: validate once, hide token in a scoped HttpOnly cookie, redirect only to configured local preview. */
|
|
51
|
+
export declare function createPublicationPreviewHandler(options: {
|
|
52
|
+
siteUrl: string;
|
|
53
|
+
previewPath: string;
|
|
54
|
+
}): (request: Request) => Promise<Response>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Article } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Single source of truth for reading-time display.
|
|
4
|
+
*
|
|
5
|
+
* The DB/API column is `reading_time` (blog_posts) — `reading_time_minutes`
|
|
6
|
+
* never existed server-side and exists on the type only for callers that
|
|
7
|
+
* pre-map their own data. Every "X min read" chip resolves through here.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readingTimeMinutes(post: Pick<Article, 'reading_time' | 'reading_time_minutes'>): number | null;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Shared, environment-free publication routes for server helpers and article UI. */
|
|
2
|
+
export interface ArticleRoutePost {
|
|
3
|
+
slug: string;
|
|
4
|
+
category?: string | {
|
|
5
|
+
slug?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
} | null;
|
|
8
|
+
canonical_url?: string | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Where a publication lives when a site doesn't say. A site whose articles
|
|
12
|
+
* already live at another path (/article, /insights, /theforge) passes basePath;
|
|
13
|
+
* changing a live URL is an SEO decision, never a default's.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_PUBLICATION_PATH = "/articles";
|
|
16
|
+
export interface PublicationRoutingOptions {
|
|
17
|
+
/** Publication root, e.g. /theforge or /news. Defaults to DEFAULT_PUBLICATION_PATH (/articles). */
|
|
18
|
+
basePath?: string;
|
|
19
|
+
/** Use /publication/category/post instead of /publication/post. */
|
|
20
|
+
includeCategoryInPath?: boolean;
|
|
21
|
+
/** Fallback category for partial cluster records. */
|
|
22
|
+
category?: string;
|
|
23
|
+
/** A custom local article path. Receives the post's slug and category. */
|
|
24
|
+
postPath?: (post: ArticleRoutePost) => string;
|
|
25
|
+
/** A custom category destination, for example /theforge?category=guides. */
|
|
26
|
+
categoryPath?: (slug: string) => string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether this site serves author pages. `true` means they live at the
|
|
29
|
+
* default `<publication>/author/<slug>`; a function gives a custom path, for
|
|
30
|
+
* example ``(slug) => `/author/${slug}` `` when author pages sit at the site
|
|
31
|
+
* root. Article JSON-LD links bylines to author pages ONLY when this is set
|
|
32
|
+
* (or the author row has `author_page_url`), because most sites have none
|
|
33
|
+
* and a byline URL that 404s is worse than no URL.
|
|
34
|
+
*/
|
|
35
|
+
authorPages?: boolean | ((slug: string) => string);
|
|
36
|
+
}
|
|
37
|
+
export declare function articleCategorySlug(category: ArticleRoutePost['category']): string;
|
|
38
|
+
/** Single source of truth for publication paths. Custom routes never change OG artwork. */
|
|
39
|
+
export declare function createPublicationRoutes(options?: PublicationRoutingOptions): {
|
|
40
|
+
index: string;
|
|
41
|
+
post(post: ArticleRoutePost): string;
|
|
42
|
+
category(slug: string): string;
|
|
43
|
+
cluster: (slug: string) => string;
|
|
44
|
+
author: (slug: string) => string;
|
|
45
|
+
rss: string;
|
|
46
|
+
atom: string;
|
|
47
|
+
};
|
|
48
|
+
export declare function resolvePublicationUrl(path: string, siteUrl?: string): string;
|
|
49
|
+
/** Metadata, feeds, sitemaps and generated schemas agree on a supplied canonical. */
|
|
50
|
+
export declare function resolveArticleUrl(post: ArticleRoutePost, options?: PublicationRoutingOptions & {
|
|
51
|
+
siteUrl?: string;
|
|
52
|
+
}): string;
|