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 CHANGED
@@ -1,63 +1,61 @@
1
1
  # `embed-dlsurf-blogs`
2
2
 
3
- Render dlsurf blog documents as a polished React component with TOC, sharing UI, and SSR-safe Tiptap JSON -> HTML rendering.
3
+ Render dlsurf blog posts with a single React component.
4
4
 
5
- ## 1. Fetch Document Data First
5
+ ## Quick Start
6
6
 
7
- Before rendering, fetch the blog document from:
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
- Example:
18
+ internally.
12
19
 
13
- ```txt
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
- Response format:
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": 1252,
25
- "title": "Why Django is a Game-Changer in Modern Web Development",
26
- "content_json": "{\"type\":\"doc\",\"content\":[...]}",
27
- "thumbnail_path": "thumbnails/3e3ded2a-0.png",
28
- "keywords": "[\"Django web framework\",\"Python Django framework\"]",
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": "2025-12-23T05:59:12.432707Z",
32
- "link_slug": "why-django-is-a-game-changer-in-modern-web-development",
33
- "updated_at": "2025-12-23T06:17:28.878387Z",
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": "rishav",
36
- "display_name": "Rishav Dahal",
40
+ "username": "arjun",
41
+ "display_name": "Arjun Ghimire",
37
42
  "account_level": "6",
38
- "profile_picture": "/media/profile_picture/a609d8df22.JPG"
43
+ "profile_picture": "/media/profile_picture/efd953bb1b.jpeg"
39
44
  },
40
- "ads_step": 1,
45
+ "ads_step": 0,
41
46
  "banner_ads": false,
42
47
  "video_ads": false
43
48
  }
44
49
  }
45
50
  ```
46
51
 
47
- Pass `response.data` directly to the component.
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
- - `react`
58
- - `@tiptap/core`
54
+ - Node.js `>= 18`
55
+ - React `>= 18`
56
+ - `@tiptap/core` (peer dependency)
59
57
 
60
- ## 3. Install
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
- ## 4. Component Usage Patterns
76
+ ## Usage Patterns
79
77
 
80
- ### Basic Usage (Client Component)
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
- type DocApiResponse = {
89
- status: string;
90
- message: string;
91
- data: BlogPost;
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
- ### Named Import Usage
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 MyBlogView({ post }: { post: BlogPost }) {
153
- return <BlogRenderer post={post} />;
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
- ### Next.js Server Component Pattern
107
+ ### 3. Advanced: override API base URL
158
108
 
159
109
  ```tsx
160
110
  import BlogRenderer from "embed-dlsurf-blogs";
161
111
 
162
- type Params = { user: string; slug: string };
163
-
164
- export default async function Page({ params }: { params: Promise<Params> }) {
165
- const { user, slug } = await params;
166
- const res = await fetch(`https://docapi.dl.surf/api/doc/${user}/${slug}`, {
167
- next: { revalidate: 60 },
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
- ## 5. Types
123
+ ### 4. Optional backward-compatible pattern: pass full post object directly
178
124
 
179
- The component expects this shape for the `post` prop:
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
- ## 6. What The Component Does Internally
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
- - Parses `post.content_json` using an SSR-safe renderer.
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
- ## 7. API Utilities Exported
175
+ ## Exported API
217
176
 
218
- From this package:
177
+ - Default export: `BlogRenderer`
178
+ - Named export: `BlogRenderer`
179
+ - Utility: `renderText`
219
180
 
220
- - `default` -> `BlogRenderer`
221
- - `{ BlogRenderer }`
222
- - `{ renderText }` (utility test export)
181
+ ## Internal Behavior
223
182
 
224
- ## 8. Troubleshooting
183
+ The component:
225
184
 
226
- - If content is empty, check that `data.content_json` is a valid Tiptap JSON string.
227
- - If the API call fails, verify `user` and `linkSlug` values in the URL.
228
- - If you use strict CSP, confirm style injection rules allow runtime-injected styles.
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
- ## 9. License
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 SingleBlogClientProps {
5
- post: BlogPost;
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 }: SingleBlogClientProps): react_jsx_runtime.JSX.Element;
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 SingleBlogClientProps {
5
- post: BlogPost;
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 }: SingleBlogClientProps): react_jsx_runtime.JSX.Element;
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({ post }) {
357
- const rawHtml = post.content_json ? renderTiptapToHTML(post.content_json) : "<p>Could not render content</p>";
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(post.thumbnail_path);
372
- const publishDate = post.created_at || (/* @__PURE__ */ new Date()).toISOString();
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 = post.title;
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: post.title })
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: post.title,
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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\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({ post }) {
332
- const rawHtml = post.content_json ? renderTiptapToHTML(post.content_json) : "<p>Could not render content</p>";
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(post.thumbnail_path);
347
- const publishDate = post.created_at || (/* @__PURE__ */ new Date()).toISOString();
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 = post.title;
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: post.title })
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: post.title,
583
+ alt: postData.title,
487
584
  className: styles.thumbnail
488
585
  }
489
586
  )
@@ -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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "embed-dlsurf-blogs",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",