embed-dlsurf-blogs 1.0.0 โ 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -128
- package/dist/index.d.mts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +104 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +104 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,63 +1,61 @@
|
|
|
1
1
|
# `embed-dlsurf-blogs`
|
|
2
2
|
|
|
3
|
-
Render dlsurf blog
|
|
3
|
+
Render dlsurf blog posts with a single React component.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Quick Start
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
You do not need to fetch API data manually first.
|
|
8
|
+
|
|
9
|
+
Pass either:
|
|
10
|
+
|
|
11
|
+
- `blogUrl`, or
|
|
12
|
+
- `user` + `linkSlug`
|
|
13
|
+
|
|
14
|
+
The component fetches from:
|
|
8
15
|
|
|
9
16
|
`https://docapi.dl.surf/api/doc/{user}/{linkSlug}`
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
internally.
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
https://docapi.dl.surf/api/doc/rishav/why-django-is-a-game-changer-in-modern-web-development
|
|
15
|
-
```
|
|
20
|
+
## API Response Shape (for reference)
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
The upstream doc API returns:
|
|
18
23
|
|
|
19
24
|
```json
|
|
20
25
|
{
|
|
21
26
|
"status": "success",
|
|
22
27
|
"message": "Document retrieved successfully",
|
|
23
28
|
"data": {
|
|
24
|
-
"id":
|
|
25
|
-
"title": "
|
|
26
|
-
"content_json": "{\"type\":\"doc\",\"content\":[...]}",
|
|
27
|
-
"thumbnail_path": "
|
|
28
|
-
"keywords": "[
|
|
29
|
+
"id": 1345,
|
|
30
|
+
"title": "6 Years of dlsurf, 300000+ Users & Counting",
|
|
31
|
+
"content_json": "{\"type\":\"doc\",\"content\":[{\"type\":\"paragraph\",\"content\":[{\"text\":\"Happy New Year 2026 ๐ฅณ\",\"type\":\"text\"}]},{\"type\":\"paragraph\",\"content\":[{\"text\":\"It has been an incredible journey of building dlsurf...\",\"type\":\"text\"}]}, ...]}",
|
|
32
|
+
"thumbnail_path": "media/default_thumbnail/thumbnail_b2.png",
|
|
33
|
+
"keywords": "[]",
|
|
29
34
|
"followers_only": false,
|
|
30
35
|
"visibility": "public",
|
|
31
|
-
"created_at": "
|
|
32
|
-
"link_slug": "
|
|
33
|
-
"updated_at": "
|
|
36
|
+
"created_at": "2026-01-01T08:02:57.253021Z",
|
|
37
|
+
"link_slug": "happy-new-year-6-years-of-building-dlsurf-300000-users-and-counting",
|
|
38
|
+
"updated_at": "2026-01-01T10:16:04.803993Z",
|
|
34
39
|
"profile": {
|
|
35
|
-
"username": "
|
|
36
|
-
"display_name": "
|
|
40
|
+
"username": "arjun",
|
|
41
|
+
"display_name": "Arjun Ghimire",
|
|
37
42
|
"account_level": "6",
|
|
38
|
-
"profile_picture": "/media/profile_picture/
|
|
43
|
+
"profile_picture": "/media/profile_picture/efd953bb1b.jpeg"
|
|
39
44
|
},
|
|
40
|
-
"ads_step":
|
|
45
|
+
"ads_step": 0,
|
|
41
46
|
"banner_ads": false,
|
|
42
47
|
"video_ads": false
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
```
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
## 2. Pre-requirements
|
|
50
|
-
|
|
51
|
-
- `node >= 18`
|
|
52
|
-
- React app (`react >= 18`)
|
|
53
|
-
- TypeScript recommended
|
|
54
|
-
|
|
55
|
-
Library peer dependencies:
|
|
52
|
+
## Pre-requirements
|
|
56
53
|
|
|
57
|
-
- `
|
|
58
|
-
-
|
|
54
|
+
- Node.js `>= 18`
|
|
55
|
+
- React `>= 18`
|
|
56
|
+
- `@tiptap/core` (peer dependency)
|
|
59
57
|
|
|
60
|
-
##
|
|
58
|
+
## Install
|
|
61
59
|
|
|
62
60
|
```bash
|
|
63
61
|
npm install embed-dlsurf-blogs @tiptap/core
|
|
@@ -75,110 +73,58 @@ or
|
|
|
75
73
|
pnpm add embed-dlsurf-blogs @tiptap/core
|
|
76
74
|
```
|
|
77
75
|
|
|
78
|
-
##
|
|
76
|
+
## Usage Patterns
|
|
79
77
|
|
|
80
|
-
###
|
|
78
|
+
### 1. Simplest: pass `blogUrl`
|
|
81
79
|
|
|
82
80
|
```tsx
|
|
83
|
-
"use client";
|
|
84
|
-
|
|
85
|
-
import { useEffect, useState } from "react";
|
|
86
81
|
import BlogRenderer from "embed-dlsurf-blogs";
|
|
87
82
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
type BlogPostProfile = {
|
|
95
|
-
username: string;
|
|
96
|
-
display_name: string;
|
|
97
|
-
account_level: string;
|
|
98
|
-
profile_picture: string;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
type BlogPost = {
|
|
102
|
-
id: number;
|
|
103
|
-
title: string;
|
|
104
|
-
content_json: string;
|
|
105
|
-
thumbnail_path: string | null;
|
|
106
|
-
keywords: string;
|
|
107
|
-
followers_only: boolean;
|
|
108
|
-
visibility: string;
|
|
109
|
-
created_at: string;
|
|
110
|
-
link_slug: string;
|
|
111
|
-
updated_at: string;
|
|
112
|
-
profile: BlogPostProfile;
|
|
113
|
-
ads_step: number;
|
|
114
|
-
banner_ads: boolean;
|
|
115
|
-
video_ads: boolean;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
export default function BlogPage() {
|
|
119
|
-
const [post, setPost] = useState<BlogPost | null>(null);
|
|
120
|
-
const [error, setError] = useState<string | null>(null);
|
|
121
|
-
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
const run = async () => {
|
|
124
|
-
try {
|
|
125
|
-
const res = await fetch(
|
|
126
|
-
"https://docapi.dl.surf/api/doc/rishav/why-django-is-a-game-changer-in-modern-web-development",
|
|
127
|
-
);
|
|
128
|
-
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
|
|
129
|
-
|
|
130
|
-
const json: DocApiResponse = await res.json();
|
|
131
|
-
setPost(json.data);
|
|
132
|
-
} catch (e) {
|
|
133
|
-
setError(e instanceof Error ? e.message : "Unknown error");
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
run();
|
|
138
|
-
}, []);
|
|
139
|
-
|
|
140
|
-
if (error) return <p>{error}</p>;
|
|
141
|
-
if (!post) return <p>Loading...</p>;
|
|
142
|
-
|
|
143
|
-
return <BlogRenderer post={post} />;
|
|
83
|
+
export default function Page() {
|
|
84
|
+
return (
|
|
85
|
+
<BlogRenderer blogUrl="https://docapi.dl.surf/api/doc/rishav/why-django-is-a-game-changer-in-modern-web-development" />
|
|
86
|
+
);
|
|
144
87
|
}
|
|
145
88
|
```
|
|
146
89
|
|
|
147
|
-
|
|
90
|
+
You can also pass a non-API URL as long as the last two path segments are `{user}/{linkSlug}`.
|
|
91
|
+
|
|
92
|
+
### 2. Pass `user` + `linkSlug`
|
|
148
93
|
|
|
149
94
|
```tsx
|
|
150
95
|
import { BlogRenderer } from "embed-dlsurf-blogs";
|
|
151
96
|
|
|
152
|
-
export function
|
|
153
|
-
return
|
|
97
|
+
export default function Page() {
|
|
98
|
+
return (
|
|
99
|
+
<BlogRenderer
|
|
100
|
+
user="rishav"
|
|
101
|
+
linkSlug="why-django-is-a-game-changer-in-modern-web-development"
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
154
104
|
}
|
|
155
105
|
```
|
|
156
106
|
|
|
157
|
-
###
|
|
107
|
+
### 3. Advanced: override API base URL
|
|
158
108
|
|
|
159
109
|
```tsx
|
|
160
110
|
import BlogRenderer from "embed-dlsurf-blogs";
|
|
161
111
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (!res.ok) return <div>Not found</div>;
|
|
171
|
-
|
|
172
|
-
const json = await res.json();
|
|
173
|
-
return <BlogRenderer post={json.data} />;
|
|
112
|
+
export default function Page() {
|
|
113
|
+
return (
|
|
114
|
+
<BlogRenderer
|
|
115
|
+
user="rishav"
|
|
116
|
+
linkSlug="why-django-is-a-game-changer-in-modern-web-development"
|
|
117
|
+
apiBaseUrl="https://docapi.dl.surf/api/doc"
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
174
120
|
}
|
|
175
121
|
```
|
|
176
122
|
|
|
177
|
-
|
|
123
|
+
### 4. Optional backward-compatible pattern: pass full post object directly
|
|
178
124
|
|
|
179
|
-
|
|
125
|
+
```tsx
|
|
126
|
+
import BlogRenderer from "embed-dlsurf-blogs";
|
|
180
127
|
|
|
181
|
-
```ts
|
|
182
128
|
type BlogPostProfile = {
|
|
183
129
|
username: string;
|
|
184
130
|
display_name: string;
|
|
@@ -202,31 +148,46 @@ type BlogPost = {
|
|
|
202
148
|
banner_ads: boolean;
|
|
203
149
|
video_ads: boolean;
|
|
204
150
|
};
|
|
151
|
+
|
|
152
|
+
export default function Page({ post }: { post: BlogPost }) {
|
|
153
|
+
return <BlogRenderer post={post} />;
|
|
154
|
+
}
|
|
205
155
|
```
|
|
206
156
|
|
|
207
|
-
##
|
|
157
|
+
## Component Props
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
type BlogRendererProps = {
|
|
161
|
+
post?: BlogPost;
|
|
162
|
+
user?: string;
|
|
163
|
+
linkSlug?: string;
|
|
164
|
+
blogUrl?: string;
|
|
165
|
+
apiBaseUrl?: string;
|
|
166
|
+
};
|
|
167
|
+
```
|
|
208
168
|
|
|
209
|
-
|
|
210
|
-
- Strips outer `prose` wrappers from generated HTML.
|
|
211
|
-
- Builds an auto Table of Contents from `h2`/`h3` headings.
|
|
212
|
-
- Handles image URL normalization for `thumbnail_path`.
|
|
213
|
-
- Injects scoped component styles during build (`injectStyle: true`).
|
|
169
|
+
Notes:
|
|
214
170
|
|
|
171
|
+
- Preferred inputs are `blogUrl` or `user` + `linkSlug`.
|
|
172
|
+
- If `post` is provided, no fetch is performed.
|
|
173
|
+
- If required fetch fields are missing, the component shows an inline error message.
|
|
215
174
|
|
|
216
|
-
##
|
|
175
|
+
## Exported API
|
|
217
176
|
|
|
218
|
-
|
|
177
|
+
- Default export: `BlogRenderer`
|
|
178
|
+
- Named export: `BlogRenderer`
|
|
179
|
+
- Utility: `renderText`
|
|
219
180
|
|
|
220
|
-
|
|
221
|
-
- `{ BlogRenderer }`
|
|
222
|
-
- `{ renderText }` (utility test export)
|
|
181
|
+
## Internal Behavior
|
|
223
182
|
|
|
224
|
-
|
|
183
|
+
The component:
|
|
225
184
|
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
-
|
|
185
|
+
- Fetches post data internally when needed.
|
|
186
|
+
- Parses `content_json` via SSR-safe renderer.
|
|
187
|
+
- Strips outer `prose` wrappers.
|
|
188
|
+
- Generates a Table of Contents from `h2`/`h3`.
|
|
189
|
+
- Injects scoped styles at build time.
|
|
229
190
|
|
|
230
|
-
##
|
|
191
|
+
## License
|
|
231
192
|
|
|
232
193
|
ISC
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
export { JSONContent } from '@tiptap/core';
|
|
3
3
|
|
|
4
|
-
interface
|
|
5
|
-
post
|
|
4
|
+
interface BlogRendererProps {
|
|
5
|
+
post?: BlogPost;
|
|
6
|
+
user?: string;
|
|
7
|
+
linkSlug?: string;
|
|
8
|
+
blogUrl?: string;
|
|
6
9
|
}
|
|
7
10
|
interface BlogPostProfile {
|
|
8
11
|
username: string;
|
|
@@ -26,7 +29,7 @@ interface BlogPost {
|
|
|
26
29
|
banner_ads: boolean;
|
|
27
30
|
video_ads: boolean;
|
|
28
31
|
}
|
|
29
|
-
declare function BlogRenderer({ post }:
|
|
32
|
+
declare function BlogRenderer({ post, user, linkSlug, blogUrl, }: BlogRendererProps): react_jsx_runtime.JSX.Element;
|
|
30
33
|
|
|
31
34
|
declare function renderText(): string;
|
|
32
35
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
export { JSONContent } from '@tiptap/core';
|
|
3
3
|
|
|
4
|
-
interface
|
|
5
|
-
post
|
|
4
|
+
interface BlogRendererProps {
|
|
5
|
+
post?: BlogPost;
|
|
6
|
+
user?: string;
|
|
7
|
+
linkSlug?: string;
|
|
8
|
+
blogUrl?: string;
|
|
6
9
|
}
|
|
7
10
|
interface BlogPostProfile {
|
|
8
11
|
username: string;
|
|
@@ -26,7 +29,7 @@ interface BlogPost {
|
|
|
26
29
|
banner_ads: boolean;
|
|
27
30
|
video_ads: boolean;
|
|
28
31
|
}
|
|
29
|
-
declare function BlogRenderer({ post }:
|
|
32
|
+
declare function BlogRenderer({ post, user, linkSlug, blogUrl, }: BlogRendererProps): react_jsx_runtime.JSX.Element;
|
|
30
33
|
|
|
31
34
|
declare function renderText(): string;
|
|
32
35
|
|
package/dist/index.js
CHANGED
|
@@ -342,6 +342,32 @@ var LinkIcon = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
|
342
342
|
]
|
|
343
343
|
})
|
|
344
344
|
);
|
|
345
|
+
var DEFAULT_DOC_API_BASE_URL = "https://docapi.dl.surf/api/doc";
|
|
346
|
+
var normalizeUsername = (value) => value.trim().replace(/^@+/, "");
|
|
347
|
+
var parseBlogReference = (blogUrl) => {
|
|
348
|
+
try {
|
|
349
|
+
const url = new URL(blogUrl);
|
|
350
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
351
|
+
const apiIndex = segments.findIndex(
|
|
352
|
+
(segment, idx) => segment === "api" && segments[idx + 1] === "doc"
|
|
353
|
+
);
|
|
354
|
+
if (apiIndex >= 0 && segments.length >= apiIndex + 4) {
|
|
355
|
+
return {
|
|
356
|
+
user: decodeURIComponent(segments[apiIndex + 2]),
|
|
357
|
+
linkSlug: decodeURIComponent(segments[apiIndex + 3])
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (segments.length >= 2) {
|
|
361
|
+
return {
|
|
362
|
+
user: decodeURIComponent(segments[segments.length - 2]),
|
|
363
|
+
linkSlug: decodeURIComponent(segments[segments.length - 1])
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
} catch (e) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
345
371
|
var stripProseWrappers = (html) => {
|
|
346
372
|
let result = html;
|
|
347
373
|
const proseDiv = /^\s*<div[^>]*\bprose\b[^>]*>([\s\S]*)<\/div>\s*$/;
|
|
@@ -353,8 +379,75 @@ var stripProseWrappers = (html) => {
|
|
|
353
379
|
}
|
|
354
380
|
return result;
|
|
355
381
|
};
|
|
356
|
-
function BlogRenderer({
|
|
357
|
-
|
|
382
|
+
function BlogRenderer({
|
|
383
|
+
post,
|
|
384
|
+
user,
|
|
385
|
+
linkSlug,
|
|
386
|
+
blogUrl
|
|
387
|
+
}) {
|
|
388
|
+
const [resolvedPost, setResolvedPost] = (0, import_react.useState)(
|
|
389
|
+
post != null ? post : null
|
|
390
|
+
);
|
|
391
|
+
const [isFetchingPost, setIsFetchingPost] = (0, import_react.useState)(!post);
|
|
392
|
+
const [fetchError, setFetchError] = (0, import_react.useState)(null);
|
|
393
|
+
(0, import_react.useEffect)(() => {
|
|
394
|
+
if (post) {
|
|
395
|
+
setResolvedPost(post);
|
|
396
|
+
setIsFetchingPost(false);
|
|
397
|
+
setFetchError(null);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const directUser = user ? normalizeUsername(user) : void 0;
|
|
401
|
+
const directSlug = linkSlug == null ? void 0 : linkSlug.trim();
|
|
402
|
+
let resolvedUser = directUser;
|
|
403
|
+
let resolvedSlug = directSlug;
|
|
404
|
+
if ((!resolvedUser || !resolvedSlug) && blogUrl) {
|
|
405
|
+
const parsed = parseBlogReference(blogUrl);
|
|
406
|
+
if (parsed) {
|
|
407
|
+
resolvedUser = normalizeUsername(parsed.user);
|
|
408
|
+
resolvedSlug = parsed.linkSlug;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (!resolvedUser || !resolvedSlug) {
|
|
412
|
+
setResolvedPost(null);
|
|
413
|
+
setIsFetchingPost(false);
|
|
414
|
+
setFetchError(
|
|
415
|
+
"Provide either post, user+linkSlug, or a blogUrl with /{user}/{linkSlug}."
|
|
416
|
+
);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const controller = new AbortController();
|
|
420
|
+
const fetchPost = async () => {
|
|
421
|
+
try {
|
|
422
|
+
setIsFetchingPost(true);
|
|
423
|
+
setFetchError(null);
|
|
424
|
+
const base = DEFAULT_DOC_API_BASE_URL;
|
|
425
|
+
const endpoint = `${base}/${encodeURIComponent(resolvedUser)}/${encodeURIComponent(resolvedSlug)}`;
|
|
426
|
+
const response = await fetch(endpoint, { signal: controller.signal });
|
|
427
|
+
if (!response.ok) {
|
|
428
|
+
throw new Error(`Failed to fetch document (${response.status})`);
|
|
429
|
+
}
|
|
430
|
+
const payload = await response.json();
|
|
431
|
+
if (!(payload == null ? void 0 : payload.data)) {
|
|
432
|
+
throw new Error("Invalid API response: missing data field");
|
|
433
|
+
}
|
|
434
|
+
setResolvedPost(payload.data);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (controller.signal.aborted) return;
|
|
437
|
+
setResolvedPost(null);
|
|
438
|
+
setFetchError(
|
|
439
|
+
error instanceof Error ? error.message : "Failed to fetch document"
|
|
440
|
+
);
|
|
441
|
+
} finally {
|
|
442
|
+
if (!controller.signal.aborted) {
|
|
443
|
+
setIsFetchingPost(false);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
fetchPost();
|
|
448
|
+
return () => controller.abort();
|
|
449
|
+
}, [post, user, linkSlug, blogUrl]);
|
|
450
|
+
const rawHtml = (resolvedPost == null ? void 0 : resolvedPost.content_json) ? renderTiptapToHTML(resolvedPost.content_json) : "<p>Could not render content</p>";
|
|
358
451
|
const htmlContent = stripProseWrappers(rawHtml);
|
|
359
452
|
const articleRef = (0, import_react.useRef)(null);
|
|
360
453
|
const [renderedHtml, setRenderedHtml] = (0, import_react.useState)(htmlContent);
|
|
@@ -363,20 +456,21 @@ function BlogRenderer({ post }) {
|
|
|
363
456
|
const [tocOpen, setTocOpen] = (0, import_react.useState)(false);
|
|
364
457
|
const [scrollProgress, setScrollProgress] = (0, import_react.useState)(0);
|
|
365
458
|
const [mounted, setMounted] = (0, import_react.useState)(false);
|
|
459
|
+
const postData = resolvedPost;
|
|
366
460
|
const getFullThumbnail = (path) => {
|
|
367
461
|
if (!path) return void 0;
|
|
368
462
|
if (path.startsWith("http")) return path;
|
|
369
463
|
return `https://cdn.dl.surf/${path}`;
|
|
370
464
|
};
|
|
371
|
-
const thumbUrl = getFullThumbnail(
|
|
372
|
-
const publishDate =
|
|
465
|
+
const thumbUrl = getFullThumbnail(postData == null ? void 0 : postData.thumbnail_path);
|
|
466
|
+
const publishDate = (postData == null ? void 0 : postData.created_at) || (/* @__PURE__ */ new Date()).toISOString();
|
|
373
467
|
const formattedDate = new Date(publishDate).toLocaleDateString("en-US", {
|
|
374
468
|
year: "numeric",
|
|
375
469
|
month: "long",
|
|
376
470
|
day: "numeric"
|
|
377
471
|
});
|
|
378
472
|
const shareUrl = typeof window !== "undefined" ? window.location.href : "";
|
|
379
|
-
const shareTitle =
|
|
473
|
+
const shareTitle = (postData == null ? void 0 : postData.title) || "";
|
|
380
474
|
const shareOnTwitter = () => window.open(
|
|
381
475
|
`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}`,
|
|
382
476
|
"_blank"
|
|
@@ -457,6 +551,9 @@ function BlogRenderer({ post }) {
|
|
|
457
551
|
window.scrollTo({ top, behavior: "smooth" });
|
|
458
552
|
}, 280);
|
|
459
553
|
};
|
|
554
|
+
if (!postData) {
|
|
555
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.wrapper, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("main", { className: styles.main, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.bodyContainer, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: isFetchingPost ? "Loading blog..." : fetchError || "Could not load blog content." }) }) }) });
|
|
556
|
+
}
|
|
460
557
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.wrapper, children: [
|
|
461
558
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
462
559
|
"div",
|
|
@@ -490,7 +587,7 @@ function BlogRenderer({ post }) {
|
|
|
490
587
|
formattedDate
|
|
491
588
|
] })
|
|
492
589
|
] }),
|
|
493
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: styles.postTitle, children:
|
|
590
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { className: styles.postTitle, children: postData.title })
|
|
494
591
|
]
|
|
495
592
|
}
|
|
496
593
|
) }) }),
|
|
@@ -508,7 +605,7 @@ function BlogRenderer({ post }) {
|
|
|
508
605
|
"img",
|
|
509
606
|
{
|
|
510
607
|
src: thumbUrl,
|
|
511
|
-
alt:
|
|
608
|
+
alt: postData.title,
|
|
512
609
|
className: styles.thumbnail
|
|
513
610
|
}
|
|
514
611
|
)
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/BlogRenderer.tsx","#style-inject:#style-inject","../src/BlogRenderer.css","../src/tiptap-renderer.ts"],"sourcesContent":["export { BlogRenderer } from \"./BlogRenderer\";\nexport { BlogRenderer as default } from \"./BlogRenderer\";\nexport function renderText() {\n return \"Hello World\";\n}\nexport type { JSONContent } from \"@tiptap/core\";\n","import React, { useEffect, useRef, useState } from \"react\";\nimport \"./BlogRenderer.css\";\nimport { renderTiptapToHTML } from \"./tiptap-renderer\";\n\nconst styles = {\n wrapper: \"edlsb-wrapper\",\n progressBar: \"edlsb-progressBar\",\n main: \"edlsb-main\",\n headerSection: \"edlsb-headerSection\",\n headerContainer: \"edlsb-headerContainer\",\n headerContent: \"edlsb-headerContent\",\n metaRow: \"edlsb-metaRow\",\n articleBadge: \"edlsb-articleBadge\",\n dot: \"edlsb-dot\",\n metaItem: \"edlsb-metaItem\",\n icon: \"edlsb-icon\",\n postTitle: \"edlsb-postTitle\",\n bodyContainer: \"edlsb-bodyContainer\",\n thumbnailWrapper: \"edlsb-thumbnailWrapper\",\n thumbnail: \"edlsb-thumbnail\",\n tocWrapper: \"edlsb-tocWrapper\",\n tocToggle: \"edlsb-tocToggle\",\n tocToggleInner: \"edlsb-tocToggleInner\",\n tocIconWrap: \"edlsb-tocIconWrap\",\n tocLabel: \"edlsb-tocLabel\",\n tocCount: \"edlsb-tocCount\",\n chevronIcon: \"edlsb-chevronIcon\",\n chevronIconOpen: \"edlsb-chevronIconOpen\",\n tocPanelInner: \"edlsb-tocPanelInner\",\n tocGrid: \"edlsb-tocGrid\",\n tocItem: \"edlsb-tocItem\",\n tocItemActive: \"edlsb-tocItemActive\",\n tocItemLevel3: \"edlsb-tocItemLevel3\",\n tocBadge: \"edlsb-tocBadge\",\n tocBadgeActive: \"edlsb-tocBadgeActive\",\n tocItemText: \"edlsb-tocItemText\",\n shareSection: \"edlsb-shareSection\",\n shareInner: \"edlsb-shareInner\",\n shareTitle: \"edlsb-shareTitle\",\n shareSubtitle: \"edlsb-shareSubtitle\",\n shareButtons: \"edlsb-shareButtons\",\n shareBtn: \"edlsb-shareBtn\",\n shareBtnInner: \"edlsb-shareBtnInner\",\n copyBtn: \"edlsb-copyBtn\",\n} as const;\n\nconst baseIconProps = {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"1em\",\n height: \"1em\",\n style: { flexShrink: 0 },\n \"aria-hidden\": true,\n} as const;\n\nconst Calendar = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" strokeWidth=\"2\" />\n <line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\" strokeWidth=\"2\" />\n <line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\" strokeWidth=\"2\" />\n <line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst Clock = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <circle cx=\"12\" cy=\"12\" r=\"9\" strokeWidth=\"2\" />\n <line x1=\"12\" y1=\"7\" x2=\"12\" y2=\"12\" strokeWidth=\"2\" />\n <line x1=\"12\" y1=\"12\" x2=\"15\" y2=\"14\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst ChevronDown = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <polyline points=\"6 9 12 15 18 9\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst Twitter = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M18.244 2h3.308l-7.227 8.26L22.8 22h-6.637l-5.197-6.787L4.99 22H1.68l7.73-8.835L1.2 2h6.806l4.697 6.21L18.244 2Zm-1.16 18h1.833L7.01 3.896H5.044L17.083 20Z\" />\n </svg>\n);\n\nconst Linkedin = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M6.94 8.5a1.94 1.94 0 1 1 0-3.88 1.94 1.94 0 0 1 0 3.88ZM5.26 9.94h3.36V20H5.26V9.94Zm5.28 0h3.22v1.38h.05c.45-.85 1.55-1.74 3.2-1.74 3.42 0 4.05 2.1 4.05 4.84V20h-3.36v-4.95c0-1.18-.02-2.7-1.78-2.7-1.78 0-2.05 1.3-2.05 2.62V20h-3.33V9.94Z\" />\n </svg>\n);\n\nconst Facebook = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M13.5 22v-8h2.7l.5-3h-3.2V9.2c0-.9.3-1.5 1.6-1.5h1.7V5c-.3 0-1.3-.1-2.4-.1-2.4 0-4 1.4-4 3.9V11H8v3h2.4v8h3.1Z\" />\n </svg>\n);\n\nconst LinkIcon = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path\n d=\"M10 14a5 5 0 0 1 0-7l1.5-1.5a5 5 0 0 1 7 7L17 14\"\n strokeWidth=\"2\"\n />\n <path\n d=\"M14 10a5 5 0 0 1 0 7l-1.5 1.5a5 5 0 1 1-7-7L7 10\"\n strokeWidth=\"2\"\n />\n </svg>\n);\n\ninterface SingleBlogClientProps {\n post: BlogPost;\n}\n\ninterface BlogPostProfile {\n username: string;\n display_name: string;\n account_level: string;\n profile_picture: string;\n}\n\ninterface BlogPost {\n id: number;\n title: string;\n content_json: string;\n thumbnail_path: string | null;\n keywords: string;\n followers_only: boolean;\n visibility: string;\n created_at: string;\n link_slug: string;\n updated_at: string;\n profile: BlogPostProfile;\n ads_step: number;\n banner_ads: boolean;\n video_ads: boolean;\n}\n\ninterface TocItem {\n id: string;\n text: string;\n level: 2 | 3;\n}\n\nconst stripProseWrappers = (html: string): string => {\n // Repeatedly unwrap outermost <div class=\"prose ...\"> until none remain.\n let result = html;\n const proseDiv = /^\\s*<div[^>]*\\bprose\\b[^>]*>([\\s\\S]*)<\\/div>\\s*$/;\n let prev = \"\";\n\n while (prev !== result) {\n prev = result;\n const match = result.match(proseDiv);\n if (match) result = match[1].trim();\n }\n\n return result;\n};\n\nexport function BlogRenderer({ post }: SingleBlogClientProps) {\n const rawHtml = post.content_json\n ? renderTiptapToHTML(post.content_json)\n : \"<p>Could not render content</p>\";\n const htmlContent = stripProseWrappers(rawHtml);\n\n const articleRef = useRef<HTMLDivElement>(null);\n const [renderedHtml, setRenderedHtml] = useState(htmlContent);\n const [tocItems, setTocItems] = useState<TocItem[]>([]);\n const [activeHeadingId, setActiveHeadingId] = useState(\"\");\n\n const [tocOpen, setTocOpen] = useState(false);\n const [scrollProgress, setScrollProgress] = useState(0);\n const [mounted, setMounted] = useState(false);\n\n const getFullThumbnail = (path: string | undefined | null) => {\n if (!path) return undefined;\n if (path.startsWith(\"http\")) return path;\n return `https://cdn.dl.surf/${path}`;\n };\n\n // Use the pre-rendered htmlContent passed as a prop\n const thumbUrl = getFullThumbnail(post.thumbnail_path);\n const publishDate = post.created_at || new Date().toISOString();\n const formattedDate = new Date(publishDate).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n\n // Share handlers\n const shareUrl = typeof window !== \"undefined\" ? window.location.href : \"\";\n const shareTitle = post.title;\n\n const shareOnTwitter = () =>\n window.open(\n `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}`,\n \"_blank\",\n );\n const shareOnFacebook = () =>\n window.open(\n `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`,\n \"_blank\",\n );\n const shareOnLinkedIn = () =>\n window.open(\n `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`,\n \"_blank\",\n );\n const copyLink = () => {\n navigator.clipboard.writeText(shareUrl);\n alert(\"Link copied to clipboard!\");\n };\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n const handleScroll = () => {\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n const docHeight =\n document.documentElement.scrollHeight - window.innerHeight;\n setScrollProgress(docHeight > 0 ? scrollTop / docHeight : 0);\n };\n window.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", handleScroll);\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const wrapper = document.createElement(\"div\");\n wrapper.innerHTML = htmlContent || \"\";\n\n const headings = Array.from(wrapper.querySelectorAll(\"h2, h3\"));\n const nextTocItems: TocItem[] = [];\n const usedIds = new Set<string>();\n\n headings.forEach((heading, index) => {\n const text = heading.textContent?.trim() || `Section ${index + 1}`;\n const level = heading.tagName.toLowerCase() === \"h2\" ? 2 : 3;\n const base =\n text\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, \"\")\n .trim()\n .replace(/\\s+/g, \"-\") || `section-${index + 1}`;\n\n let id = base;\n let suffix = 2;\n while (usedIds.has(id)) {\n id = `${base}-${suffix}`;\n suffix += 1;\n }\n usedIds.add(id);\n\n heading.setAttribute(\"id\", id);\n nextTocItems.push({ id, text, level });\n });\n\n setRenderedHtml(wrapper.innerHTML);\n setTocItems(nextTocItems);\n }, [htmlContent]);\n\n useEffect(() => {\n if (!tocItems.length) return;\n\n const headingElements = tocItems\n .map((item) => document.getElementById(item.id))\n .filter((el): el is HTMLElement => Boolean(el));\n\n if (!headingElements.length) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n const visible = entries\n .filter((entry) => entry.isIntersecting)\n .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);\n\n if (visible[0]?.target?.id) {\n setActiveHeadingId(visible[0].target.id);\n }\n },\n { rootMargin: \"-90px 0px -65% 0px\", threshold: 0.1 },\n );\n\n headingElements.forEach((el) => observer.observe(el));\n return () => observer.disconnect();\n }, [tocItems, renderedHtml]);\n\n const scrollToHeading = (id: string) => {\n setTocOpen(false);\n // Wait for collapse animation to finish before calculating position\n setTimeout(() => {\n const el = document.getElementById(id);\n if (!el) return;\n const offset = 110;\n const top = el.getBoundingClientRect().top + window.scrollY - offset;\n window.scrollTo({ top, behavior: \"smooth\" });\n }, 280);\n };\n\n return (\n <div className={styles.wrapper}>\n <div\n className={styles.progressBar}\n style={{ transform: `scaleX(${scrollProgress})` }}\n />\n\n <main className={styles.main} ref={articleRef}>\n <div className={styles.headerSection}>\n <div className={styles.headerContainer}>\n <div\n className={styles.headerContent}\n style={{\n opacity: mounted ? 1 : 0,\n transform: mounted ? \"none\" : \"translateY(20px)\",\n transition: \"opacity 0.4s ease, transform 0.4s ease\",\n }}\n >\n <div className={styles.metaRow}>\n <span className={styles.articleBadge}>Article</span>\n <span className={styles.dot} />\n <span className={styles.metaItem}>\n <Clock className={styles.icon} /> 5 Min Read\n </span>\n <span className={styles.dot} />\n <span className={styles.metaItem}>\n <Calendar className={styles.icon} /> {formattedDate}\n </span>\n </div>\n\n <h1 className={styles.postTitle}>{post.title}</h1>\n </div>\n </div>\n </div>\n\n <div className={styles.bodyContainer}>\n {thumbUrl && (\n <div\n className={styles.thumbnailWrapper}\n style={{\n opacity: mounted ? 1 : 0,\n transform: mounted ? \"none\" : \"scale(0.95)\",\n transition: \"opacity 0.4s ease 0.2s, transform 0.4s ease 0.2s\",\n }}\n >\n <img\n src={thumbUrl}\n alt={post.title}\n className={styles.thumbnail}\n />\n </div>\n )}\n\n {/* Table of Contents โ collapsible */}\n {tocItems.length > 0 && (\n <div className={styles.tocWrapper}>\n <button\n onClick={() => setTocOpen(!tocOpen)}\n className={styles.tocToggle}\n >\n <div className={styles.tocToggleInner}>\n <span className={styles.tocIconWrap}>\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M2 4h12M2 8h8M2 12h10\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n </span>\n <span className={styles.tocLabel}>\n Table of contents\n <span className={styles.tocCount}>\n ยท {tocItems.length} sections\n </span>\n </span>\n </div>\n <ChevronDown\n className={`${styles.chevronIcon}${tocOpen ? ` ${styles.chevronIconOpen}` : \"\"}`}\n />\n </button>\n\n <div\n style={{\n overflow: \"hidden\",\n maxHeight: tocOpen ? \"2000px\" : \"0px\",\n opacity: tocOpen ? 1 : 0,\n transition:\n \"max-height 0.25s ease-in-out, opacity 0.25s ease-in-out\",\n }}\n >\n <div className={styles.tocPanelInner}>\n <div className={styles.tocGrid}>\n {tocItems.map((item, idx) => (\n <button\n key={item.id}\n onClick={() => scrollToHeading(item.id)}\n className={[\n styles.tocItem,\n activeHeadingId === item.id\n ? styles.tocItemActive\n : \"\",\n item.level === 3 ? styles.tocItemLevel3 : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n <span\n className={[\n styles.tocBadge,\n activeHeadingId === item.id\n ? styles.tocBadgeActive\n : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n {idx + 1}\n </span>\n <span className={styles.tocItemText}>{item.text}</span>\n </button>\n ))}\n </div>\n </div>\n </div>\n </div>\n )}\n\n <div\n className=\"blog-content\"\n dangerouslySetInnerHTML={{ __html: renderedHtml }}\n />\n\n {/* Share section */}\n <div className={styles.shareSection}>\n <div className={styles.shareInner}>\n <div>\n <h3 className={styles.shareTitle}>Share this article</h3>\n <p className={styles.shareSubtitle}>\n If it helped, pass it on.\n </p>\n </div>\n <div className={styles.shareButtons}>\n <button onClick={shareOnTwitter} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Twitter className={styles.icon} /> X\n </span>\n </button>\n <button onClick={shareOnLinkedIn} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Linkedin className={styles.icon} /> LinkedIn\n </span>\n </button>\n <button onClick={shareOnFacebook} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Facebook className={styles.icon} /> Facebook\n </span>\n </button>\n <button\n onClick={copyLink}\n className={styles.copyBtn}\n title=\"Copy link\"\n >\n <LinkIcon className={styles.icon} />\n </button>\n </div>\n </div>\n </div>\n </div>\n </main>\n </div>\n );\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".edlsb-wrapper {\\n background-color: #f8fafc;\\n font-family:\\n var(--font-merriweather),\\n \\\"Merriweather\\\",\\n Georgia,\\n serif;\\n}\\n.edlsb-progressBar {\\n position: fixed;\\n top: 0;\\n left: 0;\\n right: 0;\\n height: 4px;\\n background-color: #4f46e5;\\n transform-origin: left center;\\n z-index: 100;\\n}\\n.edlsb-main {\\n position: relative;\\n}\\n.edlsb-headerSection {\\n padding-top: 2rem;\\n padding-bottom: 5rem;\\n position: relative;\\n overflow: hidden;\\n}\\n.edlsb-headerContainer {\\n width: 100%;\\n max-width: 56rem;\\n margin: 0 auto;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n position: relative;\\n z-index: 10;\\n}\\n.edlsb-headerContent {\\n text-align: center;\\n}\\n.edlsb-headerContent > * + * {\\n margin-top: 2rem;\\n}\\n@media (min-width: 768px) {\\n .edlsb-headerContent {\\n text-align: left;\\n }\\n}\\n.edlsb-metaRow {\\n display: flex;\\n flex-wrap: wrap;\\n align-items: center;\\n gap: 1rem;\\n color: #94a3b8;\\n font-weight: 700;\\n font-size: 0.75rem;\\n line-height: 1rem;\\n text-transform: uppercase;\\n letter-spacing: 0.1em;\\n justify-content: center;\\n}\\n@media (min-width: 768px) {\\n .edlsb-metaRow {\\n justify-content: flex-start;\\n }\\n}\\n.edlsb-articleBadge {\\n padding: 0.25rem 0.75rem;\\n background-color: #ffffff;\\n border-radius: 0.5rem;\\n border: 1px solid #e2e8f0;\\n color: #4f46e5;\\n}\\n.edlsb-dot {\\n width: 0.25rem;\\n height: 0.25rem;\\n background-color: #cbd5e1;\\n border-radius: 9999px;\\n}\\n.edlsb-metaItem {\\n display: flex;\\n align-items: center;\\n gap: 0.5rem;\\n}\\n.edlsb-icon {\\n width: 1rem;\\n height: 1rem;\\n}\\n.edlsb-postTitle {\\n font-size: 2.25rem;\\n line-height: 0.95;\\n font-weight: 1000;\\n color: #0f172a;\\n letter-spacing: -0.04em;\\n}\\n@media (min-width: 768px) {\\n .edlsb-postTitle {\\n font-size: 3.75rem;\\n }\\n}\\n@media (min-width: 1024px) {\\n .edlsb-postTitle {\\n font-size: 4.5rem;\\n }\\n}\\n.edlsb-bodyContainer {\\n width: 100%;\\n max-width: 56rem;\\n margin: 0 auto;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n padding-top: 5rem;\\n padding-bottom: 5rem;\\n}\\n.edlsb-thumbnailWrapper {\\n margin-bottom: 2rem;\\n border-radius: 1rem;\\n overflow: hidden;\\n box-shadow: 0 25px 50px -12px rgb(49 46 129 / 0.1);\\n margin-top: -8rem;\\n position: relative;\\n z-index: 20;\\n}\\n.edlsb-thumbnail {\\n width: 100%;\\n height: auto;\\n object-fit: cover;\\n max-height: 600px;\\n}\\n.edlsb-tocWrapper {\\n margin-bottom: 2.5rem;\\n}\\n.edlsb-tocToggle {\\n width: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n gap: 0.75rem;\\n padding: 1rem 1.25rem;\\n border-radius: 1rem;\\n background-color: rgba(241, 245, 249, 0.8);\\n border: none;\\n cursor: pointer;\\n transition-property: all;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocToggle:hover {\\n background-color: #f1f5f9;\\n}\\n.edlsb-tocToggle:hover .edlsb-tocIconWrap {\\n color: #334155;\\n}\\n.edlsb-tocToggleInner {\\n display: flex;\\n align-items: center;\\n gap: 0.75rem;\\n}\\n.edlsb-tocIconWrap {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n width: 1.75rem;\\n height: 1.75rem;\\n border-radius: 0.5rem;\\n background-color: rgba(226, 232, 240, 0.8);\\n color: #64748b;\\n transition-property: color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocLabel {\\n font-size: 13px;\\n font-weight: 600;\\n color: #475569;\\n}\\n.edlsb-tocCount {\\n color: #94a3b8;\\n font-weight: 400;\\n margin-left: 0.25rem;\\n}\\n.edlsb-chevronIcon {\\n width: 1rem;\\n height: 1rem;\\n color: #94a3b8;\\n transition-property: transform;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 200ms;\\n}\\n.edlsb-chevronIconOpen {\\n transform: rotate(180deg);\\n}\\n.edlsb-tocPanelInner {\\n margin-top: 0.375rem;\\n border-radius: 1rem;\\n background-color: rgba(241, 245, 249, 0.8);\\n padding: 0.5rem;\\n}\\n.edlsb-tocGrid {\\n display: grid;\\n grid-template-columns: 1fr;\\n gap: 0.125rem;\\n}\\n@media (min-width: 640px) {\\n .edlsb-tocGrid {\\n grid-template-columns: repeat(2, minmax(0, 1fr));\\n }\\n}\\n.edlsb-tocItem {\\n display: flex;\\n align-items: center;\\n gap: 0.625rem;\\n text-align: left;\\n border-radius: 0.75rem;\\n padding: 0.625rem 0.75rem;\\n font-size: 13px;\\n border: none;\\n cursor: pointer;\\n background-color: transparent;\\n color: #475569;\\n transition-property: all;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocItem:hover {\\n background-color: #ffffff;\\n color: #0f172a;\\n box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\\n}\\n.edlsb-tocItemActive {\\n background-color: #0f172a;\\n color: #ffffff;\\n box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\\n}\\n.edlsb-tocItemActive:hover {\\n background-color: #0f172a;\\n color: #ffffff;\\n}\\n.edlsb-tocItemLevel3 {\\n padding-left: 2rem;\\n}\\n.edlsb-tocBadge {\\n flex-shrink: 0;\\n width: 1.25rem;\\n height: 1.25rem;\\n border-radius: 0.375rem;\\n font-size: 10px;\\n font-weight: 700;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n background-color: rgba(226, 232, 240, 0.8);\\n color: #94a3b8;\\n}\\n.edlsb-tocBadgeActive {\\n background-color: rgba(255, 255, 255, 0.15);\\n color: rgba(255, 255, 255, 0.8);\\n}\\n.edlsb-tocItemText {\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n.edlsb-shareSection {\\n margin-top: 3.5rem;\\n border-top: 1px solid #e2e8f0;\\n padding-top: 1.5rem;\\n}\\n.edlsb-shareInner {\\n display: flex;\\n flex-direction: column;\\n gap: 1rem;\\n}\\n@media (min-width: 640px) {\\n .edlsb-shareInner {\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-between;\\n }\\n}\\n.edlsb-shareTitle {\\n font-size: 1rem;\\n line-height: 1.5rem;\\n font-weight: 600;\\n color: #0f172a;\\n}\\n.edlsb-shareSubtitle {\\n font-size: 0.875rem;\\n line-height: 1.25rem;\\n color: #64748b;\\n}\\n.edlsb-shareButtons {\\n display: flex;\\n align-items: center;\\n gap: 0.625rem;\\n}\\n.edlsb-shareBtn {\\n height: 2.5rem;\\n padding-left: 1rem;\\n padding-right: 1rem;\\n border-radius: 9999px;\\n border: 1px solid #e2e8f0;\\n background-color: #ffffff;\\n color: #334155;\\n font-size: 0.875rem;\\n font-weight: 600;\\n cursor: pointer;\\n transition-property:\\n color,\\n background-color,\\n border-color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-shareBtn:hover {\\n border-color: #cbd5e1;\\n color: #0f172a;\\n}\\n.edlsb-shareBtnInner {\\n display: inline-flex;\\n align-items: center;\\n gap: 0.5rem;\\n}\\n.edlsb-copyBtn {\\n height: 2.5rem;\\n width: 2.5rem;\\n border-radius: 9999px;\\n border: 1px solid #e2e8f0;\\n background-color: #ffffff;\\n color: #475569;\\n cursor: pointer;\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n transition-property:\\n color,\\n background-color,\\n border-color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-copyBtn:hover {\\n border-color: #cbd5e1;\\n color: #0f172a;\\n}\\n.edlsb-bodyContainer .blog-content {\\n color: #1e293b;\\n line-height: 1.8;\\n font-size: 1.05rem;\\n overflow-wrap: anywhere;\\n}\\n.edlsb-bodyContainer .blog-content > * + * {\\n margin-top: 1rem;\\n}\\n.edlsb-bodyContainer .blog-content pre {\\n margin: 1.25rem 0;\\n padding: 0.9rem 1rem;\\n border-radius: 0.75rem;\\n background: #0b1220;\\n color: #e2e8f0;\\n overflow-x: auto;\\n border: 1px solid #1f2937;\\n -webkit-overflow-scrolling: touch;\\n}\\n.edlsb-bodyContainer .blog-content pre code {\\n background: transparent;\\n padding: 0;\\n border-radius: 0;\\n color: inherit;\\n font-size: 0.875rem;\\n line-height: 1.6;\\n}\\n.edlsb-bodyContainer .blog-content code {\\n font-family:\\n ui-monospace,\\n SFMono-Regular,\\n Menlo,\\n Monaco,\\n Consolas,\\n \\\"Liberation Mono\\\",\\n \\\"Courier New\\\",\\n monospace;\\n background: #e2e8f0;\\n color: #0f172a;\\n padding: 0.15rem 0.4rem;\\n border-radius: 0.35rem;\\n font-size: 0.875em;\\n}\\n.edlsb-bodyContainer .blog-content img {\\n display: block;\\n max-width: 100%;\\n width: auto;\\n height: auto;\\n margin: 1.25rem auto;\\n border-radius: 0.75rem;\\n}\\n.edlsb-bodyContainer .blog-content a {\\n color: #2563eb;\\n text-decoration: underline;\\n text-underline-offset: 2px;\\n}\\n\")","/**\n * SSR-safe Tiptap JSON โ HTML renderer.\n *\n * Pure recursive serializer โ zero DOM APIs required.\n * Works in Next.js server components and any Node.js environment.\n *\n * Styling matches the Blog View Styling Guide exactly:\n * - prose prose-lg dark:prose-invert wrapper classes\n * - per-node Tailwind classes identical to the Tiptap extension HTMLAttributes\n * - dark-mode variants included throughout\n */\n\n// โโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n// โโโ Mark renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction applyMark(html: string, mark: any): string {\n switch (mark.type) {\n case 'bold':\n return `<strong>${html}</strong>`;\n case 'italic':\n return `<em>${html}</em>`;\n case 'strike':\n return `<s>${html}</s>`;\n case 'underline':\n return `<u>${html}</u>`;\n case 'code':\n return `<code>${html}</code>`;\n case 'link': {\n const href = escapeHtml(mark.attrs?.href ?? '');\n const target = escapeHtml(mark.attrs?.target ?? '_blank');\n return `<a href=\"${href}\" target=\"${target}\" rel=\"noopener noreferrer\">${html}</a>`;\n }\n case 'textStyle': {\n // Handles color / font-size set via the TextStyle extension\n const color = mark.attrs?.color;\n const fontSize = mark.attrs?.fontSize;\n const style = [\n color ? `color:${color}` : '',\n fontSize ? `font-size:${fontSize}` : '',\n ].filter(Boolean).join(';');\n return style ? `<span style=\"${style}\">${html}</span>` : html;\n }\n case 'highlight': {\n const color = mark.attrs?.color;\n const style = color ? ` style=\"background-color:${color}\"` : '';\n return `<mark${style}>${html}</mark>`;\n }\n default:\n return html;\n }\n}\n\n// โโโ Node renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction renderChildren(node: any): string {\n if (!Array.isArray(node?.content)) return '';\n return node.content.map(renderNode).join('');\n}\n\nfunction renderNode(node: any): string {\n if (!node) return '';\n\n switch (node.type) {\n\n // โโ Document root โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'doc':\n return renderChildren(node);\n\n // โโ Block nodes โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'paragraph': {\n const inner = renderChildren(node);\n if (!inner) return `<p><br /></p>`;\n return `<p>${inner}</p>`;\n }\n\n case 'heading': {\n const level = node.attrs?.level ?? 1;\n return `<h${level}>${renderChildren(node)}</h${level}>`;\n }\n\n case 'blockquote':\n return `<blockquote>${renderChildren(node)}</blockquote>`;\n\n case 'bulletList':\n return `<ul>${renderChildren(node)}</ul>`;\n\n case 'orderedList':\n return `<ol>${renderChildren(node)}</ol>`;\n\n case 'listItem':\n return `<li>${renderChildren(node)}</li>`;\n\n case 'taskList':\n return `<ul class=\"task-list\">${renderChildren(node)}</ul>`;\n\n case 'taskItem': {\n const checked = node.attrs?.checked ? ' checked' : '';\n return `<li class=\"task-item\"><input type=\"checkbox\"${checked} disabled /><div>${renderChildren(node)}</div></li>`;\n }\n\n case 'codeBlock': {\n const lang = node.attrs?.language ? ` data-language=\"${escapeHtml(node.attrs.language)}\"` : '';\n return `<pre${lang}><code>${renderChildren(node)}</code></pre>`;\n }\n\n case 'horizontalRule':\n return `<hr />`;\n\n case 'hardBreak':\n return `<br />`;\n\n // โโ Media / embeds โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'image': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const alt = escapeHtml(node.attrs?.alt ?? '');\n const title = node.attrs?.title ? ` title=\"${escapeHtml(node.attrs.title)}\"` : '';\n return `<img src=\"${src}\" alt=\"${alt}\"${title} loading=\"lazy\" />`;\n }\n\n case 'resizableImage': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const alt = escapeHtml(node.attrs?.alt ?? '');\n const width = node.attrs?.width ? ` width=\"${escapeHtml(String(node.attrs.width))}\"` : '';\n return `<img src=\"${src}\" alt=\"${alt}\"${width} loading=\"lazy\" />`;\n }\n\n case 'youtube': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const width = node.attrs?.width ?? 640;\n const height = node.attrs?.height ?? 480;\n if (!src) return '';\n return `<div class=\"rounded-lg my-4 overflow-hidden\" style=\"position:relative;padding-bottom:56.25%;height:0;\"><iframe src=\"${src}\" width=\"${width}\" height=\"${height}\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe></div>`;\n }\n\n case 'twitter':\n case 'tweet': {\n // Render a simple link card since Twitter embeds require client-side JS\n const url = escapeHtml(node.attrs?.src ?? node.attrs?.url ?? '');\n if (!url) return '';\n return `<div class=\"max-w-xl\"><a href=\"${url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-themecolor font-semibold underline underline-offset-[3px] hover:text-themecolorhover transition-colors\">${url}</a></div>`;\n }\n\n // โโ Inline nodes โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'text': {\n let out = escapeHtml(node.text ?? '');\n if (Array.isArray(node.marks)) {\n for (const mark of node.marks) {\n out = applyMark(out, mark);\n }\n }\n return out;\n }\n\n default:\n // Gracefully handle unknown nodes by rendering their children\n return renderChildren(node);\n }\n}\n\n// โโโ Public API โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport function renderTiptapToHTML(\n jsonContent: Record<string, any> | string,\n): string {\n try {\n if (!jsonContent) return '';\n\n let contentObj: any;\n if (typeof jsonContent === 'string') {\n try {\n contentObj = JSON.parse(jsonContent);\n } catch {\n // Not valid JSON โ treat as a raw HTML string and return as-is\n return jsonContent;\n }\n } else {\n contentObj = jsonContent;\n }\n\n if (!contentObj || typeof contentObj !== 'object') {\n return String(jsonContent);\n }\n\n return renderNode(contentObj);\n } catch (error) {\n console.error('Failed to parse Tiptap JSON to HTML:', error);\n return '<p>Error loading content.</p>';\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/BlogRenderer.tsx","#style-inject:#style-inject","../src/BlogRenderer.css","../src/tiptap-renderer.ts"],"sourcesContent":["export { BlogRenderer } from \"./BlogRenderer\";\nexport { BlogRenderer as default } from \"./BlogRenderer\";\nexport function renderText() {\n return \"Hello World\";\n}\nexport type { JSONContent } from \"@tiptap/core\";\n","import React, { useEffect, useRef, useState } from \"react\";\nimport \"./BlogRenderer.css\";\nimport { renderTiptapToHTML } from \"./tiptap-renderer\";\n\nconst styles = {\n wrapper: \"edlsb-wrapper\",\n progressBar: \"edlsb-progressBar\",\n main: \"edlsb-main\",\n headerSection: \"edlsb-headerSection\",\n headerContainer: \"edlsb-headerContainer\",\n headerContent: \"edlsb-headerContent\",\n metaRow: \"edlsb-metaRow\",\n articleBadge: \"edlsb-articleBadge\",\n dot: \"edlsb-dot\",\n metaItem: \"edlsb-metaItem\",\n icon: \"edlsb-icon\",\n postTitle: \"edlsb-postTitle\",\n bodyContainer: \"edlsb-bodyContainer\",\n thumbnailWrapper: \"edlsb-thumbnailWrapper\",\n thumbnail: \"edlsb-thumbnail\",\n tocWrapper: \"edlsb-tocWrapper\",\n tocToggle: \"edlsb-tocToggle\",\n tocToggleInner: \"edlsb-tocToggleInner\",\n tocIconWrap: \"edlsb-tocIconWrap\",\n tocLabel: \"edlsb-tocLabel\",\n tocCount: \"edlsb-tocCount\",\n chevronIcon: \"edlsb-chevronIcon\",\n chevronIconOpen: \"edlsb-chevronIconOpen\",\n tocPanelInner: \"edlsb-tocPanelInner\",\n tocGrid: \"edlsb-tocGrid\",\n tocItem: \"edlsb-tocItem\",\n tocItemActive: \"edlsb-tocItemActive\",\n tocItemLevel3: \"edlsb-tocItemLevel3\",\n tocBadge: \"edlsb-tocBadge\",\n tocBadgeActive: \"edlsb-tocBadgeActive\",\n tocItemText: \"edlsb-tocItemText\",\n shareSection: \"edlsb-shareSection\",\n shareInner: \"edlsb-shareInner\",\n shareTitle: \"edlsb-shareTitle\",\n shareSubtitle: \"edlsb-shareSubtitle\",\n shareButtons: \"edlsb-shareButtons\",\n shareBtn: \"edlsb-shareBtn\",\n shareBtnInner: \"edlsb-shareBtnInner\",\n copyBtn: \"edlsb-copyBtn\",\n} as const;\n\nconst baseIconProps = {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"1em\",\n height: \"1em\",\n style: { flexShrink: 0 },\n \"aria-hidden\": true,\n} as const;\n\nconst Calendar = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" strokeWidth=\"2\" />\n <line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\" strokeWidth=\"2\" />\n <line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\" strokeWidth=\"2\" />\n <line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst Clock = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <circle cx=\"12\" cy=\"12\" r=\"9\" strokeWidth=\"2\" />\n <line x1=\"12\" y1=\"7\" x2=\"12\" y2=\"12\" strokeWidth=\"2\" />\n <line x1=\"12\" y1=\"12\" x2=\"15\" y2=\"14\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst ChevronDown = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <polyline points=\"6 9 12 15 18 9\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst Twitter = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M18.244 2h3.308l-7.227 8.26L22.8 22h-6.637l-5.197-6.787L4.99 22H1.68l7.73-8.835L1.2 2h6.806l4.697 6.21L18.244 2Zm-1.16 18h1.833L7.01 3.896H5.044L17.083 20Z\" />\n </svg>\n);\n\nconst Linkedin = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M6.94 8.5a1.94 1.94 0 1 1 0-3.88 1.94 1.94 0 0 1 0 3.88ZM5.26 9.94h3.36V20H5.26V9.94Zm5.28 0h3.22v1.38h.05c.45-.85 1.55-1.74 3.2-1.74 3.42 0 4.05 2.1 4.05 4.84V20h-3.36v-4.95c0-1.18-.02-2.7-1.78-2.7-1.78 0-2.05 1.3-2.05 2.62V20h-3.33V9.94Z\" />\n </svg>\n);\n\nconst Facebook = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M13.5 22v-8h2.7l.5-3h-3.2V9.2c0-.9.3-1.5 1.6-1.5h1.7V5c-.3 0-1.3-.1-2.4-.1-2.4 0-4 1.4-4 3.9V11H8v3h2.4v8h3.1Z\" />\n </svg>\n);\n\nconst LinkIcon = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path\n d=\"M10 14a5 5 0 0 1 0-7l1.5-1.5a5 5 0 0 1 7 7L17 14\"\n strokeWidth=\"2\"\n />\n <path\n d=\"M14 10a5 5 0 0 1 0 7l-1.5 1.5a5 5 0 1 1-7-7L7 10\"\n strokeWidth=\"2\"\n />\n </svg>\n);\n\ninterface BlogRendererProps {\n post?: BlogPost;\n user?: string;\n linkSlug?: string;\n blogUrl?: string;\n}\n\ninterface BlogPostProfile {\n username: string;\n display_name: string;\n account_level: string;\n profile_picture: string;\n}\n\ninterface BlogPost {\n id: number;\n title: string;\n content_json: string;\n thumbnail_path: string | null;\n keywords: string;\n followers_only: boolean;\n visibility: string;\n created_at: string;\n link_slug: string;\n updated_at: string;\n profile: BlogPostProfile;\n ads_step: number;\n banner_ads: boolean;\n video_ads: boolean;\n}\n\ninterface TocItem {\n id: string;\n text: string;\n level: 2 | 3;\n}\n\ninterface DocApiResponse {\n status: string;\n message: string;\n data: BlogPost;\n}\n\nconst DEFAULT_DOC_API_BASE_URL = \"https://docapi.dl.surf/api/doc\";\n\nconst normalizeUsername = (value: string): string =>\n value.trim().replace(/^@+/, \"\");\n\nconst parseBlogReference = (\n blogUrl: string,\n): { user: string; linkSlug: string } | null => {\n try {\n const url = new URL(blogUrl);\n const segments = url.pathname.split(\"/\").filter(Boolean);\n\n // Supports: /api/doc/{user}/{linkSlug}\n const apiIndex = segments.findIndex(\n (segment, idx) => segment === \"api\" && segments[idx + 1] === \"doc\",\n );\n if (apiIndex >= 0 && segments.length >= apiIndex + 4) {\n return {\n user: decodeURIComponent(segments[apiIndex + 2]),\n linkSlug: decodeURIComponent(segments[apiIndex + 3]),\n };\n }\n\n // Fallback: use last two path segments as {user}/{linkSlug}\n if (segments.length >= 2) {\n return {\n user: decodeURIComponent(segments[segments.length - 2]),\n linkSlug: decodeURIComponent(segments[segments.length - 1]),\n };\n }\n\n return null;\n } catch {\n return null;\n }\n};\n\nconst stripProseWrappers = (html: string): string => {\n // Repeatedly unwrap outermost <div class=\"prose ...\"> until none remain.\n let result = html;\n const proseDiv = /^\\s*<div[^>]*\\bprose\\b[^>]*>([\\s\\S]*)<\\/div>\\s*$/;\n let prev = \"\";\n\n while (prev !== result) {\n prev = result;\n const match = result.match(proseDiv);\n if (match) result = match[1].trim();\n }\n\n return result;\n};\n\nexport function BlogRenderer({\n post,\n user,\n linkSlug,\n blogUrl,\n}: BlogRendererProps) {\n const [resolvedPost, setResolvedPost] = useState<BlogPost | null>(\n post ?? null,\n );\n const [isFetchingPost, setIsFetchingPost] = useState(!post);\n const [fetchError, setFetchError] = useState<string | null>(null);\n\n useEffect(() => {\n if (post) {\n setResolvedPost(post);\n setIsFetchingPost(false);\n setFetchError(null);\n return;\n }\n\n const directUser = user ? normalizeUsername(user) : undefined;\n const directSlug = linkSlug?.trim();\n let resolvedUser = directUser;\n let resolvedSlug = directSlug;\n\n if ((!resolvedUser || !resolvedSlug) && blogUrl) {\n const parsed = parseBlogReference(blogUrl);\n if (parsed) {\n resolvedUser = normalizeUsername(parsed.user);\n resolvedSlug = parsed.linkSlug;\n }\n }\n\n if (!resolvedUser || !resolvedSlug) {\n setResolvedPost(null);\n setIsFetchingPost(false);\n setFetchError(\n \"Provide either post, user+linkSlug, or a blogUrl with /{user}/{linkSlug}.\",\n );\n return;\n }\n\n const controller = new AbortController();\n const fetchPost = async () => {\n try {\n setIsFetchingPost(true);\n setFetchError(null);\n\n const base = DEFAULT_DOC_API_BASE_URL;\n\n const endpoint = `${base}/${encodeURIComponent(resolvedUser!)}/${encodeURIComponent(resolvedSlug!)}`;\n const response = await fetch(endpoint, { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch document (${response.status})`);\n }\n\n const payload = (await response.json()) as DocApiResponse;\n if (!payload?.data) {\n throw new Error(\"Invalid API response: missing data field\");\n }\n\n setResolvedPost(payload.data);\n } catch (error) {\n if (controller.signal.aborted) return;\n setResolvedPost(null);\n setFetchError(\n error instanceof Error ? error.message : \"Failed to fetch document\",\n );\n } finally {\n if (!controller.signal.aborted) {\n setIsFetchingPost(false);\n }\n }\n };\n\n fetchPost();\n return () => controller.abort();\n }, [post, user, linkSlug, blogUrl]);\n\n const rawHtml = resolvedPost?.content_json\n ? renderTiptapToHTML(resolvedPost.content_json)\n : \"<p>Could not render content</p>\";\n const htmlContent = stripProseWrappers(rawHtml);\n\n const articleRef = useRef<HTMLDivElement>(null);\n const [renderedHtml, setRenderedHtml] = useState(htmlContent);\n const [tocItems, setTocItems] = useState<TocItem[]>([]);\n const [activeHeadingId, setActiveHeadingId] = useState(\"\");\n\n const [tocOpen, setTocOpen] = useState(false);\n const [scrollProgress, setScrollProgress] = useState(0);\n const [mounted, setMounted] = useState(false);\n\n const postData = resolvedPost;\n\n const getFullThumbnail = (path: string | undefined | null) => {\n if (!path) return undefined;\n if (path.startsWith(\"http\")) return path;\n return `https://cdn.dl.surf/${path}`;\n };\n\n const thumbUrl = getFullThumbnail(postData?.thumbnail_path);\n const publishDate = postData?.created_at || new Date().toISOString();\n const formattedDate = new Date(publishDate).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n\n // Share handlers\n const shareUrl = typeof window !== \"undefined\" ? window.location.href : \"\";\n const shareTitle = postData?.title || \"\";\n\n const shareOnTwitter = () =>\n window.open(\n `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}`,\n \"_blank\",\n );\n const shareOnFacebook = () =>\n window.open(\n `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`,\n \"_blank\",\n );\n const shareOnLinkedIn = () =>\n window.open(\n `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`,\n \"_blank\",\n );\n const copyLink = () => {\n navigator.clipboard.writeText(shareUrl);\n alert(\"Link copied to clipboard!\");\n };\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n const handleScroll = () => {\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n const docHeight =\n document.documentElement.scrollHeight - window.innerHeight;\n setScrollProgress(docHeight > 0 ? scrollTop / docHeight : 0);\n };\n window.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", handleScroll);\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const wrapper = document.createElement(\"div\");\n wrapper.innerHTML = htmlContent || \"\";\n\n const headings = Array.from(wrapper.querySelectorAll(\"h2, h3\"));\n const nextTocItems: TocItem[] = [];\n const usedIds = new Set<string>();\n\n headings.forEach((heading, index) => {\n const text = heading.textContent?.trim() || `Section ${index + 1}`;\n const level = heading.tagName.toLowerCase() === \"h2\" ? 2 : 3;\n const base =\n text\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, \"\")\n .trim()\n .replace(/\\s+/g, \"-\") || `section-${index + 1}`;\n\n let id = base;\n let suffix = 2;\n while (usedIds.has(id)) {\n id = `${base}-${suffix}`;\n suffix += 1;\n }\n usedIds.add(id);\n\n heading.setAttribute(\"id\", id);\n nextTocItems.push({ id, text, level });\n });\n\n setRenderedHtml(wrapper.innerHTML);\n setTocItems(nextTocItems);\n }, [htmlContent]);\n\n useEffect(() => {\n if (!tocItems.length) return;\n\n const headingElements = tocItems\n .map((item) => document.getElementById(item.id))\n .filter((el): el is HTMLElement => Boolean(el));\n\n if (!headingElements.length) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n const visible = entries\n .filter((entry) => entry.isIntersecting)\n .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);\n\n if (visible[0]?.target?.id) {\n setActiveHeadingId(visible[0].target.id);\n }\n },\n { rootMargin: \"-90px 0px -65% 0px\", threshold: 0.1 },\n );\n\n headingElements.forEach((el) => observer.observe(el));\n return () => observer.disconnect();\n }, [tocItems, renderedHtml]);\n\n const scrollToHeading = (id: string) => {\n setTocOpen(false);\n // Wait for collapse animation to finish before calculating position\n setTimeout(() => {\n const el = document.getElementById(id);\n if (!el) return;\n const offset = 110;\n const top = el.getBoundingClientRect().top + window.scrollY - offset;\n window.scrollTo({ top, behavior: \"smooth\" });\n }, 280);\n };\n\n if (!postData) {\n return (\n <div className={styles.wrapper}>\n <main className={styles.main}>\n <div className={styles.bodyContainer}>\n <p>\n {isFetchingPost\n ? \"Loading blog...\"\n : fetchError || \"Could not load blog content.\"}\n </p>\n </div>\n </main>\n </div>\n );\n }\n\n return (\n <div className={styles.wrapper}>\n <div\n className={styles.progressBar}\n style={{ transform: `scaleX(${scrollProgress})` }}\n />\n\n <main className={styles.main} ref={articleRef}>\n <div className={styles.headerSection}>\n <div className={styles.headerContainer}>\n <div\n className={styles.headerContent}\n style={{\n opacity: mounted ? 1 : 0,\n transform: mounted ? \"none\" : \"translateY(20px)\",\n transition: \"opacity 0.4s ease, transform 0.4s ease\",\n }}\n >\n <div className={styles.metaRow}>\n <span className={styles.articleBadge}>Article</span>\n <span className={styles.dot} />\n <span className={styles.metaItem}>\n <Clock className={styles.icon} /> 5 Min Read\n </span>\n <span className={styles.dot} />\n <span className={styles.metaItem}>\n <Calendar className={styles.icon} /> {formattedDate}\n </span>\n </div>\n\n <h1 className={styles.postTitle}>{postData.title}</h1>\n </div>\n </div>\n </div>\n\n <div className={styles.bodyContainer}>\n {thumbUrl && (\n <div\n className={styles.thumbnailWrapper}\n style={{\n opacity: mounted ? 1 : 0,\n transform: mounted ? \"none\" : \"scale(0.95)\",\n transition: \"opacity 0.4s ease 0.2s, transform 0.4s ease 0.2s\",\n }}\n >\n <img\n src={thumbUrl}\n alt={postData.title}\n className={styles.thumbnail}\n />\n </div>\n )}\n\n {/* Table of Contents โ collapsible */}\n {tocItems.length > 0 && (\n <div className={styles.tocWrapper}>\n <button\n onClick={() => setTocOpen(!tocOpen)}\n className={styles.tocToggle}\n >\n <div className={styles.tocToggleInner}>\n <span className={styles.tocIconWrap}>\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M2 4h12M2 8h8M2 12h10\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n </span>\n <span className={styles.tocLabel}>\n Table of contents\n <span className={styles.tocCount}>\n ยท {tocItems.length} sections\n </span>\n </span>\n </div>\n <ChevronDown\n className={`${styles.chevronIcon}${tocOpen ? ` ${styles.chevronIconOpen}` : \"\"}`}\n />\n </button>\n\n <div\n style={{\n overflow: \"hidden\",\n maxHeight: tocOpen ? \"2000px\" : \"0px\",\n opacity: tocOpen ? 1 : 0,\n transition:\n \"max-height 0.25s ease-in-out, opacity 0.25s ease-in-out\",\n }}\n >\n <div className={styles.tocPanelInner}>\n <div className={styles.tocGrid}>\n {tocItems.map((item, idx) => (\n <button\n key={item.id}\n onClick={() => scrollToHeading(item.id)}\n className={[\n styles.tocItem,\n activeHeadingId === item.id\n ? styles.tocItemActive\n : \"\",\n item.level === 3 ? styles.tocItemLevel3 : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n <span\n className={[\n styles.tocBadge,\n activeHeadingId === item.id\n ? styles.tocBadgeActive\n : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n {idx + 1}\n </span>\n <span className={styles.tocItemText}>{item.text}</span>\n </button>\n ))}\n </div>\n </div>\n </div>\n </div>\n )}\n\n <div\n className=\"blog-content\"\n dangerouslySetInnerHTML={{ __html: renderedHtml }}\n />\n\n {/* Share section */}\n <div className={styles.shareSection}>\n <div className={styles.shareInner}>\n <div>\n <h3 className={styles.shareTitle}>Share this article</h3>\n <p className={styles.shareSubtitle}>\n If it helped, pass it on.\n </p>\n </div>\n <div className={styles.shareButtons}>\n <button onClick={shareOnTwitter} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Twitter className={styles.icon} /> X\n </span>\n </button>\n <button onClick={shareOnLinkedIn} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Linkedin className={styles.icon} /> LinkedIn\n </span>\n </button>\n <button onClick={shareOnFacebook} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Facebook className={styles.icon} /> Facebook\n </span>\n </button>\n <button\n onClick={copyLink}\n className={styles.copyBtn}\n title=\"Copy link\"\n >\n <LinkIcon className={styles.icon} />\n </button>\n </div>\n </div>\n </div>\n </div>\n </main>\n </div>\n );\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".edlsb-wrapper {\\n background-color: #f8fafc;\\n font-family:\\n var(--font-merriweather),\\n \\\"Merriweather\\\",\\n Georgia,\\n serif;\\n}\\n.edlsb-progressBar {\\n position: fixed;\\n top: 0;\\n left: 0;\\n right: 0;\\n height: 4px;\\n background-color: #4f46e5;\\n transform-origin: left center;\\n z-index: 100;\\n}\\n.edlsb-main {\\n position: relative;\\n}\\n.edlsb-headerSection {\\n padding-top: 2rem;\\n padding-bottom: 5rem;\\n position: relative;\\n overflow: hidden;\\n}\\n.edlsb-headerContainer {\\n width: 100%;\\n max-width: 56rem;\\n margin: 0 auto;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n position: relative;\\n z-index: 10;\\n}\\n.edlsb-headerContent {\\n text-align: center;\\n}\\n.edlsb-headerContent > * + * {\\n margin-top: 2rem;\\n}\\n@media (min-width: 768px) {\\n .edlsb-headerContent {\\n text-align: left;\\n }\\n}\\n.edlsb-metaRow {\\n display: flex;\\n flex-wrap: wrap;\\n align-items: center;\\n gap: 1rem;\\n color: #94a3b8;\\n font-weight: 700;\\n font-size: 0.75rem;\\n line-height: 1rem;\\n text-transform: uppercase;\\n letter-spacing: 0.1em;\\n justify-content: center;\\n}\\n@media (min-width: 768px) {\\n .edlsb-metaRow {\\n justify-content: flex-start;\\n }\\n}\\n.edlsb-articleBadge {\\n padding: 0.25rem 0.75rem;\\n background-color: #ffffff;\\n border-radius: 0.5rem;\\n border: 1px solid #e2e8f0;\\n color: #4f46e5;\\n}\\n.edlsb-dot {\\n width: 0.25rem;\\n height: 0.25rem;\\n background-color: #cbd5e1;\\n border-radius: 9999px;\\n}\\n.edlsb-metaItem {\\n display: flex;\\n align-items: center;\\n gap: 0.5rem;\\n}\\n.edlsb-icon {\\n width: 1rem;\\n height: 1rem;\\n}\\n.edlsb-postTitle {\\n font-size: 2.25rem;\\n line-height: 0.95;\\n font-weight: 1000;\\n color: #0f172a;\\n letter-spacing: -0.04em;\\n}\\n@media (min-width: 768px) {\\n .edlsb-postTitle {\\n font-size: 3.75rem;\\n }\\n}\\n@media (min-width: 1024px) {\\n .edlsb-postTitle {\\n font-size: 4.5rem;\\n }\\n}\\n.edlsb-bodyContainer {\\n width: 100%;\\n max-width: 56rem;\\n margin: 0 auto;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n padding-top: 5rem;\\n padding-bottom: 5rem;\\n}\\n.edlsb-thumbnailWrapper {\\n margin-bottom: 2rem;\\n border-radius: 1rem;\\n overflow: hidden;\\n box-shadow: 0 25px 50px -12px rgb(49 46 129 / 0.1);\\n margin-top: -8rem;\\n position: relative;\\n z-index: 20;\\n}\\n.edlsb-thumbnail {\\n width: 100%;\\n height: auto;\\n object-fit: cover;\\n max-height: 600px;\\n}\\n.edlsb-tocWrapper {\\n margin-bottom: 2.5rem;\\n}\\n.edlsb-tocToggle {\\n width: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n gap: 0.75rem;\\n padding: 1rem 1.25rem;\\n border-radius: 1rem;\\n background-color: rgba(241, 245, 249, 0.8);\\n border: none;\\n cursor: pointer;\\n transition-property: all;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocToggle:hover {\\n background-color: #f1f5f9;\\n}\\n.edlsb-tocToggle:hover .edlsb-tocIconWrap {\\n color: #334155;\\n}\\n.edlsb-tocToggleInner {\\n display: flex;\\n align-items: center;\\n gap: 0.75rem;\\n}\\n.edlsb-tocIconWrap {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n width: 1.75rem;\\n height: 1.75rem;\\n border-radius: 0.5rem;\\n background-color: rgba(226, 232, 240, 0.8);\\n color: #64748b;\\n transition-property: color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocLabel {\\n font-size: 13px;\\n font-weight: 600;\\n color: #475569;\\n}\\n.edlsb-tocCount {\\n color: #94a3b8;\\n font-weight: 400;\\n margin-left: 0.25rem;\\n}\\n.edlsb-chevronIcon {\\n width: 1rem;\\n height: 1rem;\\n color: #94a3b8;\\n transition-property: transform;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 200ms;\\n}\\n.edlsb-chevronIconOpen {\\n transform: rotate(180deg);\\n}\\n.edlsb-tocPanelInner {\\n margin-top: 0.375rem;\\n border-radius: 1rem;\\n background-color: rgba(241, 245, 249, 0.8);\\n padding: 0.5rem;\\n}\\n.edlsb-tocGrid {\\n display: grid;\\n grid-template-columns: 1fr;\\n gap: 0.125rem;\\n}\\n@media (min-width: 640px) {\\n .edlsb-tocGrid {\\n grid-template-columns: repeat(2, minmax(0, 1fr));\\n }\\n}\\n.edlsb-tocItem {\\n display: flex;\\n align-items: center;\\n gap: 0.625rem;\\n text-align: left;\\n border-radius: 0.75rem;\\n padding: 0.625rem 0.75rem;\\n font-size: 13px;\\n border: none;\\n cursor: pointer;\\n background-color: transparent;\\n color: #475569;\\n transition-property: all;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocItem:hover {\\n background-color: #ffffff;\\n color: #0f172a;\\n box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\\n}\\n.edlsb-tocItemActive {\\n background-color: #0f172a;\\n color: #ffffff;\\n box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\\n}\\n.edlsb-tocItemActive:hover {\\n background-color: #0f172a;\\n color: #ffffff;\\n}\\n.edlsb-tocItemLevel3 {\\n padding-left: 2rem;\\n}\\n.edlsb-tocBadge {\\n flex-shrink: 0;\\n width: 1.25rem;\\n height: 1.25rem;\\n border-radius: 0.375rem;\\n font-size: 10px;\\n font-weight: 700;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n background-color: rgba(226, 232, 240, 0.8);\\n color: #94a3b8;\\n}\\n.edlsb-tocBadgeActive {\\n background-color: rgba(255, 255, 255, 0.15);\\n color: rgba(255, 255, 255, 0.8);\\n}\\n.edlsb-tocItemText {\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n.edlsb-shareSection {\\n margin-top: 3.5rem;\\n border-top: 1px solid #e2e8f0;\\n padding-top: 1.5rem;\\n}\\n.edlsb-shareInner {\\n display: flex;\\n flex-direction: column;\\n gap: 1rem;\\n}\\n@media (min-width: 640px) {\\n .edlsb-shareInner {\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-between;\\n }\\n}\\n.edlsb-shareTitle {\\n font-size: 1rem;\\n line-height: 1.5rem;\\n font-weight: 600;\\n color: #0f172a;\\n}\\n.edlsb-shareSubtitle {\\n font-size: 0.875rem;\\n line-height: 1.25rem;\\n color: #64748b;\\n}\\n.edlsb-shareButtons {\\n display: flex;\\n align-items: center;\\n gap: 0.625rem;\\n}\\n.edlsb-shareBtn {\\n height: 2.5rem;\\n padding-left: 1rem;\\n padding-right: 1rem;\\n border-radius: 9999px;\\n border: 1px solid #e2e8f0;\\n background-color: #ffffff;\\n color: #334155;\\n font-size: 0.875rem;\\n font-weight: 600;\\n cursor: pointer;\\n transition-property:\\n color,\\n background-color,\\n border-color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-shareBtn:hover {\\n border-color: #cbd5e1;\\n color: #0f172a;\\n}\\n.edlsb-shareBtnInner {\\n display: inline-flex;\\n align-items: center;\\n gap: 0.5rem;\\n}\\n.edlsb-copyBtn {\\n height: 2.5rem;\\n width: 2.5rem;\\n border-radius: 9999px;\\n border: 1px solid #e2e8f0;\\n background-color: #ffffff;\\n color: #475569;\\n cursor: pointer;\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n transition-property:\\n color,\\n background-color,\\n border-color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-copyBtn:hover {\\n border-color: #cbd5e1;\\n color: #0f172a;\\n}\\n.edlsb-bodyContainer .blog-content {\\n color: #1e293b;\\n line-height: 1.8;\\n font-size: 1.05rem;\\n overflow-wrap: anywhere;\\n}\\n.edlsb-bodyContainer .blog-content > * + * {\\n margin-top: 1rem;\\n}\\n.edlsb-bodyContainer .blog-content pre {\\n margin: 1.25rem 0;\\n padding: 0.9rem 1rem;\\n border-radius: 0.75rem;\\n background: #0b1220;\\n color: #e2e8f0;\\n overflow-x: auto;\\n border: 1px solid #1f2937;\\n -webkit-overflow-scrolling: touch;\\n}\\n.edlsb-bodyContainer .blog-content pre code {\\n background: transparent;\\n padding: 0;\\n border-radius: 0;\\n color: inherit;\\n font-size: 0.875rem;\\n line-height: 1.6;\\n}\\n.edlsb-bodyContainer .blog-content code {\\n font-family:\\n ui-monospace,\\n SFMono-Regular,\\n Menlo,\\n Monaco,\\n Consolas,\\n \\\"Liberation Mono\\\",\\n \\\"Courier New\\\",\\n monospace;\\n background: #e2e8f0;\\n color: #0f172a;\\n padding: 0.15rem 0.4rem;\\n border-radius: 0.35rem;\\n font-size: 0.875em;\\n}\\n.edlsb-bodyContainer .blog-content img {\\n display: block;\\n max-width: 100%;\\n width: auto;\\n height: auto;\\n margin: 1.25rem auto;\\n border-radius: 0.75rem;\\n}\\n.edlsb-bodyContainer .blog-content a {\\n color: #2563eb;\\n text-decoration: underline;\\n text-underline-offset: 2px;\\n}\\n\")","/**\n * SSR-safe Tiptap JSON โ HTML renderer.\n *\n * Pure recursive serializer โ zero DOM APIs required.\n * Works in Next.js server components and any Node.js environment.\n *\n * Styling matches the Blog View Styling Guide exactly:\n * - prose prose-lg dark:prose-invert wrapper classes\n * - per-node Tailwind classes identical to the Tiptap extension HTMLAttributes\n * - dark-mode variants included throughout\n */\n\n// โโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n// โโโ Mark renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction applyMark(html: string, mark: any): string {\n switch (mark.type) {\n case 'bold':\n return `<strong>${html}</strong>`;\n case 'italic':\n return `<em>${html}</em>`;\n case 'strike':\n return `<s>${html}</s>`;\n case 'underline':\n return `<u>${html}</u>`;\n case 'code':\n return `<code>${html}</code>`;\n case 'link': {\n const href = escapeHtml(mark.attrs?.href ?? '');\n const target = escapeHtml(mark.attrs?.target ?? '_blank');\n return `<a href=\"${href}\" target=\"${target}\" rel=\"noopener noreferrer\">${html}</a>`;\n }\n case 'textStyle': {\n // Handles color / font-size set via the TextStyle extension\n const color = mark.attrs?.color;\n const fontSize = mark.attrs?.fontSize;\n const style = [\n color ? `color:${color}` : '',\n fontSize ? `font-size:${fontSize}` : '',\n ].filter(Boolean).join(';');\n return style ? `<span style=\"${style}\">${html}</span>` : html;\n }\n case 'highlight': {\n const color = mark.attrs?.color;\n const style = color ? ` style=\"background-color:${color}\"` : '';\n return `<mark${style}>${html}</mark>`;\n }\n default:\n return html;\n }\n}\n\n// โโโ Node renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction renderChildren(node: any): string {\n if (!Array.isArray(node?.content)) return '';\n return node.content.map(renderNode).join('');\n}\n\nfunction renderNode(node: any): string {\n if (!node) return '';\n\n switch (node.type) {\n\n // โโ Document root โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'doc':\n return renderChildren(node);\n\n // โโ Block nodes โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'paragraph': {\n const inner = renderChildren(node);\n if (!inner) return `<p><br /></p>`;\n return `<p>${inner}</p>`;\n }\n\n case 'heading': {\n const level = node.attrs?.level ?? 1;\n return `<h${level}>${renderChildren(node)}</h${level}>`;\n }\n\n case 'blockquote':\n return `<blockquote>${renderChildren(node)}</blockquote>`;\n\n case 'bulletList':\n return `<ul>${renderChildren(node)}</ul>`;\n\n case 'orderedList':\n return `<ol>${renderChildren(node)}</ol>`;\n\n case 'listItem':\n return `<li>${renderChildren(node)}</li>`;\n\n case 'taskList':\n return `<ul class=\"task-list\">${renderChildren(node)}</ul>`;\n\n case 'taskItem': {\n const checked = node.attrs?.checked ? ' checked' : '';\n return `<li class=\"task-item\"><input type=\"checkbox\"${checked} disabled /><div>${renderChildren(node)}</div></li>`;\n }\n\n case 'codeBlock': {\n const lang = node.attrs?.language ? ` data-language=\"${escapeHtml(node.attrs.language)}\"` : '';\n return `<pre${lang}><code>${renderChildren(node)}</code></pre>`;\n }\n\n case 'horizontalRule':\n return `<hr />`;\n\n case 'hardBreak':\n return `<br />`;\n\n // โโ Media / embeds โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'image': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const alt = escapeHtml(node.attrs?.alt ?? '');\n const title = node.attrs?.title ? ` title=\"${escapeHtml(node.attrs.title)}\"` : '';\n return `<img src=\"${src}\" alt=\"${alt}\"${title} loading=\"lazy\" />`;\n }\n\n case 'resizableImage': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const alt = escapeHtml(node.attrs?.alt ?? '');\n const width = node.attrs?.width ? ` width=\"${escapeHtml(String(node.attrs.width))}\"` : '';\n return `<img src=\"${src}\" alt=\"${alt}\"${width} loading=\"lazy\" />`;\n }\n\n case 'youtube': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const width = node.attrs?.width ?? 640;\n const height = node.attrs?.height ?? 480;\n if (!src) return '';\n return `<div class=\"rounded-lg my-4 overflow-hidden\" style=\"position:relative;padding-bottom:56.25%;height:0;\"><iframe src=\"${src}\" width=\"${width}\" height=\"${height}\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe></div>`;\n }\n\n case 'twitter':\n case 'tweet': {\n // Render a simple link card since Twitter embeds require client-side JS\n const url = escapeHtml(node.attrs?.src ?? node.attrs?.url ?? '');\n if (!url) return '';\n return `<div class=\"max-w-xl\"><a href=\"${url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-themecolor font-semibold underline underline-offset-[3px] hover:text-themecolorhover transition-colors\">${url}</a></div>`;\n }\n\n // โโ Inline nodes โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'text': {\n let out = escapeHtml(node.text ?? '');\n if (Array.isArray(node.marks)) {\n for (const mark of node.marks) {\n out = applyMark(out, mark);\n }\n }\n return out;\n }\n\n default:\n // Gracefully handle unknown nodes by rendering their children\n return renderChildren(node);\n }\n}\n\n// โโโ Public API โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport function renderTiptapToHTML(\n jsonContent: Record<string, any> | string,\n): string {\n try {\n if (!jsonContent) return '';\n\n let contentObj: any;\n if (typeof jsonContent === 'string') {\n try {\n contentObj = JSON.parse(jsonContent);\n } catch {\n // Not valid JSON โ treat as a raw HTML string and return as-is\n return jsonContent;\n }\n } else {\n contentObj = jsonContent;\n }\n\n if (!contentObj || typeof contentObj !== 'object') {\n return String(jsonContent);\n }\n\n return renderNode(contentObj);\n } catch (error) {\n console.error('Failed to parse Tiptap JSON to HTML:', error);\n return '<p>Error loading content.</p>';\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -317,6 +317,32 @@ var LinkIcon = (props) => /* @__PURE__ */ jsxs(
|
|
|
317
317
|
]
|
|
318
318
|
})
|
|
319
319
|
);
|
|
320
|
+
var DEFAULT_DOC_API_BASE_URL = "https://docapi.dl.surf/api/doc";
|
|
321
|
+
var normalizeUsername = (value) => value.trim().replace(/^@+/, "");
|
|
322
|
+
var parseBlogReference = (blogUrl) => {
|
|
323
|
+
try {
|
|
324
|
+
const url = new URL(blogUrl);
|
|
325
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
326
|
+
const apiIndex = segments.findIndex(
|
|
327
|
+
(segment, idx) => segment === "api" && segments[idx + 1] === "doc"
|
|
328
|
+
);
|
|
329
|
+
if (apiIndex >= 0 && segments.length >= apiIndex + 4) {
|
|
330
|
+
return {
|
|
331
|
+
user: decodeURIComponent(segments[apiIndex + 2]),
|
|
332
|
+
linkSlug: decodeURIComponent(segments[apiIndex + 3])
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (segments.length >= 2) {
|
|
336
|
+
return {
|
|
337
|
+
user: decodeURIComponent(segments[segments.length - 2]),
|
|
338
|
+
linkSlug: decodeURIComponent(segments[segments.length - 1])
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
} catch (e) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
320
346
|
var stripProseWrappers = (html) => {
|
|
321
347
|
let result = html;
|
|
322
348
|
const proseDiv = /^\s*<div[^>]*\bprose\b[^>]*>([\s\S]*)<\/div>\s*$/;
|
|
@@ -328,8 +354,75 @@ var stripProseWrappers = (html) => {
|
|
|
328
354
|
}
|
|
329
355
|
return result;
|
|
330
356
|
};
|
|
331
|
-
function BlogRenderer({
|
|
332
|
-
|
|
357
|
+
function BlogRenderer({
|
|
358
|
+
post,
|
|
359
|
+
user,
|
|
360
|
+
linkSlug,
|
|
361
|
+
blogUrl
|
|
362
|
+
}) {
|
|
363
|
+
const [resolvedPost, setResolvedPost] = useState(
|
|
364
|
+
post != null ? post : null
|
|
365
|
+
);
|
|
366
|
+
const [isFetchingPost, setIsFetchingPost] = useState(!post);
|
|
367
|
+
const [fetchError, setFetchError] = useState(null);
|
|
368
|
+
useEffect(() => {
|
|
369
|
+
if (post) {
|
|
370
|
+
setResolvedPost(post);
|
|
371
|
+
setIsFetchingPost(false);
|
|
372
|
+
setFetchError(null);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const directUser = user ? normalizeUsername(user) : void 0;
|
|
376
|
+
const directSlug = linkSlug == null ? void 0 : linkSlug.trim();
|
|
377
|
+
let resolvedUser = directUser;
|
|
378
|
+
let resolvedSlug = directSlug;
|
|
379
|
+
if ((!resolvedUser || !resolvedSlug) && blogUrl) {
|
|
380
|
+
const parsed = parseBlogReference(blogUrl);
|
|
381
|
+
if (parsed) {
|
|
382
|
+
resolvedUser = normalizeUsername(parsed.user);
|
|
383
|
+
resolvedSlug = parsed.linkSlug;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (!resolvedUser || !resolvedSlug) {
|
|
387
|
+
setResolvedPost(null);
|
|
388
|
+
setIsFetchingPost(false);
|
|
389
|
+
setFetchError(
|
|
390
|
+
"Provide either post, user+linkSlug, or a blogUrl with /{user}/{linkSlug}."
|
|
391
|
+
);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const controller = new AbortController();
|
|
395
|
+
const fetchPost = async () => {
|
|
396
|
+
try {
|
|
397
|
+
setIsFetchingPost(true);
|
|
398
|
+
setFetchError(null);
|
|
399
|
+
const base = DEFAULT_DOC_API_BASE_URL;
|
|
400
|
+
const endpoint = `${base}/${encodeURIComponent(resolvedUser)}/${encodeURIComponent(resolvedSlug)}`;
|
|
401
|
+
const response = await fetch(endpoint, { signal: controller.signal });
|
|
402
|
+
if (!response.ok) {
|
|
403
|
+
throw new Error(`Failed to fetch document (${response.status})`);
|
|
404
|
+
}
|
|
405
|
+
const payload = await response.json();
|
|
406
|
+
if (!(payload == null ? void 0 : payload.data)) {
|
|
407
|
+
throw new Error("Invalid API response: missing data field");
|
|
408
|
+
}
|
|
409
|
+
setResolvedPost(payload.data);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
if (controller.signal.aborted) return;
|
|
412
|
+
setResolvedPost(null);
|
|
413
|
+
setFetchError(
|
|
414
|
+
error instanceof Error ? error.message : "Failed to fetch document"
|
|
415
|
+
);
|
|
416
|
+
} finally {
|
|
417
|
+
if (!controller.signal.aborted) {
|
|
418
|
+
setIsFetchingPost(false);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
fetchPost();
|
|
423
|
+
return () => controller.abort();
|
|
424
|
+
}, [post, user, linkSlug, blogUrl]);
|
|
425
|
+
const rawHtml = (resolvedPost == null ? void 0 : resolvedPost.content_json) ? renderTiptapToHTML(resolvedPost.content_json) : "<p>Could not render content</p>";
|
|
333
426
|
const htmlContent = stripProseWrappers(rawHtml);
|
|
334
427
|
const articleRef = useRef(null);
|
|
335
428
|
const [renderedHtml, setRenderedHtml] = useState(htmlContent);
|
|
@@ -338,20 +431,21 @@ function BlogRenderer({ post }) {
|
|
|
338
431
|
const [tocOpen, setTocOpen] = useState(false);
|
|
339
432
|
const [scrollProgress, setScrollProgress] = useState(0);
|
|
340
433
|
const [mounted, setMounted] = useState(false);
|
|
434
|
+
const postData = resolvedPost;
|
|
341
435
|
const getFullThumbnail = (path) => {
|
|
342
436
|
if (!path) return void 0;
|
|
343
437
|
if (path.startsWith("http")) return path;
|
|
344
438
|
return `https://cdn.dl.surf/${path}`;
|
|
345
439
|
};
|
|
346
|
-
const thumbUrl = getFullThumbnail(
|
|
347
|
-
const publishDate =
|
|
440
|
+
const thumbUrl = getFullThumbnail(postData == null ? void 0 : postData.thumbnail_path);
|
|
441
|
+
const publishDate = (postData == null ? void 0 : postData.created_at) || (/* @__PURE__ */ new Date()).toISOString();
|
|
348
442
|
const formattedDate = new Date(publishDate).toLocaleDateString("en-US", {
|
|
349
443
|
year: "numeric",
|
|
350
444
|
month: "long",
|
|
351
445
|
day: "numeric"
|
|
352
446
|
});
|
|
353
447
|
const shareUrl = typeof window !== "undefined" ? window.location.href : "";
|
|
354
|
-
const shareTitle =
|
|
448
|
+
const shareTitle = (postData == null ? void 0 : postData.title) || "";
|
|
355
449
|
const shareOnTwitter = () => window.open(
|
|
356
450
|
`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}`,
|
|
357
451
|
"_blank"
|
|
@@ -432,6 +526,9 @@ function BlogRenderer({ post }) {
|
|
|
432
526
|
window.scrollTo({ top, behavior: "smooth" });
|
|
433
527
|
}, 280);
|
|
434
528
|
};
|
|
529
|
+
if (!postData) {
|
|
530
|
+
return /* @__PURE__ */ jsx("div", { className: styles.wrapper, children: /* @__PURE__ */ jsx("main", { className: styles.main, children: /* @__PURE__ */ jsx("div", { className: styles.bodyContainer, children: /* @__PURE__ */ jsx("p", { children: isFetchingPost ? "Loading blog..." : fetchError || "Could not load blog content." }) }) }) });
|
|
531
|
+
}
|
|
435
532
|
return /* @__PURE__ */ jsxs("div", { className: styles.wrapper, children: [
|
|
436
533
|
/* @__PURE__ */ jsx(
|
|
437
534
|
"div",
|
|
@@ -465,7 +562,7 @@ function BlogRenderer({ post }) {
|
|
|
465
562
|
formattedDate
|
|
466
563
|
] })
|
|
467
564
|
] }),
|
|
468
|
-
/* @__PURE__ */ jsx("h1", { className: styles.postTitle, children:
|
|
565
|
+
/* @__PURE__ */ jsx("h1", { className: styles.postTitle, children: postData.title })
|
|
469
566
|
]
|
|
470
567
|
}
|
|
471
568
|
) }) }),
|
|
@@ -483,7 +580,7 @@ function BlogRenderer({ post }) {
|
|
|
483
580
|
"img",
|
|
484
581
|
{
|
|
485
582
|
src: thumbUrl,
|
|
486
|
-
alt:
|
|
583
|
+
alt: postData.title,
|
|
487
584
|
className: styles.thumbnail
|
|
488
585
|
}
|
|
489
586
|
)
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/BlogRenderer.tsx","#style-inject:#style-inject","../src/BlogRenderer.css","../src/tiptap-renderer.ts","../src/index.ts"],"sourcesContent":["import React, { useEffect, useRef, useState } from \"react\";\nimport \"./BlogRenderer.css\";\nimport { renderTiptapToHTML } from \"./tiptap-renderer\";\n\nconst styles = {\n wrapper: \"edlsb-wrapper\",\n progressBar: \"edlsb-progressBar\",\n main: \"edlsb-main\",\n headerSection: \"edlsb-headerSection\",\n headerContainer: \"edlsb-headerContainer\",\n headerContent: \"edlsb-headerContent\",\n metaRow: \"edlsb-metaRow\",\n articleBadge: \"edlsb-articleBadge\",\n dot: \"edlsb-dot\",\n metaItem: \"edlsb-metaItem\",\n icon: \"edlsb-icon\",\n postTitle: \"edlsb-postTitle\",\n bodyContainer: \"edlsb-bodyContainer\",\n thumbnailWrapper: \"edlsb-thumbnailWrapper\",\n thumbnail: \"edlsb-thumbnail\",\n tocWrapper: \"edlsb-tocWrapper\",\n tocToggle: \"edlsb-tocToggle\",\n tocToggleInner: \"edlsb-tocToggleInner\",\n tocIconWrap: \"edlsb-tocIconWrap\",\n tocLabel: \"edlsb-tocLabel\",\n tocCount: \"edlsb-tocCount\",\n chevronIcon: \"edlsb-chevronIcon\",\n chevronIconOpen: \"edlsb-chevronIconOpen\",\n tocPanelInner: \"edlsb-tocPanelInner\",\n tocGrid: \"edlsb-tocGrid\",\n tocItem: \"edlsb-tocItem\",\n tocItemActive: \"edlsb-tocItemActive\",\n tocItemLevel3: \"edlsb-tocItemLevel3\",\n tocBadge: \"edlsb-tocBadge\",\n tocBadgeActive: \"edlsb-tocBadgeActive\",\n tocItemText: \"edlsb-tocItemText\",\n shareSection: \"edlsb-shareSection\",\n shareInner: \"edlsb-shareInner\",\n shareTitle: \"edlsb-shareTitle\",\n shareSubtitle: \"edlsb-shareSubtitle\",\n shareButtons: \"edlsb-shareButtons\",\n shareBtn: \"edlsb-shareBtn\",\n shareBtnInner: \"edlsb-shareBtnInner\",\n copyBtn: \"edlsb-copyBtn\",\n} as const;\n\nconst baseIconProps = {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"1em\",\n height: \"1em\",\n style: { flexShrink: 0 },\n \"aria-hidden\": true,\n} as const;\n\nconst Calendar = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" strokeWidth=\"2\" />\n <line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\" strokeWidth=\"2\" />\n <line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\" strokeWidth=\"2\" />\n <line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst Clock = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <circle cx=\"12\" cy=\"12\" r=\"9\" strokeWidth=\"2\" />\n <line x1=\"12\" y1=\"7\" x2=\"12\" y2=\"12\" strokeWidth=\"2\" />\n <line x1=\"12\" y1=\"12\" x2=\"15\" y2=\"14\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst ChevronDown = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <polyline points=\"6 9 12 15 18 9\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst Twitter = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M18.244 2h3.308l-7.227 8.26L22.8 22h-6.637l-5.197-6.787L4.99 22H1.68l7.73-8.835L1.2 2h6.806l4.697 6.21L18.244 2Zm-1.16 18h1.833L7.01 3.896H5.044L17.083 20Z\" />\n </svg>\n);\n\nconst Linkedin = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M6.94 8.5a1.94 1.94 0 1 1 0-3.88 1.94 1.94 0 0 1 0 3.88ZM5.26 9.94h3.36V20H5.26V9.94Zm5.28 0h3.22v1.38h.05c.45-.85 1.55-1.74 3.2-1.74 3.42 0 4.05 2.1 4.05 4.84V20h-3.36v-4.95c0-1.18-.02-2.7-1.78-2.7-1.78 0-2.05 1.3-2.05 2.62V20h-3.33V9.94Z\" />\n </svg>\n);\n\nconst Facebook = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M13.5 22v-8h2.7l.5-3h-3.2V9.2c0-.9.3-1.5 1.6-1.5h1.7V5c-.3 0-1.3-.1-2.4-.1-2.4 0-4 1.4-4 3.9V11H8v3h2.4v8h3.1Z\" />\n </svg>\n);\n\nconst LinkIcon = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path\n d=\"M10 14a5 5 0 0 1 0-7l1.5-1.5a5 5 0 0 1 7 7L17 14\"\n strokeWidth=\"2\"\n />\n <path\n d=\"M14 10a5 5 0 0 1 0 7l-1.5 1.5a5 5 0 1 1-7-7L7 10\"\n strokeWidth=\"2\"\n />\n </svg>\n);\n\ninterface SingleBlogClientProps {\n post: BlogPost;\n}\n\ninterface BlogPostProfile {\n username: string;\n display_name: string;\n account_level: string;\n profile_picture: string;\n}\n\ninterface BlogPost {\n id: number;\n title: string;\n content_json: string;\n thumbnail_path: string | null;\n keywords: string;\n followers_only: boolean;\n visibility: string;\n created_at: string;\n link_slug: string;\n updated_at: string;\n profile: BlogPostProfile;\n ads_step: number;\n banner_ads: boolean;\n video_ads: boolean;\n}\n\ninterface TocItem {\n id: string;\n text: string;\n level: 2 | 3;\n}\n\nconst stripProseWrappers = (html: string): string => {\n // Repeatedly unwrap outermost <div class=\"prose ...\"> until none remain.\n let result = html;\n const proseDiv = /^\\s*<div[^>]*\\bprose\\b[^>]*>([\\s\\S]*)<\\/div>\\s*$/;\n let prev = \"\";\n\n while (prev !== result) {\n prev = result;\n const match = result.match(proseDiv);\n if (match) result = match[1].trim();\n }\n\n return result;\n};\n\nexport function BlogRenderer({ post }: SingleBlogClientProps) {\n const rawHtml = post.content_json\n ? renderTiptapToHTML(post.content_json)\n : \"<p>Could not render content</p>\";\n const htmlContent = stripProseWrappers(rawHtml);\n\n const articleRef = useRef<HTMLDivElement>(null);\n const [renderedHtml, setRenderedHtml] = useState(htmlContent);\n const [tocItems, setTocItems] = useState<TocItem[]>([]);\n const [activeHeadingId, setActiveHeadingId] = useState(\"\");\n\n const [tocOpen, setTocOpen] = useState(false);\n const [scrollProgress, setScrollProgress] = useState(0);\n const [mounted, setMounted] = useState(false);\n\n const getFullThumbnail = (path: string | undefined | null) => {\n if (!path) return undefined;\n if (path.startsWith(\"http\")) return path;\n return `https://cdn.dl.surf/${path}`;\n };\n\n // Use the pre-rendered htmlContent passed as a prop\n const thumbUrl = getFullThumbnail(post.thumbnail_path);\n const publishDate = post.created_at || new Date().toISOString();\n const formattedDate = new Date(publishDate).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n\n // Share handlers\n const shareUrl = typeof window !== \"undefined\" ? window.location.href : \"\";\n const shareTitle = post.title;\n\n const shareOnTwitter = () =>\n window.open(\n `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}`,\n \"_blank\",\n );\n const shareOnFacebook = () =>\n window.open(\n `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`,\n \"_blank\",\n );\n const shareOnLinkedIn = () =>\n window.open(\n `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`,\n \"_blank\",\n );\n const copyLink = () => {\n navigator.clipboard.writeText(shareUrl);\n alert(\"Link copied to clipboard!\");\n };\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n const handleScroll = () => {\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n const docHeight =\n document.documentElement.scrollHeight - window.innerHeight;\n setScrollProgress(docHeight > 0 ? scrollTop / docHeight : 0);\n };\n window.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", handleScroll);\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const wrapper = document.createElement(\"div\");\n wrapper.innerHTML = htmlContent || \"\";\n\n const headings = Array.from(wrapper.querySelectorAll(\"h2, h3\"));\n const nextTocItems: TocItem[] = [];\n const usedIds = new Set<string>();\n\n headings.forEach((heading, index) => {\n const text = heading.textContent?.trim() || `Section ${index + 1}`;\n const level = heading.tagName.toLowerCase() === \"h2\" ? 2 : 3;\n const base =\n text\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, \"\")\n .trim()\n .replace(/\\s+/g, \"-\") || `section-${index + 1}`;\n\n let id = base;\n let suffix = 2;\n while (usedIds.has(id)) {\n id = `${base}-${suffix}`;\n suffix += 1;\n }\n usedIds.add(id);\n\n heading.setAttribute(\"id\", id);\n nextTocItems.push({ id, text, level });\n });\n\n setRenderedHtml(wrapper.innerHTML);\n setTocItems(nextTocItems);\n }, [htmlContent]);\n\n useEffect(() => {\n if (!tocItems.length) return;\n\n const headingElements = tocItems\n .map((item) => document.getElementById(item.id))\n .filter((el): el is HTMLElement => Boolean(el));\n\n if (!headingElements.length) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n const visible = entries\n .filter((entry) => entry.isIntersecting)\n .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);\n\n if (visible[0]?.target?.id) {\n setActiveHeadingId(visible[0].target.id);\n }\n },\n { rootMargin: \"-90px 0px -65% 0px\", threshold: 0.1 },\n );\n\n headingElements.forEach((el) => observer.observe(el));\n return () => observer.disconnect();\n }, [tocItems, renderedHtml]);\n\n const scrollToHeading = (id: string) => {\n setTocOpen(false);\n // Wait for collapse animation to finish before calculating position\n setTimeout(() => {\n const el = document.getElementById(id);\n if (!el) return;\n const offset = 110;\n const top = el.getBoundingClientRect().top + window.scrollY - offset;\n window.scrollTo({ top, behavior: \"smooth\" });\n }, 280);\n };\n\n return (\n <div className={styles.wrapper}>\n <div\n className={styles.progressBar}\n style={{ transform: `scaleX(${scrollProgress})` }}\n />\n\n <main className={styles.main} ref={articleRef}>\n <div className={styles.headerSection}>\n <div className={styles.headerContainer}>\n <div\n className={styles.headerContent}\n style={{\n opacity: mounted ? 1 : 0,\n transform: mounted ? \"none\" : \"translateY(20px)\",\n transition: \"opacity 0.4s ease, transform 0.4s ease\",\n }}\n >\n <div className={styles.metaRow}>\n <span className={styles.articleBadge}>Article</span>\n <span className={styles.dot} />\n <span className={styles.metaItem}>\n <Clock className={styles.icon} /> 5 Min Read\n </span>\n <span className={styles.dot} />\n <span className={styles.metaItem}>\n <Calendar className={styles.icon} /> {formattedDate}\n </span>\n </div>\n\n <h1 className={styles.postTitle}>{post.title}</h1>\n </div>\n </div>\n </div>\n\n <div className={styles.bodyContainer}>\n {thumbUrl && (\n <div\n className={styles.thumbnailWrapper}\n style={{\n opacity: mounted ? 1 : 0,\n transform: mounted ? \"none\" : \"scale(0.95)\",\n transition: \"opacity 0.4s ease 0.2s, transform 0.4s ease 0.2s\",\n }}\n >\n <img\n src={thumbUrl}\n alt={post.title}\n className={styles.thumbnail}\n />\n </div>\n )}\n\n {/* Table of Contents โ collapsible */}\n {tocItems.length > 0 && (\n <div className={styles.tocWrapper}>\n <button\n onClick={() => setTocOpen(!tocOpen)}\n className={styles.tocToggle}\n >\n <div className={styles.tocToggleInner}>\n <span className={styles.tocIconWrap}>\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M2 4h12M2 8h8M2 12h10\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n </span>\n <span className={styles.tocLabel}>\n Table of contents\n <span className={styles.tocCount}>\n ยท {tocItems.length} sections\n </span>\n </span>\n </div>\n <ChevronDown\n className={`${styles.chevronIcon}${tocOpen ? ` ${styles.chevronIconOpen}` : \"\"}`}\n />\n </button>\n\n <div\n style={{\n overflow: \"hidden\",\n maxHeight: tocOpen ? \"2000px\" : \"0px\",\n opacity: tocOpen ? 1 : 0,\n transition:\n \"max-height 0.25s ease-in-out, opacity 0.25s ease-in-out\",\n }}\n >\n <div className={styles.tocPanelInner}>\n <div className={styles.tocGrid}>\n {tocItems.map((item, idx) => (\n <button\n key={item.id}\n onClick={() => scrollToHeading(item.id)}\n className={[\n styles.tocItem,\n activeHeadingId === item.id\n ? styles.tocItemActive\n : \"\",\n item.level === 3 ? styles.tocItemLevel3 : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n <span\n className={[\n styles.tocBadge,\n activeHeadingId === item.id\n ? styles.tocBadgeActive\n : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n {idx + 1}\n </span>\n <span className={styles.tocItemText}>{item.text}</span>\n </button>\n ))}\n </div>\n </div>\n </div>\n </div>\n )}\n\n <div\n className=\"blog-content\"\n dangerouslySetInnerHTML={{ __html: renderedHtml }}\n />\n\n {/* Share section */}\n <div className={styles.shareSection}>\n <div className={styles.shareInner}>\n <div>\n <h3 className={styles.shareTitle}>Share this article</h3>\n <p className={styles.shareSubtitle}>\n If it helped, pass it on.\n </p>\n </div>\n <div className={styles.shareButtons}>\n <button onClick={shareOnTwitter} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Twitter className={styles.icon} /> X\n </span>\n </button>\n <button onClick={shareOnLinkedIn} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Linkedin className={styles.icon} /> LinkedIn\n </span>\n </button>\n <button onClick={shareOnFacebook} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Facebook className={styles.icon} /> Facebook\n </span>\n </button>\n <button\n onClick={copyLink}\n className={styles.copyBtn}\n title=\"Copy link\"\n >\n <LinkIcon className={styles.icon} />\n </button>\n </div>\n </div>\n </div>\n </div>\n </main>\n </div>\n );\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".edlsb-wrapper {\\n background-color: #f8fafc;\\n font-family:\\n var(--font-merriweather),\\n \\\"Merriweather\\\",\\n Georgia,\\n serif;\\n}\\n.edlsb-progressBar {\\n position: fixed;\\n top: 0;\\n left: 0;\\n right: 0;\\n height: 4px;\\n background-color: #4f46e5;\\n transform-origin: left center;\\n z-index: 100;\\n}\\n.edlsb-main {\\n position: relative;\\n}\\n.edlsb-headerSection {\\n padding-top: 2rem;\\n padding-bottom: 5rem;\\n position: relative;\\n overflow: hidden;\\n}\\n.edlsb-headerContainer {\\n width: 100%;\\n max-width: 56rem;\\n margin: 0 auto;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n position: relative;\\n z-index: 10;\\n}\\n.edlsb-headerContent {\\n text-align: center;\\n}\\n.edlsb-headerContent > * + * {\\n margin-top: 2rem;\\n}\\n@media (min-width: 768px) {\\n .edlsb-headerContent {\\n text-align: left;\\n }\\n}\\n.edlsb-metaRow {\\n display: flex;\\n flex-wrap: wrap;\\n align-items: center;\\n gap: 1rem;\\n color: #94a3b8;\\n font-weight: 700;\\n font-size: 0.75rem;\\n line-height: 1rem;\\n text-transform: uppercase;\\n letter-spacing: 0.1em;\\n justify-content: center;\\n}\\n@media (min-width: 768px) {\\n .edlsb-metaRow {\\n justify-content: flex-start;\\n }\\n}\\n.edlsb-articleBadge {\\n padding: 0.25rem 0.75rem;\\n background-color: #ffffff;\\n border-radius: 0.5rem;\\n border: 1px solid #e2e8f0;\\n color: #4f46e5;\\n}\\n.edlsb-dot {\\n width: 0.25rem;\\n height: 0.25rem;\\n background-color: #cbd5e1;\\n border-radius: 9999px;\\n}\\n.edlsb-metaItem {\\n display: flex;\\n align-items: center;\\n gap: 0.5rem;\\n}\\n.edlsb-icon {\\n width: 1rem;\\n height: 1rem;\\n}\\n.edlsb-postTitle {\\n font-size: 2.25rem;\\n line-height: 0.95;\\n font-weight: 1000;\\n color: #0f172a;\\n letter-spacing: -0.04em;\\n}\\n@media (min-width: 768px) {\\n .edlsb-postTitle {\\n font-size: 3.75rem;\\n }\\n}\\n@media (min-width: 1024px) {\\n .edlsb-postTitle {\\n font-size: 4.5rem;\\n }\\n}\\n.edlsb-bodyContainer {\\n width: 100%;\\n max-width: 56rem;\\n margin: 0 auto;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n padding-top: 5rem;\\n padding-bottom: 5rem;\\n}\\n.edlsb-thumbnailWrapper {\\n margin-bottom: 2rem;\\n border-radius: 1rem;\\n overflow: hidden;\\n box-shadow: 0 25px 50px -12px rgb(49 46 129 / 0.1);\\n margin-top: -8rem;\\n position: relative;\\n z-index: 20;\\n}\\n.edlsb-thumbnail {\\n width: 100%;\\n height: auto;\\n object-fit: cover;\\n max-height: 600px;\\n}\\n.edlsb-tocWrapper {\\n margin-bottom: 2.5rem;\\n}\\n.edlsb-tocToggle {\\n width: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n gap: 0.75rem;\\n padding: 1rem 1.25rem;\\n border-radius: 1rem;\\n background-color: rgba(241, 245, 249, 0.8);\\n border: none;\\n cursor: pointer;\\n transition-property: all;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocToggle:hover {\\n background-color: #f1f5f9;\\n}\\n.edlsb-tocToggle:hover .edlsb-tocIconWrap {\\n color: #334155;\\n}\\n.edlsb-tocToggleInner {\\n display: flex;\\n align-items: center;\\n gap: 0.75rem;\\n}\\n.edlsb-tocIconWrap {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n width: 1.75rem;\\n height: 1.75rem;\\n border-radius: 0.5rem;\\n background-color: rgba(226, 232, 240, 0.8);\\n color: #64748b;\\n transition-property: color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocLabel {\\n font-size: 13px;\\n font-weight: 600;\\n color: #475569;\\n}\\n.edlsb-tocCount {\\n color: #94a3b8;\\n font-weight: 400;\\n margin-left: 0.25rem;\\n}\\n.edlsb-chevronIcon {\\n width: 1rem;\\n height: 1rem;\\n color: #94a3b8;\\n transition-property: transform;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 200ms;\\n}\\n.edlsb-chevronIconOpen {\\n transform: rotate(180deg);\\n}\\n.edlsb-tocPanelInner {\\n margin-top: 0.375rem;\\n border-radius: 1rem;\\n background-color: rgba(241, 245, 249, 0.8);\\n padding: 0.5rem;\\n}\\n.edlsb-tocGrid {\\n display: grid;\\n grid-template-columns: 1fr;\\n gap: 0.125rem;\\n}\\n@media (min-width: 640px) {\\n .edlsb-tocGrid {\\n grid-template-columns: repeat(2, minmax(0, 1fr));\\n }\\n}\\n.edlsb-tocItem {\\n display: flex;\\n align-items: center;\\n gap: 0.625rem;\\n text-align: left;\\n border-radius: 0.75rem;\\n padding: 0.625rem 0.75rem;\\n font-size: 13px;\\n border: none;\\n cursor: pointer;\\n background-color: transparent;\\n color: #475569;\\n transition-property: all;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocItem:hover {\\n background-color: #ffffff;\\n color: #0f172a;\\n box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\\n}\\n.edlsb-tocItemActive {\\n background-color: #0f172a;\\n color: #ffffff;\\n box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\\n}\\n.edlsb-tocItemActive:hover {\\n background-color: #0f172a;\\n color: #ffffff;\\n}\\n.edlsb-tocItemLevel3 {\\n padding-left: 2rem;\\n}\\n.edlsb-tocBadge {\\n flex-shrink: 0;\\n width: 1.25rem;\\n height: 1.25rem;\\n border-radius: 0.375rem;\\n font-size: 10px;\\n font-weight: 700;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n background-color: rgba(226, 232, 240, 0.8);\\n color: #94a3b8;\\n}\\n.edlsb-tocBadgeActive {\\n background-color: rgba(255, 255, 255, 0.15);\\n color: rgba(255, 255, 255, 0.8);\\n}\\n.edlsb-tocItemText {\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n.edlsb-shareSection {\\n margin-top: 3.5rem;\\n border-top: 1px solid #e2e8f0;\\n padding-top: 1.5rem;\\n}\\n.edlsb-shareInner {\\n display: flex;\\n flex-direction: column;\\n gap: 1rem;\\n}\\n@media (min-width: 640px) {\\n .edlsb-shareInner {\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-between;\\n }\\n}\\n.edlsb-shareTitle {\\n font-size: 1rem;\\n line-height: 1.5rem;\\n font-weight: 600;\\n color: #0f172a;\\n}\\n.edlsb-shareSubtitle {\\n font-size: 0.875rem;\\n line-height: 1.25rem;\\n color: #64748b;\\n}\\n.edlsb-shareButtons {\\n display: flex;\\n align-items: center;\\n gap: 0.625rem;\\n}\\n.edlsb-shareBtn {\\n height: 2.5rem;\\n padding-left: 1rem;\\n padding-right: 1rem;\\n border-radius: 9999px;\\n border: 1px solid #e2e8f0;\\n background-color: #ffffff;\\n color: #334155;\\n font-size: 0.875rem;\\n font-weight: 600;\\n cursor: pointer;\\n transition-property:\\n color,\\n background-color,\\n border-color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-shareBtn:hover {\\n border-color: #cbd5e1;\\n color: #0f172a;\\n}\\n.edlsb-shareBtnInner {\\n display: inline-flex;\\n align-items: center;\\n gap: 0.5rem;\\n}\\n.edlsb-copyBtn {\\n height: 2.5rem;\\n width: 2.5rem;\\n border-radius: 9999px;\\n border: 1px solid #e2e8f0;\\n background-color: #ffffff;\\n color: #475569;\\n cursor: pointer;\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n transition-property:\\n color,\\n background-color,\\n border-color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-copyBtn:hover {\\n border-color: #cbd5e1;\\n color: #0f172a;\\n}\\n.edlsb-bodyContainer .blog-content {\\n color: #1e293b;\\n line-height: 1.8;\\n font-size: 1.05rem;\\n overflow-wrap: anywhere;\\n}\\n.edlsb-bodyContainer .blog-content > * + * {\\n margin-top: 1rem;\\n}\\n.edlsb-bodyContainer .blog-content pre {\\n margin: 1.25rem 0;\\n padding: 0.9rem 1rem;\\n border-radius: 0.75rem;\\n background: #0b1220;\\n color: #e2e8f0;\\n overflow-x: auto;\\n border: 1px solid #1f2937;\\n -webkit-overflow-scrolling: touch;\\n}\\n.edlsb-bodyContainer .blog-content pre code {\\n background: transparent;\\n padding: 0;\\n border-radius: 0;\\n color: inherit;\\n font-size: 0.875rem;\\n line-height: 1.6;\\n}\\n.edlsb-bodyContainer .blog-content code {\\n font-family:\\n ui-monospace,\\n SFMono-Regular,\\n Menlo,\\n Monaco,\\n Consolas,\\n \\\"Liberation Mono\\\",\\n \\\"Courier New\\\",\\n monospace;\\n background: #e2e8f0;\\n color: #0f172a;\\n padding: 0.15rem 0.4rem;\\n border-radius: 0.35rem;\\n font-size: 0.875em;\\n}\\n.edlsb-bodyContainer .blog-content img {\\n display: block;\\n max-width: 100%;\\n width: auto;\\n height: auto;\\n margin: 1.25rem auto;\\n border-radius: 0.75rem;\\n}\\n.edlsb-bodyContainer .blog-content a {\\n color: #2563eb;\\n text-decoration: underline;\\n text-underline-offset: 2px;\\n}\\n\")","/**\n * SSR-safe Tiptap JSON โ HTML renderer.\n *\n * Pure recursive serializer โ zero DOM APIs required.\n * Works in Next.js server components and any Node.js environment.\n *\n * Styling matches the Blog View Styling Guide exactly:\n * - prose prose-lg dark:prose-invert wrapper classes\n * - per-node Tailwind classes identical to the Tiptap extension HTMLAttributes\n * - dark-mode variants included throughout\n */\n\n// โโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n// โโโ Mark renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction applyMark(html: string, mark: any): string {\n switch (mark.type) {\n case 'bold':\n return `<strong>${html}</strong>`;\n case 'italic':\n return `<em>${html}</em>`;\n case 'strike':\n return `<s>${html}</s>`;\n case 'underline':\n return `<u>${html}</u>`;\n case 'code':\n return `<code>${html}</code>`;\n case 'link': {\n const href = escapeHtml(mark.attrs?.href ?? '');\n const target = escapeHtml(mark.attrs?.target ?? '_blank');\n return `<a href=\"${href}\" target=\"${target}\" rel=\"noopener noreferrer\">${html}</a>`;\n }\n case 'textStyle': {\n // Handles color / font-size set via the TextStyle extension\n const color = mark.attrs?.color;\n const fontSize = mark.attrs?.fontSize;\n const style = [\n color ? `color:${color}` : '',\n fontSize ? `font-size:${fontSize}` : '',\n ].filter(Boolean).join(';');\n return style ? `<span style=\"${style}\">${html}</span>` : html;\n }\n case 'highlight': {\n const color = mark.attrs?.color;\n const style = color ? ` style=\"background-color:${color}\"` : '';\n return `<mark${style}>${html}</mark>`;\n }\n default:\n return html;\n }\n}\n\n// โโโ Node renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction renderChildren(node: any): string {\n if (!Array.isArray(node?.content)) return '';\n return node.content.map(renderNode).join('');\n}\n\nfunction renderNode(node: any): string {\n if (!node) return '';\n\n switch (node.type) {\n\n // โโ Document root โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'doc':\n return renderChildren(node);\n\n // โโ Block nodes โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'paragraph': {\n const inner = renderChildren(node);\n if (!inner) return `<p><br /></p>`;\n return `<p>${inner}</p>`;\n }\n\n case 'heading': {\n const level = node.attrs?.level ?? 1;\n return `<h${level}>${renderChildren(node)}</h${level}>`;\n }\n\n case 'blockquote':\n return `<blockquote>${renderChildren(node)}</blockquote>`;\n\n case 'bulletList':\n return `<ul>${renderChildren(node)}</ul>`;\n\n case 'orderedList':\n return `<ol>${renderChildren(node)}</ol>`;\n\n case 'listItem':\n return `<li>${renderChildren(node)}</li>`;\n\n case 'taskList':\n return `<ul class=\"task-list\">${renderChildren(node)}</ul>`;\n\n case 'taskItem': {\n const checked = node.attrs?.checked ? ' checked' : '';\n return `<li class=\"task-item\"><input type=\"checkbox\"${checked} disabled /><div>${renderChildren(node)}</div></li>`;\n }\n\n case 'codeBlock': {\n const lang = node.attrs?.language ? ` data-language=\"${escapeHtml(node.attrs.language)}\"` : '';\n return `<pre${lang}><code>${renderChildren(node)}</code></pre>`;\n }\n\n case 'horizontalRule':\n return `<hr />`;\n\n case 'hardBreak':\n return `<br />`;\n\n // โโ Media / embeds โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'image': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const alt = escapeHtml(node.attrs?.alt ?? '');\n const title = node.attrs?.title ? ` title=\"${escapeHtml(node.attrs.title)}\"` : '';\n return `<img src=\"${src}\" alt=\"${alt}\"${title} loading=\"lazy\" />`;\n }\n\n case 'resizableImage': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const alt = escapeHtml(node.attrs?.alt ?? '');\n const width = node.attrs?.width ? ` width=\"${escapeHtml(String(node.attrs.width))}\"` : '';\n return `<img src=\"${src}\" alt=\"${alt}\"${width} loading=\"lazy\" />`;\n }\n\n case 'youtube': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const width = node.attrs?.width ?? 640;\n const height = node.attrs?.height ?? 480;\n if (!src) return '';\n return `<div class=\"rounded-lg my-4 overflow-hidden\" style=\"position:relative;padding-bottom:56.25%;height:0;\"><iframe src=\"${src}\" width=\"${width}\" height=\"${height}\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe></div>`;\n }\n\n case 'twitter':\n case 'tweet': {\n // Render a simple link card since Twitter embeds require client-side JS\n const url = escapeHtml(node.attrs?.src ?? node.attrs?.url ?? '');\n if (!url) return '';\n return `<div class=\"max-w-xl\"><a href=\"${url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-themecolor font-semibold underline underline-offset-[3px] hover:text-themecolorhover transition-colors\">${url}</a></div>`;\n }\n\n // โโ Inline nodes โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'text': {\n let out = escapeHtml(node.text ?? '');\n if (Array.isArray(node.marks)) {\n for (const mark of node.marks) {\n out = applyMark(out, mark);\n }\n }\n return out;\n }\n\n default:\n // Gracefully handle unknown nodes by rendering their children\n return renderChildren(node);\n }\n}\n\n// โโโ Public API โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport function renderTiptapToHTML(\n jsonContent: Record<string, any> | string,\n): string {\n try {\n if (!jsonContent) return '';\n\n let contentObj: any;\n if (typeof jsonContent === 'string') {\n try {\n contentObj = JSON.parse(jsonContent);\n } catch {\n // Not valid JSON โ treat as a raw HTML string and return as-is\n return jsonContent;\n }\n } else {\n contentObj = jsonContent;\n }\n\n if (!contentObj || typeof contentObj !== 'object') {\n return String(jsonContent);\n }\n\n return renderNode(contentObj);\n } catch (error) {\n console.error('Failed to parse Tiptap JSON to HTML:', error);\n return '<p>Error loading content.</p>';\n }\n}\n","export { BlogRenderer } from \"./BlogRenderer\";\nexport { BlogRenderer as default } from \"./BlogRenderer\";\nexport function renderText() {\n return \"Hello World\";\n}\nexport type { JSONContent } from \"@tiptap/core\";\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/BlogRenderer.tsx","#style-inject:#style-inject","../src/BlogRenderer.css","../src/tiptap-renderer.ts","../src/index.ts"],"sourcesContent":["import React, { useEffect, useRef, useState } from \"react\";\nimport \"./BlogRenderer.css\";\nimport { renderTiptapToHTML } from \"./tiptap-renderer\";\n\nconst styles = {\n wrapper: \"edlsb-wrapper\",\n progressBar: \"edlsb-progressBar\",\n main: \"edlsb-main\",\n headerSection: \"edlsb-headerSection\",\n headerContainer: \"edlsb-headerContainer\",\n headerContent: \"edlsb-headerContent\",\n metaRow: \"edlsb-metaRow\",\n articleBadge: \"edlsb-articleBadge\",\n dot: \"edlsb-dot\",\n metaItem: \"edlsb-metaItem\",\n icon: \"edlsb-icon\",\n postTitle: \"edlsb-postTitle\",\n bodyContainer: \"edlsb-bodyContainer\",\n thumbnailWrapper: \"edlsb-thumbnailWrapper\",\n thumbnail: \"edlsb-thumbnail\",\n tocWrapper: \"edlsb-tocWrapper\",\n tocToggle: \"edlsb-tocToggle\",\n tocToggleInner: \"edlsb-tocToggleInner\",\n tocIconWrap: \"edlsb-tocIconWrap\",\n tocLabel: \"edlsb-tocLabel\",\n tocCount: \"edlsb-tocCount\",\n chevronIcon: \"edlsb-chevronIcon\",\n chevronIconOpen: \"edlsb-chevronIconOpen\",\n tocPanelInner: \"edlsb-tocPanelInner\",\n tocGrid: \"edlsb-tocGrid\",\n tocItem: \"edlsb-tocItem\",\n tocItemActive: \"edlsb-tocItemActive\",\n tocItemLevel3: \"edlsb-tocItemLevel3\",\n tocBadge: \"edlsb-tocBadge\",\n tocBadgeActive: \"edlsb-tocBadgeActive\",\n tocItemText: \"edlsb-tocItemText\",\n shareSection: \"edlsb-shareSection\",\n shareInner: \"edlsb-shareInner\",\n shareTitle: \"edlsb-shareTitle\",\n shareSubtitle: \"edlsb-shareSubtitle\",\n shareButtons: \"edlsb-shareButtons\",\n shareBtn: \"edlsb-shareBtn\",\n shareBtnInner: \"edlsb-shareBtnInner\",\n copyBtn: \"edlsb-copyBtn\",\n} as const;\n\nconst baseIconProps = {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"1em\",\n height: \"1em\",\n style: { flexShrink: 0 },\n \"aria-hidden\": true,\n} as const;\n\nconst Calendar = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\" strokeWidth=\"2\" />\n <line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\" strokeWidth=\"2\" />\n <line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\" strokeWidth=\"2\" />\n <line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst Clock = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <circle cx=\"12\" cy=\"12\" r=\"9\" strokeWidth=\"2\" />\n <line x1=\"12\" y1=\"7\" x2=\"12\" y2=\"12\" strokeWidth=\"2\" />\n <line x1=\"12\" y1=\"12\" x2=\"15\" y2=\"14\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst ChevronDown = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <polyline points=\"6 9 12 15 18 9\" strokeWidth=\"2\" />\n </svg>\n);\n\nconst Twitter = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M18.244 2h3.308l-7.227 8.26L22.8 22h-6.637l-5.197-6.787L4.99 22H1.68l7.73-8.835L1.2 2h6.806l4.697 6.21L18.244 2Zm-1.16 18h1.833L7.01 3.896H5.044L17.083 20Z\" />\n </svg>\n);\n\nconst Linkedin = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M6.94 8.5a1.94 1.94 0 1 1 0-3.88 1.94 1.94 0 0 1 0 3.88ZM5.26 9.94h3.36V20H5.26V9.94Zm5.28 0h3.22v1.38h.05c.45-.85 1.55-1.74 3.2-1.74 3.42 0 4.05 2.1 4.05 4.84V20h-3.36v-4.95c0-1.18-.02-2.7-1.78-2.7-1.78 0-2.05 1.3-2.05 2.62V20h-3.33V9.94Z\" />\n </svg>\n);\n\nconst Facebook = (props: React.SVGProps<SVGSVGElement>) => (\n <svg {...baseIconProps} viewBox=\"0 0 24 24\" fill=\"currentColor\" {...props}>\n <path d=\"M13.5 22v-8h2.7l.5-3h-3.2V9.2c0-.9.3-1.5 1.6-1.5h1.7V5c-.3 0-1.3-.1-2.4-.1-2.4 0-4 1.4-4 3.9V11H8v3h2.4v8h3.1Z\" />\n </svg>\n);\n\nconst LinkIcon = (props: React.SVGProps<SVGSVGElement>) => (\n <svg\n {...baseIconProps}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <path\n d=\"M10 14a5 5 0 0 1 0-7l1.5-1.5a5 5 0 0 1 7 7L17 14\"\n strokeWidth=\"2\"\n />\n <path\n d=\"M14 10a5 5 0 0 1 0 7l-1.5 1.5a5 5 0 1 1-7-7L7 10\"\n strokeWidth=\"2\"\n />\n </svg>\n);\n\ninterface BlogRendererProps {\n post?: BlogPost;\n user?: string;\n linkSlug?: string;\n blogUrl?: string;\n}\n\ninterface BlogPostProfile {\n username: string;\n display_name: string;\n account_level: string;\n profile_picture: string;\n}\n\ninterface BlogPost {\n id: number;\n title: string;\n content_json: string;\n thumbnail_path: string | null;\n keywords: string;\n followers_only: boolean;\n visibility: string;\n created_at: string;\n link_slug: string;\n updated_at: string;\n profile: BlogPostProfile;\n ads_step: number;\n banner_ads: boolean;\n video_ads: boolean;\n}\n\ninterface TocItem {\n id: string;\n text: string;\n level: 2 | 3;\n}\n\ninterface DocApiResponse {\n status: string;\n message: string;\n data: BlogPost;\n}\n\nconst DEFAULT_DOC_API_BASE_URL = \"https://docapi.dl.surf/api/doc\";\n\nconst normalizeUsername = (value: string): string =>\n value.trim().replace(/^@+/, \"\");\n\nconst parseBlogReference = (\n blogUrl: string,\n): { user: string; linkSlug: string } | null => {\n try {\n const url = new URL(blogUrl);\n const segments = url.pathname.split(\"/\").filter(Boolean);\n\n // Supports: /api/doc/{user}/{linkSlug}\n const apiIndex = segments.findIndex(\n (segment, idx) => segment === \"api\" && segments[idx + 1] === \"doc\",\n );\n if (apiIndex >= 0 && segments.length >= apiIndex + 4) {\n return {\n user: decodeURIComponent(segments[apiIndex + 2]),\n linkSlug: decodeURIComponent(segments[apiIndex + 3]),\n };\n }\n\n // Fallback: use last two path segments as {user}/{linkSlug}\n if (segments.length >= 2) {\n return {\n user: decodeURIComponent(segments[segments.length - 2]),\n linkSlug: decodeURIComponent(segments[segments.length - 1]),\n };\n }\n\n return null;\n } catch {\n return null;\n }\n};\n\nconst stripProseWrappers = (html: string): string => {\n // Repeatedly unwrap outermost <div class=\"prose ...\"> until none remain.\n let result = html;\n const proseDiv = /^\\s*<div[^>]*\\bprose\\b[^>]*>([\\s\\S]*)<\\/div>\\s*$/;\n let prev = \"\";\n\n while (prev !== result) {\n prev = result;\n const match = result.match(proseDiv);\n if (match) result = match[1].trim();\n }\n\n return result;\n};\n\nexport function BlogRenderer({\n post,\n user,\n linkSlug,\n blogUrl,\n}: BlogRendererProps) {\n const [resolvedPost, setResolvedPost] = useState<BlogPost | null>(\n post ?? null,\n );\n const [isFetchingPost, setIsFetchingPost] = useState(!post);\n const [fetchError, setFetchError] = useState<string | null>(null);\n\n useEffect(() => {\n if (post) {\n setResolvedPost(post);\n setIsFetchingPost(false);\n setFetchError(null);\n return;\n }\n\n const directUser = user ? normalizeUsername(user) : undefined;\n const directSlug = linkSlug?.trim();\n let resolvedUser = directUser;\n let resolvedSlug = directSlug;\n\n if ((!resolvedUser || !resolvedSlug) && blogUrl) {\n const parsed = parseBlogReference(blogUrl);\n if (parsed) {\n resolvedUser = normalizeUsername(parsed.user);\n resolvedSlug = parsed.linkSlug;\n }\n }\n\n if (!resolvedUser || !resolvedSlug) {\n setResolvedPost(null);\n setIsFetchingPost(false);\n setFetchError(\n \"Provide either post, user+linkSlug, or a blogUrl with /{user}/{linkSlug}.\",\n );\n return;\n }\n\n const controller = new AbortController();\n const fetchPost = async () => {\n try {\n setIsFetchingPost(true);\n setFetchError(null);\n\n const base = DEFAULT_DOC_API_BASE_URL;\n\n const endpoint = `${base}/${encodeURIComponent(resolvedUser!)}/${encodeURIComponent(resolvedSlug!)}`;\n const response = await fetch(endpoint, { signal: controller.signal });\n\n if (!response.ok) {\n throw new Error(`Failed to fetch document (${response.status})`);\n }\n\n const payload = (await response.json()) as DocApiResponse;\n if (!payload?.data) {\n throw new Error(\"Invalid API response: missing data field\");\n }\n\n setResolvedPost(payload.data);\n } catch (error) {\n if (controller.signal.aborted) return;\n setResolvedPost(null);\n setFetchError(\n error instanceof Error ? error.message : \"Failed to fetch document\",\n );\n } finally {\n if (!controller.signal.aborted) {\n setIsFetchingPost(false);\n }\n }\n };\n\n fetchPost();\n return () => controller.abort();\n }, [post, user, linkSlug, blogUrl]);\n\n const rawHtml = resolvedPost?.content_json\n ? renderTiptapToHTML(resolvedPost.content_json)\n : \"<p>Could not render content</p>\";\n const htmlContent = stripProseWrappers(rawHtml);\n\n const articleRef = useRef<HTMLDivElement>(null);\n const [renderedHtml, setRenderedHtml] = useState(htmlContent);\n const [tocItems, setTocItems] = useState<TocItem[]>([]);\n const [activeHeadingId, setActiveHeadingId] = useState(\"\");\n\n const [tocOpen, setTocOpen] = useState(false);\n const [scrollProgress, setScrollProgress] = useState(0);\n const [mounted, setMounted] = useState(false);\n\n const postData = resolvedPost;\n\n const getFullThumbnail = (path: string | undefined | null) => {\n if (!path) return undefined;\n if (path.startsWith(\"http\")) return path;\n return `https://cdn.dl.surf/${path}`;\n };\n\n const thumbUrl = getFullThumbnail(postData?.thumbnail_path);\n const publishDate = postData?.created_at || new Date().toISOString();\n const formattedDate = new Date(publishDate).toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n\n // Share handlers\n const shareUrl = typeof window !== \"undefined\" ? window.location.href : \"\";\n const shareTitle = postData?.title || \"\";\n\n const shareOnTwitter = () =>\n window.open(\n `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareTitle)}`,\n \"_blank\",\n );\n const shareOnFacebook = () =>\n window.open(\n `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`,\n \"_blank\",\n );\n const shareOnLinkedIn = () =>\n window.open(\n `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`,\n \"_blank\",\n );\n const copyLink = () => {\n navigator.clipboard.writeText(shareUrl);\n alert(\"Link copied to clipboard!\");\n };\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n const handleScroll = () => {\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n const docHeight =\n document.documentElement.scrollHeight - window.innerHeight;\n setScrollProgress(docHeight > 0 ? scrollTop / docHeight : 0);\n };\n window.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => window.removeEventListener(\"scroll\", handleScroll);\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const wrapper = document.createElement(\"div\");\n wrapper.innerHTML = htmlContent || \"\";\n\n const headings = Array.from(wrapper.querySelectorAll(\"h2, h3\"));\n const nextTocItems: TocItem[] = [];\n const usedIds = new Set<string>();\n\n headings.forEach((heading, index) => {\n const text = heading.textContent?.trim() || `Section ${index + 1}`;\n const level = heading.tagName.toLowerCase() === \"h2\" ? 2 : 3;\n const base =\n text\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, \"\")\n .trim()\n .replace(/\\s+/g, \"-\") || `section-${index + 1}`;\n\n let id = base;\n let suffix = 2;\n while (usedIds.has(id)) {\n id = `${base}-${suffix}`;\n suffix += 1;\n }\n usedIds.add(id);\n\n heading.setAttribute(\"id\", id);\n nextTocItems.push({ id, text, level });\n });\n\n setRenderedHtml(wrapper.innerHTML);\n setTocItems(nextTocItems);\n }, [htmlContent]);\n\n useEffect(() => {\n if (!tocItems.length) return;\n\n const headingElements = tocItems\n .map((item) => document.getElementById(item.id))\n .filter((el): el is HTMLElement => Boolean(el));\n\n if (!headingElements.length) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n const visible = entries\n .filter((entry) => entry.isIntersecting)\n .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);\n\n if (visible[0]?.target?.id) {\n setActiveHeadingId(visible[0].target.id);\n }\n },\n { rootMargin: \"-90px 0px -65% 0px\", threshold: 0.1 },\n );\n\n headingElements.forEach((el) => observer.observe(el));\n return () => observer.disconnect();\n }, [tocItems, renderedHtml]);\n\n const scrollToHeading = (id: string) => {\n setTocOpen(false);\n // Wait for collapse animation to finish before calculating position\n setTimeout(() => {\n const el = document.getElementById(id);\n if (!el) return;\n const offset = 110;\n const top = el.getBoundingClientRect().top + window.scrollY - offset;\n window.scrollTo({ top, behavior: \"smooth\" });\n }, 280);\n };\n\n if (!postData) {\n return (\n <div className={styles.wrapper}>\n <main className={styles.main}>\n <div className={styles.bodyContainer}>\n <p>\n {isFetchingPost\n ? \"Loading blog...\"\n : fetchError || \"Could not load blog content.\"}\n </p>\n </div>\n </main>\n </div>\n );\n }\n\n return (\n <div className={styles.wrapper}>\n <div\n className={styles.progressBar}\n style={{ transform: `scaleX(${scrollProgress})` }}\n />\n\n <main className={styles.main} ref={articleRef}>\n <div className={styles.headerSection}>\n <div className={styles.headerContainer}>\n <div\n className={styles.headerContent}\n style={{\n opacity: mounted ? 1 : 0,\n transform: mounted ? \"none\" : \"translateY(20px)\",\n transition: \"opacity 0.4s ease, transform 0.4s ease\",\n }}\n >\n <div className={styles.metaRow}>\n <span className={styles.articleBadge}>Article</span>\n <span className={styles.dot} />\n <span className={styles.metaItem}>\n <Clock className={styles.icon} /> 5 Min Read\n </span>\n <span className={styles.dot} />\n <span className={styles.metaItem}>\n <Calendar className={styles.icon} /> {formattedDate}\n </span>\n </div>\n\n <h1 className={styles.postTitle}>{postData.title}</h1>\n </div>\n </div>\n </div>\n\n <div className={styles.bodyContainer}>\n {thumbUrl && (\n <div\n className={styles.thumbnailWrapper}\n style={{\n opacity: mounted ? 1 : 0,\n transform: mounted ? \"none\" : \"scale(0.95)\",\n transition: \"opacity 0.4s ease 0.2s, transform 0.4s ease 0.2s\",\n }}\n >\n <img\n src={thumbUrl}\n alt={postData.title}\n className={styles.thumbnail}\n />\n </div>\n )}\n\n {/* Table of Contents โ collapsible */}\n {tocItems.length > 0 && (\n <div className={styles.tocWrapper}>\n <button\n onClick={() => setTocOpen(!tocOpen)}\n className={styles.tocToggle}\n >\n <div className={styles.tocToggleInner}>\n <span className={styles.tocIconWrap}>\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M2 4h12M2 8h8M2 12h10\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n </span>\n <span className={styles.tocLabel}>\n Table of contents\n <span className={styles.tocCount}>\n ยท {tocItems.length} sections\n </span>\n </span>\n </div>\n <ChevronDown\n className={`${styles.chevronIcon}${tocOpen ? ` ${styles.chevronIconOpen}` : \"\"}`}\n />\n </button>\n\n <div\n style={{\n overflow: \"hidden\",\n maxHeight: tocOpen ? \"2000px\" : \"0px\",\n opacity: tocOpen ? 1 : 0,\n transition:\n \"max-height 0.25s ease-in-out, opacity 0.25s ease-in-out\",\n }}\n >\n <div className={styles.tocPanelInner}>\n <div className={styles.tocGrid}>\n {tocItems.map((item, idx) => (\n <button\n key={item.id}\n onClick={() => scrollToHeading(item.id)}\n className={[\n styles.tocItem,\n activeHeadingId === item.id\n ? styles.tocItemActive\n : \"\",\n item.level === 3 ? styles.tocItemLevel3 : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n <span\n className={[\n styles.tocBadge,\n activeHeadingId === item.id\n ? styles.tocBadgeActive\n : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n {idx + 1}\n </span>\n <span className={styles.tocItemText}>{item.text}</span>\n </button>\n ))}\n </div>\n </div>\n </div>\n </div>\n )}\n\n <div\n className=\"blog-content\"\n dangerouslySetInnerHTML={{ __html: renderedHtml }}\n />\n\n {/* Share section */}\n <div className={styles.shareSection}>\n <div className={styles.shareInner}>\n <div>\n <h3 className={styles.shareTitle}>Share this article</h3>\n <p className={styles.shareSubtitle}>\n If it helped, pass it on.\n </p>\n </div>\n <div className={styles.shareButtons}>\n <button onClick={shareOnTwitter} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Twitter className={styles.icon} /> X\n </span>\n </button>\n <button onClick={shareOnLinkedIn} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Linkedin className={styles.icon} /> LinkedIn\n </span>\n </button>\n <button onClick={shareOnFacebook} className={styles.shareBtn}>\n <span className={styles.shareBtnInner}>\n <Facebook className={styles.icon} /> Facebook\n </span>\n </button>\n <button\n onClick={copyLink}\n className={styles.copyBtn}\n title=\"Copy link\"\n >\n <LinkIcon className={styles.icon} />\n </button>\n </div>\n </div>\n </div>\n </div>\n </main>\n </div>\n );\n}\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\".edlsb-wrapper {\\n background-color: #f8fafc;\\n font-family:\\n var(--font-merriweather),\\n \\\"Merriweather\\\",\\n Georgia,\\n serif;\\n}\\n.edlsb-progressBar {\\n position: fixed;\\n top: 0;\\n left: 0;\\n right: 0;\\n height: 4px;\\n background-color: #4f46e5;\\n transform-origin: left center;\\n z-index: 100;\\n}\\n.edlsb-main {\\n position: relative;\\n}\\n.edlsb-headerSection {\\n padding-top: 2rem;\\n padding-bottom: 5rem;\\n position: relative;\\n overflow: hidden;\\n}\\n.edlsb-headerContainer {\\n width: 100%;\\n max-width: 56rem;\\n margin: 0 auto;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n position: relative;\\n z-index: 10;\\n}\\n.edlsb-headerContent {\\n text-align: center;\\n}\\n.edlsb-headerContent > * + * {\\n margin-top: 2rem;\\n}\\n@media (min-width: 768px) {\\n .edlsb-headerContent {\\n text-align: left;\\n }\\n}\\n.edlsb-metaRow {\\n display: flex;\\n flex-wrap: wrap;\\n align-items: center;\\n gap: 1rem;\\n color: #94a3b8;\\n font-weight: 700;\\n font-size: 0.75rem;\\n line-height: 1rem;\\n text-transform: uppercase;\\n letter-spacing: 0.1em;\\n justify-content: center;\\n}\\n@media (min-width: 768px) {\\n .edlsb-metaRow {\\n justify-content: flex-start;\\n }\\n}\\n.edlsb-articleBadge {\\n padding: 0.25rem 0.75rem;\\n background-color: #ffffff;\\n border-radius: 0.5rem;\\n border: 1px solid #e2e8f0;\\n color: #4f46e5;\\n}\\n.edlsb-dot {\\n width: 0.25rem;\\n height: 0.25rem;\\n background-color: #cbd5e1;\\n border-radius: 9999px;\\n}\\n.edlsb-metaItem {\\n display: flex;\\n align-items: center;\\n gap: 0.5rem;\\n}\\n.edlsb-icon {\\n width: 1rem;\\n height: 1rem;\\n}\\n.edlsb-postTitle {\\n font-size: 2.25rem;\\n line-height: 0.95;\\n font-weight: 1000;\\n color: #0f172a;\\n letter-spacing: -0.04em;\\n}\\n@media (min-width: 768px) {\\n .edlsb-postTitle {\\n font-size: 3.75rem;\\n }\\n}\\n@media (min-width: 1024px) {\\n .edlsb-postTitle {\\n font-size: 4.5rem;\\n }\\n}\\n.edlsb-bodyContainer {\\n width: 100%;\\n max-width: 56rem;\\n margin: 0 auto;\\n padding-left: 1.5rem;\\n padding-right: 1.5rem;\\n padding-top: 5rem;\\n padding-bottom: 5rem;\\n}\\n.edlsb-thumbnailWrapper {\\n margin-bottom: 2rem;\\n border-radius: 1rem;\\n overflow: hidden;\\n box-shadow: 0 25px 50px -12px rgb(49 46 129 / 0.1);\\n margin-top: -8rem;\\n position: relative;\\n z-index: 20;\\n}\\n.edlsb-thumbnail {\\n width: 100%;\\n height: auto;\\n object-fit: cover;\\n max-height: 600px;\\n}\\n.edlsb-tocWrapper {\\n margin-bottom: 2.5rem;\\n}\\n.edlsb-tocToggle {\\n width: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: space-between;\\n gap: 0.75rem;\\n padding: 1rem 1.25rem;\\n border-radius: 1rem;\\n background-color: rgba(241, 245, 249, 0.8);\\n border: none;\\n cursor: pointer;\\n transition-property: all;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocToggle:hover {\\n background-color: #f1f5f9;\\n}\\n.edlsb-tocToggle:hover .edlsb-tocIconWrap {\\n color: #334155;\\n}\\n.edlsb-tocToggleInner {\\n display: flex;\\n align-items: center;\\n gap: 0.75rem;\\n}\\n.edlsb-tocIconWrap {\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n width: 1.75rem;\\n height: 1.75rem;\\n border-radius: 0.5rem;\\n background-color: rgba(226, 232, 240, 0.8);\\n color: #64748b;\\n transition-property: color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocLabel {\\n font-size: 13px;\\n font-weight: 600;\\n color: #475569;\\n}\\n.edlsb-tocCount {\\n color: #94a3b8;\\n font-weight: 400;\\n margin-left: 0.25rem;\\n}\\n.edlsb-chevronIcon {\\n width: 1rem;\\n height: 1rem;\\n color: #94a3b8;\\n transition-property: transform;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 200ms;\\n}\\n.edlsb-chevronIconOpen {\\n transform: rotate(180deg);\\n}\\n.edlsb-tocPanelInner {\\n margin-top: 0.375rem;\\n border-radius: 1rem;\\n background-color: rgba(241, 245, 249, 0.8);\\n padding: 0.5rem;\\n}\\n.edlsb-tocGrid {\\n display: grid;\\n grid-template-columns: 1fr;\\n gap: 0.125rem;\\n}\\n@media (min-width: 640px) {\\n .edlsb-tocGrid {\\n grid-template-columns: repeat(2, minmax(0, 1fr));\\n }\\n}\\n.edlsb-tocItem {\\n display: flex;\\n align-items: center;\\n gap: 0.625rem;\\n text-align: left;\\n border-radius: 0.75rem;\\n padding: 0.625rem 0.75rem;\\n font-size: 13px;\\n border: none;\\n cursor: pointer;\\n background-color: transparent;\\n color: #475569;\\n transition-property: all;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-tocItem:hover {\\n background-color: #ffffff;\\n color: #0f172a;\\n box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\\n}\\n.edlsb-tocItemActive {\\n background-color: #0f172a;\\n color: #ffffff;\\n box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);\\n}\\n.edlsb-tocItemActive:hover {\\n background-color: #0f172a;\\n color: #ffffff;\\n}\\n.edlsb-tocItemLevel3 {\\n padding-left: 2rem;\\n}\\n.edlsb-tocBadge {\\n flex-shrink: 0;\\n width: 1.25rem;\\n height: 1.25rem;\\n border-radius: 0.375rem;\\n font-size: 10px;\\n font-weight: 700;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n background-color: rgba(226, 232, 240, 0.8);\\n color: #94a3b8;\\n}\\n.edlsb-tocBadgeActive {\\n background-color: rgba(255, 255, 255, 0.15);\\n color: rgba(255, 255, 255, 0.8);\\n}\\n.edlsb-tocItemText {\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n}\\n.edlsb-shareSection {\\n margin-top: 3.5rem;\\n border-top: 1px solid #e2e8f0;\\n padding-top: 1.5rem;\\n}\\n.edlsb-shareInner {\\n display: flex;\\n flex-direction: column;\\n gap: 1rem;\\n}\\n@media (min-width: 640px) {\\n .edlsb-shareInner {\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-between;\\n }\\n}\\n.edlsb-shareTitle {\\n font-size: 1rem;\\n line-height: 1.5rem;\\n font-weight: 600;\\n color: #0f172a;\\n}\\n.edlsb-shareSubtitle {\\n font-size: 0.875rem;\\n line-height: 1.25rem;\\n color: #64748b;\\n}\\n.edlsb-shareButtons {\\n display: flex;\\n align-items: center;\\n gap: 0.625rem;\\n}\\n.edlsb-shareBtn {\\n height: 2.5rem;\\n padding-left: 1rem;\\n padding-right: 1rem;\\n border-radius: 9999px;\\n border: 1px solid #e2e8f0;\\n background-color: #ffffff;\\n color: #334155;\\n font-size: 0.875rem;\\n font-weight: 600;\\n cursor: pointer;\\n transition-property:\\n color,\\n background-color,\\n border-color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-shareBtn:hover {\\n border-color: #cbd5e1;\\n color: #0f172a;\\n}\\n.edlsb-shareBtnInner {\\n display: inline-flex;\\n align-items: center;\\n gap: 0.5rem;\\n}\\n.edlsb-copyBtn {\\n height: 2.5rem;\\n width: 2.5rem;\\n border-radius: 9999px;\\n border: 1px solid #e2e8f0;\\n background-color: #ffffff;\\n color: #475569;\\n cursor: pointer;\\n display: inline-flex;\\n align-items: center;\\n justify-content: center;\\n transition-property:\\n color,\\n background-color,\\n border-color;\\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\\n transition-duration: 150ms;\\n}\\n.edlsb-copyBtn:hover {\\n border-color: #cbd5e1;\\n color: #0f172a;\\n}\\n.edlsb-bodyContainer .blog-content {\\n color: #1e293b;\\n line-height: 1.8;\\n font-size: 1.05rem;\\n overflow-wrap: anywhere;\\n}\\n.edlsb-bodyContainer .blog-content > * + * {\\n margin-top: 1rem;\\n}\\n.edlsb-bodyContainer .blog-content pre {\\n margin: 1.25rem 0;\\n padding: 0.9rem 1rem;\\n border-radius: 0.75rem;\\n background: #0b1220;\\n color: #e2e8f0;\\n overflow-x: auto;\\n border: 1px solid #1f2937;\\n -webkit-overflow-scrolling: touch;\\n}\\n.edlsb-bodyContainer .blog-content pre code {\\n background: transparent;\\n padding: 0;\\n border-radius: 0;\\n color: inherit;\\n font-size: 0.875rem;\\n line-height: 1.6;\\n}\\n.edlsb-bodyContainer .blog-content code {\\n font-family:\\n ui-monospace,\\n SFMono-Regular,\\n Menlo,\\n Monaco,\\n Consolas,\\n \\\"Liberation Mono\\\",\\n \\\"Courier New\\\",\\n monospace;\\n background: #e2e8f0;\\n color: #0f172a;\\n padding: 0.15rem 0.4rem;\\n border-radius: 0.35rem;\\n font-size: 0.875em;\\n}\\n.edlsb-bodyContainer .blog-content img {\\n display: block;\\n max-width: 100%;\\n width: auto;\\n height: auto;\\n margin: 1.25rem auto;\\n border-radius: 0.75rem;\\n}\\n.edlsb-bodyContainer .blog-content a {\\n color: #2563eb;\\n text-decoration: underline;\\n text-underline-offset: 2px;\\n}\\n\")","/**\n * SSR-safe Tiptap JSON โ HTML renderer.\n *\n * Pure recursive serializer โ zero DOM APIs required.\n * Works in Next.js server components and any Node.js environment.\n *\n * Styling matches the Blog View Styling Guide exactly:\n * - prose prose-lg dark:prose-invert wrapper classes\n * - per-node Tailwind classes identical to the Tiptap extension HTMLAttributes\n * - dark-mode variants included throughout\n */\n\n// โโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n// โโโ Mark renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction applyMark(html: string, mark: any): string {\n switch (mark.type) {\n case 'bold':\n return `<strong>${html}</strong>`;\n case 'italic':\n return `<em>${html}</em>`;\n case 'strike':\n return `<s>${html}</s>`;\n case 'underline':\n return `<u>${html}</u>`;\n case 'code':\n return `<code>${html}</code>`;\n case 'link': {\n const href = escapeHtml(mark.attrs?.href ?? '');\n const target = escapeHtml(mark.attrs?.target ?? '_blank');\n return `<a href=\"${href}\" target=\"${target}\" rel=\"noopener noreferrer\">${html}</a>`;\n }\n case 'textStyle': {\n // Handles color / font-size set via the TextStyle extension\n const color = mark.attrs?.color;\n const fontSize = mark.attrs?.fontSize;\n const style = [\n color ? `color:${color}` : '',\n fontSize ? `font-size:${fontSize}` : '',\n ].filter(Boolean).join(';');\n return style ? `<span style=\"${style}\">${html}</span>` : html;\n }\n case 'highlight': {\n const color = mark.attrs?.color;\n const style = color ? ` style=\"background-color:${color}\"` : '';\n return `<mark${style}>${html}</mark>`;\n }\n default:\n return html;\n }\n}\n\n// โโโ Node renderer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nfunction renderChildren(node: any): string {\n if (!Array.isArray(node?.content)) return '';\n return node.content.map(renderNode).join('');\n}\n\nfunction renderNode(node: any): string {\n if (!node) return '';\n\n switch (node.type) {\n\n // โโ Document root โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'doc':\n return renderChildren(node);\n\n // โโ Block nodes โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'paragraph': {\n const inner = renderChildren(node);\n if (!inner) return `<p><br /></p>`;\n return `<p>${inner}</p>`;\n }\n\n case 'heading': {\n const level = node.attrs?.level ?? 1;\n return `<h${level}>${renderChildren(node)}</h${level}>`;\n }\n\n case 'blockquote':\n return `<blockquote>${renderChildren(node)}</blockquote>`;\n\n case 'bulletList':\n return `<ul>${renderChildren(node)}</ul>`;\n\n case 'orderedList':\n return `<ol>${renderChildren(node)}</ol>`;\n\n case 'listItem':\n return `<li>${renderChildren(node)}</li>`;\n\n case 'taskList':\n return `<ul class=\"task-list\">${renderChildren(node)}</ul>`;\n\n case 'taskItem': {\n const checked = node.attrs?.checked ? ' checked' : '';\n return `<li class=\"task-item\"><input type=\"checkbox\"${checked} disabled /><div>${renderChildren(node)}</div></li>`;\n }\n\n case 'codeBlock': {\n const lang = node.attrs?.language ? ` data-language=\"${escapeHtml(node.attrs.language)}\"` : '';\n return `<pre${lang}><code>${renderChildren(node)}</code></pre>`;\n }\n\n case 'horizontalRule':\n return `<hr />`;\n\n case 'hardBreak':\n return `<br />`;\n\n // โโ Media / embeds โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'image': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const alt = escapeHtml(node.attrs?.alt ?? '');\n const title = node.attrs?.title ? ` title=\"${escapeHtml(node.attrs.title)}\"` : '';\n return `<img src=\"${src}\" alt=\"${alt}\"${title} loading=\"lazy\" />`;\n }\n\n case 'resizableImage': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const alt = escapeHtml(node.attrs?.alt ?? '');\n const width = node.attrs?.width ? ` width=\"${escapeHtml(String(node.attrs.width))}\"` : '';\n return `<img src=\"${src}\" alt=\"${alt}\"${width} loading=\"lazy\" />`;\n }\n\n case 'youtube': {\n const src = escapeHtml(node.attrs?.src ?? '');\n const width = node.attrs?.width ?? 640;\n const height = node.attrs?.height ?? 480;\n if (!src) return '';\n return `<div class=\"rounded-lg my-4 overflow-hidden\" style=\"position:relative;padding-bottom:56.25%;height:0;\"><iframe src=\"${src}\" width=\"${width}\" height=\"${height}\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe></div>`;\n }\n\n case 'twitter':\n case 'tweet': {\n // Render a simple link card since Twitter embeds require client-side JS\n const url = escapeHtml(node.attrs?.src ?? node.attrs?.url ?? '');\n if (!url) return '';\n return `<div class=\"max-w-xl\"><a href=\"${url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-themecolor font-semibold underline underline-offset-[3px] hover:text-themecolorhover transition-colors\">${url}</a></div>`;\n }\n\n // โโ Inline nodes โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n case 'text': {\n let out = escapeHtml(node.text ?? '');\n if (Array.isArray(node.marks)) {\n for (const mark of node.marks) {\n out = applyMark(out, mark);\n }\n }\n return out;\n }\n\n default:\n // Gracefully handle unknown nodes by rendering their children\n return renderChildren(node);\n }\n}\n\n// โโโ Public API โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\nexport function renderTiptapToHTML(\n jsonContent: Record<string, any> | string,\n): string {\n try {\n if (!jsonContent) return '';\n\n let contentObj: any;\n if (typeof jsonContent === 'string') {\n try {\n contentObj = JSON.parse(jsonContent);\n } catch {\n // Not valid JSON โ treat as a raw HTML string and return as-is\n return jsonContent;\n }\n } else {\n contentObj = jsonContent;\n }\n\n if (!contentObj || typeof contentObj !== 'object') {\n return String(jsonContent);\n }\n\n return renderNode(contentObj);\n } catch (error) {\n console.error('Failed to parse Tiptap JSON to HTML:', error);\n return '<p>Error loading content.</p>';\n }\n}\n","export { BlogRenderer } from \"./BlogRenderer\";\nexport { BlogRenderer as default } from \"./BlogRenderer\";\nexport function renderText() {\n return \"Hello World\";\n}\nexport type { JSONContent } from \"@tiptap/core\";\n"],"mappings":"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","names":[]}
|