mentionwell 1.2.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/README.md +148 -0
- package/dist/api-reader.d.ts +47 -0
- package/dist/api-reader.d.ts.map +1 -0
- package/dist/api-reader.js +125 -0
- package/dist/api-reader.js.map +1 -0
- package/dist/html-utils.d.ts +24 -0
- package/dist/html-utils.d.ts.map +1 -0
- package/dist/html-utils.js +41 -0
- package/dist/html-utils.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/mentionwell-article.css +877 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# mentionwell
|
|
2
|
+
|
|
3
|
+
Destination-side reader + styles for [Mentionwell](https://github.com/isaachorowitz/Witz-Blog-Bot-1)-generated blog posts. The recommended setup reads published articles from the hosted Mentionwell API with a per-site `MENTIONWELL_API_KEY`; no Supabase credentials or GitHub delivery are needed on the destination domain. The package also ships ready-to-use CSS for tables, callouts, pull quotes, FAQ accordions, citations, YouTube embeds, and a sticky table-of-contents sidebar.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install mentionwell
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Use
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// lib/blogoto-blog.ts
|
|
15
|
+
import {
|
|
16
|
+
getBlogPostViaApi,
|
|
17
|
+
getBlogPostsViaApi,
|
|
18
|
+
getAllBlogSlugsViaApi
|
|
19
|
+
} from 'mentionwell/api'
|
|
20
|
+
|
|
21
|
+
const config = {
|
|
22
|
+
apiUrl: process.env.MENTIONWELL_API_BASE!,
|
|
23
|
+
apiKey: process.env.MENTIONWELL_API_KEY!,
|
|
24
|
+
siteSlug: process.env.MENTIONWELL_SITE_SLUG!
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const fetchBlogPost = (slug: string) => getBlogPostViaApi(config, slug)
|
|
28
|
+
export const fetchBlogPosts = (page = 1) => getBlogPostsViaApi(config, page, 12)
|
|
29
|
+
export const fetchAllSlugs = () => getAllBlogSlugsViaApi(config)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// app/blog/[slug]/page.tsx
|
|
34
|
+
import 'mentionwell/styles'
|
|
35
|
+
import {
|
|
36
|
+
prepareArticleHtml,
|
|
37
|
+
extractTocHtml,
|
|
38
|
+
stripTocFromArticle
|
|
39
|
+
} from 'mentionwell/html-utils'
|
|
40
|
+
import { fetchBlogPost } from '@/lib/blogoto-blog'
|
|
41
|
+
|
|
42
|
+
export default async function ArticlePage({ params }) {
|
|
43
|
+
const { slug } = await params
|
|
44
|
+
const post = await fetchBlogPost(slug)
|
|
45
|
+
if (!post) return <NotFound />
|
|
46
|
+
|
|
47
|
+
const html = prepareArticleHtml(post.html)
|
|
48
|
+
const tocHtml = extractTocHtml(html)
|
|
49
|
+
const bodyHtml = stripTocFromArticle(html)
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<article className="grid lg:grid-cols-[220px_minmax(0,1fr)_240px] gap-10 max-w-[1200px] mx-auto">
|
|
53
|
+
<aside className="hidden lg:block">
|
|
54
|
+
{tocHtml && (
|
|
55
|
+
<div
|
|
56
|
+
className="sticky top-40 wb-toc-sidebar"
|
|
57
|
+
dangerouslySetInnerHTML={{ __html: tocHtml }}
|
|
58
|
+
/>
|
|
59
|
+
)}
|
|
60
|
+
</aside>
|
|
61
|
+
|
|
62
|
+
<div className="wb-article-host" dangerouslySetInnerHTML={{ __html: bodyHtml }} />
|
|
63
|
+
|
|
64
|
+
<aside className="hidden lg:block">
|
|
65
|
+
<div className="sticky top-40 rounded-xl bg-slate-900 text-white p-5">
|
|
66
|
+
<h3>Talk to us</h3>
|
|
67
|
+
<a href="/contact">Contact</a>
|
|
68
|
+
</div>
|
|
69
|
+
</aside>
|
|
70
|
+
</article>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## What you get for free
|
|
76
|
+
|
|
77
|
+
The Mentionwell pipeline emits a self-contained article with all of these elements; this package's CSS styles them under the `wb-*` namespace inside `.wb-article-host`:
|
|
78
|
+
|
|
79
|
+
- **TL;DR** key-takeaways aside
|
|
80
|
+
- **Table of contents** (H2-only entries; H3s remain anchor targets)
|
|
81
|
+
- **Tables** with forest-green header and zebra rows
|
|
82
|
+
- **Pull quotes** (`<blockquote class="wb-quote">`)
|
|
83
|
+
- **Tip / Watch out / By the numbers callouts** (green / amber / purple boxes with emoji)
|
|
84
|
+
- **Numbered process steps** (`<ol class="wb-steps">`) with green numbered chips
|
|
85
|
+
- **YouTube embeds** as cards with eyebrow + title + channel subtitle + responsive iframe
|
|
86
|
+
- **Inline / footer CTAs** with bronze button
|
|
87
|
+
- **FAQ accordion** (`<details>` with custom rotating chevron)
|
|
88
|
+
- **Citations** numbered source list
|
|
89
|
+
- **Author block**
|
|
90
|
+
|
|
91
|
+
## Re-theming
|
|
92
|
+
|
|
93
|
+
Override the CSS variables anywhere in your global stylesheet:
|
|
94
|
+
|
|
95
|
+
```css
|
|
96
|
+
:root {
|
|
97
|
+
--wb-ink: #111;
|
|
98
|
+
--wb-bronze: #c47a40;
|
|
99
|
+
--wb-forest: #1f4a32;
|
|
100
|
+
--wb-paper: #fafafa;
|
|
101
|
+
--wb-font-display: 'Your Display Font', serif;
|
|
102
|
+
--wb-font-body: 'Your Body Font', sans-serif;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
All wb-* classes resolve through these tokens.
|
|
107
|
+
|
|
108
|
+
## API
|
|
109
|
+
|
|
110
|
+
### `getBlogPostsViaApi(config, page?, perPage?)`
|
|
111
|
+
|
|
112
|
+
Returns a paginated list of published posts through the Mentionwell hosted API using `Authorization: Bearer ***
|
|
113
|
+
|
|
114
|
+
### `getBlogPostViaApi(config, slug)`
|
|
115
|
+
|
|
116
|
+
Returns one published post from the hosted API, or `null`.
|
|
117
|
+
|
|
118
|
+
### `getAllBlogSlugsViaApi(config)`
|
|
119
|
+
|
|
120
|
+
Returns every published slug from the hosted API. Useful for sitemaps.
|
|
121
|
+
|
|
122
|
+
### `getBlogPosts(config, page?, perPage?)`
|
|
123
|
+
|
|
124
|
+
Legacy Supabase-direct reader. Prefer the API reader above unless you intentionally want the destination site to hold Supabase credentials.
|
|
125
|
+
|
|
126
|
+
### `getBlogPost(config, slug)`
|
|
127
|
+
|
|
128
|
+
Returns one published post, or `null`.
|
|
129
|
+
|
|
130
|
+
### `getAllBlogSlugs(config)`
|
|
131
|
+
|
|
132
|
+
Returns every published slug. Useful for sitemaps.
|
|
133
|
+
|
|
134
|
+
### `getBlogPostSummaries(config)`
|
|
135
|
+
|
|
136
|
+
Returns slug + updated/published timestamps for every published post. Useful for sitemap last-modified.
|
|
137
|
+
|
|
138
|
+
### `prepareArticleHtml(html)`
|
|
139
|
+
|
|
140
|
+
Strips duplicate chrome blocks (`<header class="wb-header">`, `<figure class="wb-hero">`) and any leaked `[CTA]` placeholder tokens from the article HTML before injection.
|
|
141
|
+
|
|
142
|
+
### `extractTocHtml(html)` / `stripTocFromArticle(html)`
|
|
143
|
+
|
|
144
|
+
Pull the inline `<nav class="wb-toc">` out so you can mount it in a sidebar layout.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API-mode reader for Blogoto.
|
|
3
|
+
*
|
|
4
|
+
* Talks to a hosted Blogoto instance over HTTPS instead of going directly to
|
|
5
|
+
* Supabase. This is the recommended mode when you don't want destination
|
|
6
|
+
* sites to know about your Supabase URL/anon key.
|
|
7
|
+
*
|
|
8
|
+
* Endpoints (provided by Blogoto `app/api/public/[siteSlug]/...`):
|
|
9
|
+
* GET /api/public/{siteSlug}/posts?limit=100 → list
|
|
10
|
+
* GET /api/public/{siteSlug}/posts/{slug} → detail
|
|
11
|
+
*
|
|
12
|
+
* Auth: `Authorization: Bearer <apiKey>` where apiKey is the per-site key
|
|
13
|
+
* surfaced in the Blogoto dashboard's integration page (deterministically
|
|
14
|
+
* derived from siteId + READER_API_SECRET, prefix `bgo_read_`).
|
|
15
|
+
*/
|
|
16
|
+
import type { ReaderPost } from "./index.js";
|
|
17
|
+
export interface BlogotoApiConfig {
|
|
18
|
+
/** Base URL of the Mentionwell instance, e.g. https://app.mentionwell.com */
|
|
19
|
+
apiUrl: string;
|
|
20
|
+
/** Per-site API key (Bearer token), prefix `bgo_read_`. */
|
|
21
|
+
apiKey: string;
|
|
22
|
+
/** Slug of this destination site as registered in Mentionwell. */
|
|
23
|
+
siteSlug: string;
|
|
24
|
+
/** Optional fetch override (Next.js fetch with `next.revalidate` / `cache` works). */
|
|
25
|
+
fetch?: typeof fetch;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Fetches a paginated list of published posts for the configured site.
|
|
29
|
+
*
|
|
30
|
+
* Pagination is currently client-side: we fetch up to `perPage * page`
|
|
31
|
+
* posts (capped server-side at 100 per request) and slice. This is fine
|
|
32
|
+
* for typical destination sites (<100 posts); larger sites should add a
|
|
33
|
+
* server-side `?page=` parameter to the API route and bump the package.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getBlogPostsViaApi(config: BlogotoApiConfig, page?: number, perPage?: number): Promise<{
|
|
36
|
+
posts: ReaderPost[];
|
|
37
|
+
total: number;
|
|
38
|
+
totalPages: number;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function getBlogPostViaApi(config: BlogotoApiConfig, slug: string): Promise<ReaderPost | null>;
|
|
41
|
+
export declare function getAllBlogSlugsViaApi(config: BlogotoApiConfig): Promise<string[]>;
|
|
42
|
+
export declare function getBlogPostSummariesViaApi(config: BlogotoApiConfig): Promise<Array<{
|
|
43
|
+
slug: string;
|
|
44
|
+
updatedAt?: string;
|
|
45
|
+
publishedAt?: string;
|
|
46
|
+
}>>;
|
|
47
|
+
//# sourceMappingURL=api-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-reader.d.ts","sourceRoot":"","sources":["../src/api-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAmGD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,gBAAgB,EACxB,IAAI,SAAI,EACR,OAAO,SAAK,GACX,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBrE;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAa5B;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOvF;AAED,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAW5E"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API-mode reader for Blogoto.
|
|
3
|
+
*
|
|
4
|
+
* Talks to a hosted Blogoto instance over HTTPS instead of going directly to
|
|
5
|
+
* Supabase. This is the recommended mode when you don't want destination
|
|
6
|
+
* sites to know about your Supabase URL/anon key.
|
|
7
|
+
*
|
|
8
|
+
* Endpoints (provided by Blogoto `app/api/public/[siteSlug]/...`):
|
|
9
|
+
* GET /api/public/{siteSlug}/posts?limit=100 → list
|
|
10
|
+
* GET /api/public/{siteSlug}/posts/{slug} → detail
|
|
11
|
+
*
|
|
12
|
+
* Auth: `Authorization: Bearer <apiKey>` where apiKey is the per-site key
|
|
13
|
+
* surfaced in the Blogoto dashboard's integration page (deterministically
|
|
14
|
+
* derived from siteId + READER_API_SECRET, prefix `bgo_read_`).
|
|
15
|
+
*/
|
|
16
|
+
function trimUrl(url) {
|
|
17
|
+
return url.replace(/\/+$/, "");
|
|
18
|
+
}
|
|
19
|
+
function toReader(post) {
|
|
20
|
+
return {
|
|
21
|
+
// The public API doesn't expose internal ids; slug is unique per site
|
|
22
|
+
// and is what destinations actually key off of in routes/UI.
|
|
23
|
+
id: post.slug,
|
|
24
|
+
slug: post.slug,
|
|
25
|
+
title: post.title,
|
|
26
|
+
metaTitle: post.metaTitle,
|
|
27
|
+
metaDescription: post.metaDescription,
|
|
28
|
+
excerpt: post.excerpt ?? "",
|
|
29
|
+
html: post.html ?? "",
|
|
30
|
+
markdown: post.markdown,
|
|
31
|
+
featuredImage: post.featuredImage ?? undefined,
|
|
32
|
+
readingTime: post.readingTime,
|
|
33
|
+
tags: post.tags ?? [],
|
|
34
|
+
category: post.category ?? null,
|
|
35
|
+
relatedPosts: [],
|
|
36
|
+
publishedAt: post.publishedAt ?? undefined,
|
|
37
|
+
updatedAt: post.updatedAt ?? undefined,
|
|
38
|
+
author: post.author ?? undefined,
|
|
39
|
+
tldr: post.tldr ?? undefined,
|
|
40
|
+
toc: post.toc ?? undefined,
|
|
41
|
+
faqs: post.faqs ?? undefined,
|
|
42
|
+
schema: post.jsonLd ? { jsonLd: post.jsonLd } : undefined,
|
|
43
|
+
canonicalUrl: post.canonicalUrl ?? undefined
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function callApi(config, path, init) {
|
|
47
|
+
const fetchImpl = config.fetch ?? fetch;
|
|
48
|
+
const url = `${trimUrl(config.apiUrl)}${path}`;
|
|
49
|
+
const response = await fetchImpl(url, {
|
|
50
|
+
...init,
|
|
51
|
+
headers: {
|
|
52
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
53
|
+
Accept: "application/json",
|
|
54
|
+
...(init?.headers ?? {})
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
let message = `${response.status} ${response.statusText}`;
|
|
59
|
+
try {
|
|
60
|
+
const body = (await response.json());
|
|
61
|
+
if (body?.message)
|
|
62
|
+
message = body.message;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// body wasn't JSON; keep status text
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`Blogoto API ${path} failed: ${message}`);
|
|
68
|
+
}
|
|
69
|
+
return (await response.json());
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Fetches a paginated list of published posts for the configured site.
|
|
73
|
+
*
|
|
74
|
+
* Pagination is currently client-side: we fetch up to `perPage * page`
|
|
75
|
+
* posts (capped server-side at 100 per request) and slice. This is fine
|
|
76
|
+
* for typical destination sites (<100 posts); larger sites should add a
|
|
77
|
+
* server-side `?page=` parameter to the API route and bump the package.
|
|
78
|
+
*/
|
|
79
|
+
export async function getBlogPostsViaApi(config, page = 1, perPage = 12) {
|
|
80
|
+
const limit = Math.min(100, perPage * page);
|
|
81
|
+
const data = await callApi(config, `/api/public/${encodeURIComponent(config.siteSlug)}/posts?limit=${limit}`);
|
|
82
|
+
if (!data.ok || !data.posts) {
|
|
83
|
+
throw new Error(`Blogoto API returned not-ok for ${config.siteSlug}: ${data.message ?? "unknown"}`);
|
|
84
|
+
}
|
|
85
|
+
const all = data.posts.map(toReader);
|
|
86
|
+
const from = (page - 1) * perPage;
|
|
87
|
+
const slice = all.slice(from, from + perPage);
|
|
88
|
+
const total = all.length;
|
|
89
|
+
return {
|
|
90
|
+
posts: slice,
|
|
91
|
+
total,
|
|
92
|
+
totalPages: Math.max(1, Math.ceil(total / perPage))
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export async function getBlogPostViaApi(config, slug) {
|
|
96
|
+
try {
|
|
97
|
+
const data = await callApi(config, `/api/public/${encodeURIComponent(config.siteSlug)}/posts/${encodeURIComponent(slug)}`);
|
|
98
|
+
if (!data.ok || !data.post)
|
|
99
|
+
return null;
|
|
100
|
+
return toReader(data.post);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
// 404 from the API throws above; surface as null so callers can `notFound()`.
|
|
104
|
+
if (e instanceof Error && /failed: .*not found/i.test(e.message))
|
|
105
|
+
return null;
|
|
106
|
+
throw e;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export async function getAllBlogSlugsViaApi(config) {
|
|
110
|
+
const data = await callApi(config, `/api/public/${encodeURIComponent(config.siteSlug)}/posts?limit=100`);
|
|
111
|
+
if (!data.ok || !data.posts)
|
|
112
|
+
return [];
|
|
113
|
+
return data.posts.map((p) => p.slug);
|
|
114
|
+
}
|
|
115
|
+
export async function getBlogPostSummariesViaApi(config) {
|
|
116
|
+
const data = await callApi(config, `/api/public/${encodeURIComponent(config.siteSlug)}/posts?limit=100`);
|
|
117
|
+
if (!data.ok || !data.posts)
|
|
118
|
+
return [];
|
|
119
|
+
return data.posts.map((p) => ({
|
|
120
|
+
slug: p.slug,
|
|
121
|
+
updatedAt: p.updatedAt ?? undefined,
|
|
122
|
+
publishedAt: p.publishedAt ?? undefined
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=api-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-reader.js","sourceRoot":"","sources":["../src/api-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAkDH,SAAS,OAAO,CAAC,GAAW;IAC1B,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,QAAQ,CAAC,IAAa;IAC7B,OAAO;QACL,sEAAsE;QACtE,6DAA6D;QAC7D,EAAE,EAAE,IAAI,CAAC,IAAI;QACb,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,EAAE;QAC3B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,SAAS;QAC9C,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI;QAC/B,YAAY,EAAE,EAAE;QAChB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,SAAS;QAC1C,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS;QACtC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS;QAChC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,SAAS;QAC5B,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,SAAS;QAC1B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,SAAS;QAC5B,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS;QACzD,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,SAAS;KAC7C,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,OAAO,CACpB,MAAwB,EACxB,IAAY,EACZ,IAAkB;IAElB,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,IAAI,KAAK,CAAC;IACxC,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IAC/C,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;QACpC,GAAG,IAAI;QACP,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE;YACxC,MAAM,EAAE,kBAAkB;YAC1B,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC;SACzB;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,IAAI,OAAO,GAAG,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;QAC1D,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAyB,CAAC;YAC7D,IAAI,IAAI,EAAE,OAAO;gBAAE,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,qCAAqC;QACvC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,YAAY,OAAO,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;AACtC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAwB,EACxB,IAAI,GAAG,CAAC,EACR,OAAO,GAAG,EAAE;IAEZ,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,MAAM,OAAO,CACxB,MAAM,EACN,eAAe,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAK,EAAE,CAC1E,CAAC;IAEF,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,mCAAmC,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,OAAO,IAAI,SAAS,EAAE,CAAC,CAAC;IACtG,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;IAClC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC;IACzB,OAAO;QACL,KAAK,EAAE,KAAK;QACZ,KAAK;QACL,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC;KACpD,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAwB,EACxB,IAAY;IAEZ,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,CACxB,MAAM,EACN,eAAe,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,kBAAkB,CAAC,IAAI,CAAC,EAAE,CACvF,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACxC,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,8EAA8E;QAC9E,IAAI,CAAC,YAAY,KAAK,IAAI,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9E,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,MAAwB;IAClE,MAAM,IAAI,GAAG,MAAM,OAAO,CACxB,MAAM,EACN,eAAe,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CACrE,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACvC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,MAAwB;IAExB,MAAM,IAAI,GAAG,MAAM,OAAO,CACxB,MAAM,EACN,eAAe,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CACrE,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACvC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,SAAS;QACnC,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,SAAS;KACxC,CAAC,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for rendering Blogoto-generated article HTML on a destination site.
|
|
3
|
+
*
|
|
4
|
+
* Blogoto's `buildArticleHtml` produces a self-contained article with a
|
|
5
|
+
* `<header class="wb-header">` (title + dek + tags) and a
|
|
6
|
+
* `<figure class="wb-hero">` (cover image). Most destination templates also
|
|
7
|
+
* render their own hero/title/cover, so injecting `post.html` raw double-
|
|
8
|
+
* renders these. `stripDuplicateChrome` removes the redundant blocks so the
|
|
9
|
+
* destination template owns the chrome.
|
|
10
|
+
*
|
|
11
|
+
* `extractTocHtml` + `stripTocFromArticle` let a 3-column page template host
|
|
12
|
+
* the table of contents in a sidebar instead of inline.
|
|
13
|
+
*
|
|
14
|
+
* `stripLegacyCtaLeaks` strips literal `[CTA]` placeholder tokens that older
|
|
15
|
+
* articles (generated before the prompt was hardened) sometimes leaked into
|
|
16
|
+
* the body.
|
|
17
|
+
*/
|
|
18
|
+
export declare function stripDuplicateChrome(html: string): string;
|
|
19
|
+
export declare function stripLegacyCtaLeaks(html: string): string;
|
|
20
|
+
export declare function extractTocHtml(html: string): string | null;
|
|
21
|
+
export declare function stripTocFromArticle(html: string): string;
|
|
22
|
+
/** Convenience: apply all the safe transforms in one call. */
|
|
23
|
+
export declare function prepareArticleHtml(html: string): string;
|
|
24
|
+
//# sourceMappingURL=html-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-utils.d.ts","sourceRoot":"","sources":["../src/html-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKxD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG1D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for rendering Blogoto-generated article HTML on a destination site.
|
|
3
|
+
*
|
|
4
|
+
* Blogoto's `buildArticleHtml` produces a self-contained article with a
|
|
5
|
+
* `<header class="wb-header">` (title + dek + tags) and a
|
|
6
|
+
* `<figure class="wb-hero">` (cover image). Most destination templates also
|
|
7
|
+
* render their own hero/title/cover, so injecting `post.html` raw double-
|
|
8
|
+
* renders these. `stripDuplicateChrome` removes the redundant blocks so the
|
|
9
|
+
* destination template owns the chrome.
|
|
10
|
+
*
|
|
11
|
+
* `extractTocHtml` + `stripTocFromArticle` let a 3-column page template host
|
|
12
|
+
* the table of contents in a sidebar instead of inline.
|
|
13
|
+
*
|
|
14
|
+
* `stripLegacyCtaLeaks` strips literal `[CTA]` placeholder tokens that older
|
|
15
|
+
* articles (generated before the prompt was hardened) sometimes leaked into
|
|
16
|
+
* the body.
|
|
17
|
+
*/
|
|
18
|
+
export function stripDuplicateChrome(html) {
|
|
19
|
+
return html
|
|
20
|
+
.replace(/<header class="wb-header"[\s\S]*?<\/header>/g, "")
|
|
21
|
+
.replace(/<figure class="wb-hero"[\s\S]*?<\/figure>/g, "")
|
|
22
|
+
.replace(/<figure class="wb-featured"[\s\S]*?<\/figure>/g, "");
|
|
23
|
+
}
|
|
24
|
+
export function stripLegacyCtaLeaks(html) {
|
|
25
|
+
return html
|
|
26
|
+
.replace(/<blockquote[^>]*>[\s\S]*?\[CTA\][\s\S]*?<\/blockquote>/g, "")
|
|
27
|
+
.replace(/<strong>\s*\[CTA\]\s*<\/strong>/g, "")
|
|
28
|
+
.replace(/\[CTA\]/g, "");
|
|
29
|
+
}
|
|
30
|
+
export function extractTocHtml(html) {
|
|
31
|
+
const match = html.match(/<nav class="wb-toc"[\s\S]*?<\/nav>/);
|
|
32
|
+
return match ? match[0] : null;
|
|
33
|
+
}
|
|
34
|
+
export function stripTocFromArticle(html) {
|
|
35
|
+
return html.replace(/<nav class="wb-toc"[\s\S]*?<\/nav>/, "");
|
|
36
|
+
}
|
|
37
|
+
/** Convenience: apply all the safe transforms in one call. */
|
|
38
|
+
export function prepareArticleHtml(html) {
|
|
39
|
+
return stripLegacyCtaLeaks(stripDuplicateChrome(html));
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=html-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-utils.js","sourceRoot":"","sources":["../src/html-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAO,IAAI;SACR,OAAO,CAAC,8CAA8C,EAAE,EAAE,CAAC;SAC3D,OAAO,CAAC,4CAA4C,EAAE,EAAE,CAAC;SACzD,OAAO,CAAC,gDAAgD,EAAE,EAAE,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI;SACR,OAAO,CAAC,yDAAyD,EAAE,EAAE,CAAC;SACtE,OAAO,CAAC,kCAAkC,EAAE,EAAE,CAAC;SAC/C,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;IAC/D,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI,CAAC,OAAO,CAAC,oCAAoC,EAAE,EAAE,CAAC,CAAC;AAChE,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,mBAAmB,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC;AACzD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export interface BlogReaderConfig {
|
|
2
|
+
supabaseUrl: string;
|
|
3
|
+
supabaseAnonKey: string;
|
|
4
|
+
siteSlug: string;
|
|
5
|
+
table?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ReaderAuthor {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
bio?: string;
|
|
12
|
+
avatarUrl?: string;
|
|
13
|
+
url?: string;
|
|
14
|
+
twitter?: string;
|
|
15
|
+
linkedin?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ReaderFaq {
|
|
18
|
+
question: string;
|
|
19
|
+
answer: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ReaderTocEntry {
|
|
22
|
+
title: string;
|
|
23
|
+
id: string;
|
|
24
|
+
level: 2 | 3;
|
|
25
|
+
}
|
|
26
|
+
export interface ReaderPost {
|
|
27
|
+
id: string;
|
|
28
|
+
slug: string;
|
|
29
|
+
title: string;
|
|
30
|
+
metaTitle?: string;
|
|
31
|
+
metaDescription?: string;
|
|
32
|
+
excerpt: string;
|
|
33
|
+
html: string;
|
|
34
|
+
markdown?: string;
|
|
35
|
+
featuredImage?: string;
|
|
36
|
+
readingTime?: number;
|
|
37
|
+
tags: string[];
|
|
38
|
+
category?: {
|
|
39
|
+
title: string;
|
|
40
|
+
slug: string;
|
|
41
|
+
} | null;
|
|
42
|
+
relatedPosts: Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
title: string;
|
|
45
|
+
slug: string;
|
|
46
|
+
}>;
|
|
47
|
+
publishedAt?: string;
|
|
48
|
+
updatedAt?: string;
|
|
49
|
+
author?: ReaderAuthor;
|
|
50
|
+
tldr?: {
|
|
51
|
+
items: string[];
|
|
52
|
+
};
|
|
53
|
+
toc?: ReaderTocEntry[];
|
|
54
|
+
faqs?: ReaderFaq[];
|
|
55
|
+
schema?: {
|
|
56
|
+
jsonLd: string;
|
|
57
|
+
};
|
|
58
|
+
canonicalUrl?: string;
|
|
59
|
+
}
|
|
60
|
+
export declare function getBlogPosts(config: BlogReaderConfig, page?: number, perPage?: number): Promise<{
|
|
61
|
+
posts: ReaderPost[];
|
|
62
|
+
total: number;
|
|
63
|
+
totalPages: number;
|
|
64
|
+
}>;
|
|
65
|
+
export declare function getBlogPost(config: BlogReaderConfig, slug: string): Promise<ReaderPost | null>;
|
|
66
|
+
export declare function getAllBlogSlugs(config: BlogReaderConfig): Promise<string[]>;
|
|
67
|
+
export declare function getBlogPostSummaries(config: BlogReaderConfig): Promise<Array<{
|
|
68
|
+
slug: string;
|
|
69
|
+
updatedAt?: string;
|
|
70
|
+
publishedAt?: string;
|
|
71
|
+
}>>;
|
|
72
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClD,YAAY,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC3B,GAAG,CAAC,EAAE,cAAc,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAmCD,wBAAsB,YAAY,CAChC,MAAM,EAAE,gBAAgB,EACxB,IAAI,SAAI,EACR,OAAO,SAAK,GACX,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAuBrE;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAiB5B;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAejF;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAsB5E"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createClient } from "@supabase/supabase-js";
|
|
2
|
+
function getClient(config) {
|
|
3
|
+
return createClient(config.supabaseUrl, config.supabaseAnonKey);
|
|
4
|
+
}
|
|
5
|
+
const SELECT_COLUMNS = "id, slug, title, meta_title, meta_description, excerpt, html, markdown, featured_image, reading_time, tags, category, related_posts, published_at, updated_at, author, tldr, toc, faqs, schema, canonical_url";
|
|
6
|
+
function toReaderPost(row) {
|
|
7
|
+
return {
|
|
8
|
+
id: String(row.id),
|
|
9
|
+
slug: String(row.slug),
|
|
10
|
+
title: String(row.title),
|
|
11
|
+
metaTitle: (row.meta_title ?? row.metaTitle),
|
|
12
|
+
metaDescription: (row.meta_description ?? row.metaDescription),
|
|
13
|
+
excerpt: String(row.excerpt ?? ""),
|
|
14
|
+
html: String(row.html ?? ""),
|
|
15
|
+
markdown: (row.markdown ?? ""),
|
|
16
|
+
featuredImage: (row.featured_image ?? row.featuredImage),
|
|
17
|
+
readingTime: (row.reading_time ?? row.readingTime),
|
|
18
|
+
tags: row.tags ?? [],
|
|
19
|
+
category: row.category ?? null,
|
|
20
|
+
relatedPosts: (row.related_posts ?? row.relatedPosts) ?? [],
|
|
21
|
+
publishedAt: (row.published_at ?? row.publishedAt),
|
|
22
|
+
updatedAt: (row.updated_at ?? row.updatedAt),
|
|
23
|
+
author: row.author ?? undefined,
|
|
24
|
+
tldr: row.tldr ?? undefined,
|
|
25
|
+
toc: row.toc ?? undefined,
|
|
26
|
+
faqs: row.faqs ?? undefined,
|
|
27
|
+
schema: row.schema ?? undefined,
|
|
28
|
+
canonicalUrl: (row.canonical_url ?? row.canonicalUrl)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export async function getBlogPosts(config, page = 1, perPage = 12) {
|
|
32
|
+
const client = getClient(config);
|
|
33
|
+
const from = (page - 1) * perPage;
|
|
34
|
+
const to = from + perPage - 1;
|
|
35
|
+
const table = config.table ?? "blog_posts";
|
|
36
|
+
const { data, count, error } = await client
|
|
37
|
+
.from(table)
|
|
38
|
+
.select(SELECT_COLUMNS, { count: "exact" })
|
|
39
|
+
.eq("published", true)
|
|
40
|
+
.eq("site_slug", config.siteSlug)
|
|
41
|
+
.order("published_at", { ascending: false })
|
|
42
|
+
.range(from, to);
|
|
43
|
+
if (error) {
|
|
44
|
+
throw new Error(`Failed to fetch posts for ${config.siteSlug}: ${error.message}`);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
posts: (data ?? []).map(toReaderPost),
|
|
48
|
+
total: count ?? 0,
|
|
49
|
+
totalPages: Math.max(1, Math.ceil((count ?? 0) / perPage))
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export async function getBlogPost(config, slug) {
|
|
53
|
+
const client = getClient(config);
|
|
54
|
+
const table = config.table ?? "blog_posts";
|
|
55
|
+
const { data, error } = await client
|
|
56
|
+
.from(table)
|
|
57
|
+
.select(SELECT_COLUMNS)
|
|
58
|
+
.eq("published", true)
|
|
59
|
+
.eq("site_slug", config.siteSlug)
|
|
60
|
+
.eq("slug", slug)
|
|
61
|
+
.maybeSingle();
|
|
62
|
+
if (error) {
|
|
63
|
+
throw new Error(`Failed to fetch slug ${slug}: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
if (!data)
|
|
66
|
+
return null;
|
|
67
|
+
return toReaderPost(data);
|
|
68
|
+
}
|
|
69
|
+
export async function getAllBlogSlugs(config) {
|
|
70
|
+
const client = getClient(config);
|
|
71
|
+
const table = config.table ?? "blog_posts";
|
|
72
|
+
const { data, error } = await client
|
|
73
|
+
.from(table)
|
|
74
|
+
.select("slug, updated_at, published_at")
|
|
75
|
+
.eq("published", true)
|
|
76
|
+
.eq("site_slug", config.siteSlug)
|
|
77
|
+
.order("published_at", { ascending: false });
|
|
78
|
+
if (error) {
|
|
79
|
+
throw new Error(`Failed to fetch slugs for ${config.siteSlug}: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
return (data ?? []).map((row) => String(row.slug));
|
|
82
|
+
}
|
|
83
|
+
export async function getBlogPostSummaries(config) {
|
|
84
|
+
const client = getClient(config);
|
|
85
|
+
const table = config.table ?? "blog_posts";
|
|
86
|
+
const { data, error } = await client
|
|
87
|
+
.from(table)
|
|
88
|
+
.select("slug, updated_at, published_at")
|
|
89
|
+
.eq("published", true)
|
|
90
|
+
.eq("site_slug", config.siteSlug)
|
|
91
|
+
.order("published_at", { ascending: false });
|
|
92
|
+
if (error) {
|
|
93
|
+
throw new Error(`Failed to fetch summaries for ${config.siteSlug}: ${error.message}`);
|
|
94
|
+
}
|
|
95
|
+
return (data ?? []).map((row) => {
|
|
96
|
+
const record = row;
|
|
97
|
+
return {
|
|
98
|
+
slug: String(record.slug),
|
|
99
|
+
updatedAt: (record.updated_at ?? undefined),
|
|
100
|
+
publishedAt: (record.published_at ?? undefined)
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|