@opinly/next 0.1.2 → 0.1.4

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.
Files changed (34) hide show
  1. package/dist/index.cjs +24 -29
  2. package/dist/index.d.cts +17 -0
  3. package/dist/index.js +21 -26
  4. package/package.json +8 -2
  5. package/.turbo/turbo-lint.log +0 -14
  6. package/CHANGELOG.md +0 -21
  7. package/src/components/author-page/_helpers/json-ld.ts +0 -0
  8. package/src/components/author-page/index.tsx +0 -62
  9. package/src/components/authors-page/index.tsx +0 -90
  10. package/src/components/blog-post/_components/content.tsx +0 -141
  11. package/src/components/blog-post/_components/faq.tsx +0 -18
  12. package/src/components/blog-post/_components/share.tsx +0 -48
  13. package/src/components/blog-post/_components/table-of-contents.tsx +0 -63
  14. package/src/components/blog-post/_helpers/calc-reading-time.ts +0 -0
  15. package/src/components/blog-post/_helpers/extract-headings.ts +0 -25
  16. package/src/components/blog-post/_helpers/json-ld.ts +0 -176
  17. package/src/components/blog-post/_helpers/reading-time.ts +0 -28
  18. package/src/components/blog-post/_types/blog-post.ts +0 -6
  19. package/src/components/blog-post/_types/class-names.ts +0 -10
  20. package/src/components/blog-post/index.tsx +0 -126
  21. package/src/components/bread-crumbs.tsx +0 -48
  22. package/src/components/folder-page/index.tsx +0 -50
  23. package/src/components/home-page.tsx +0 -65
  24. package/src/components/not-found.tsx +0 -36
  25. package/src/components/opinly-blog.tsx +0 -37
  26. package/src/components/post-preview.tsx +0 -73
  27. package/src/index.ts +0 -2
  28. package/src/sdk-config.ts +0 -37
  29. package/src/utils/dates.ts +0 -6
  30. package/src/utils/generate-metadata.ts +0 -54
  31. package/src/utils/images.ts +0 -7
  32. package/src/utils/merge.ts +0 -6
  33. package/sst-env.d.ts +0 -9
  34. package/tsconfig.json +0 -19
package/dist/index.cjs CHANGED
@@ -28,12 +28,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
33
  HomePage: () => HomePage,
34
34
  OpinlyBlog: () => OpinlyBlog
35
35
  });
36
- module.exports = __toCommonJS(src_exports);
36
+ module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/components/blog-post/index.tsx
39
39
  var import_link5 = __toESM(require("next/link"), 1);
@@ -80,8 +80,7 @@ function replaceImageNameSpace(fileKey) {
80
80
 
81
81
  // src/components/blog-post/_helpers/json-ld.ts
82
82
  function generateArticleBodyHtml(content) {
83
- if (!content?.content)
84
- return "";
83
+ if (!content?.content) return "";
85
84
  let htmlString = "";
86
85
  content.content.forEach((node) => {
87
86
  switch (node.type) {
@@ -101,6 +100,14 @@ function generateArticleBodyHtml(content) {
101
100
  case "listItem":
102
101
  htmlString += `<li>${generateChildrenHtml(node.content)}</li>`;
103
102
  break;
103
+ // Add other node types if they contain text content relevant for articleBody
104
+ // For images, you might just skip them or include an <img> tag if semantic
105
+ // For example:
106
+ // case "image":
107
+ // if (node.attrs?.src) {
108
+ // htmlString += `<img src="${replaceImageHostname(node.attrs.src, opinlyConfig.imagesPrefix)}" alt="${node.attrs?.alt || ''}" />`;
109
+ // }
110
+ // break;
104
111
  default:
105
112
  break;
106
113
  }
@@ -108,18 +115,15 @@ function generateArticleBodyHtml(content) {
108
115
  return htmlString;
109
116
  }
110
117
  function generateChildrenHtml(nodes) {
111
- if (!nodes)
112
- return "";
118
+ if (!nodes) return "";
113
119
  let childrenHtml = "";
114
120
  nodes.forEach((child) => {
115
121
  if (child.type === "text") {
116
122
  let text = child.text || "";
117
123
  if (child.marks) {
118
124
  child.marks.forEach((mark) => {
119
- if (mark.type === "bold")
120
- text = `<strong>${text}</strong>`;
121
- if (mark.type === "italic")
122
- text = `<em>${text}</em>`;
125
+ if (mark.type === "bold") text = `<strong>${text}</strong>`;
126
+ if (mark.type === "italic") text = `<em>${text}</em>`;
123
127
  if (mark.type === "link" && mark.attrs?.href) {
124
128
  text = `<a href="${mark.attrs.href}">${text}</a>`;
125
129
  }
@@ -213,8 +217,7 @@ var genBreadcrumbJSONLD = (post) => {
213
217
  // src/components/blog-post/_components/faq.tsx
214
218
  var import_jsx_runtime = require("react/jsx-runtime");
215
219
  var FAQS = ({ faqs }) => {
216
- if (!faqs)
217
- return null;
220
+ if (!faqs) return null;
218
221
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
219
222
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-lg font-semibold mb-2", children: "FAQS" }),
220
223
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: faqs.map((faq) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
@@ -226,8 +229,7 @@ var FAQS = ({ faqs }) => {
226
229
 
227
230
  // src/components/blog-post/_helpers/reading-time.ts
228
231
  var countWords = (content) => {
229
- if (!content?.content)
230
- return 0;
232
+ if (!content?.content) return 0;
231
233
  let wordCount = 0;
232
234
  const countWords2 = (nodes) => {
233
235
  for (const node of nodes) {
@@ -235,8 +237,7 @@ var countWords = (content) => {
235
237
  const words = node.text.trim().split(/\s+/).filter(Boolean);
236
238
  wordCount += words.length;
237
239
  }
238
- if (node.content)
239
- countWords2(node.content);
240
+ if (node.content) countWords2(node.content);
240
241
  }
241
242
  };
242
243
  countWords2(content.content);
@@ -252,8 +253,7 @@ function calculateReadingTime(content) {
252
253
  var import_link = __toESM(require("next/link"), 1);
253
254
  var import_jsx_runtime2 = require("react/jsx-runtime");
254
255
  function renderTiptapContent(content, classNames) {
255
- if (!content?.content)
256
- return [];
256
+ if (!content?.content) return [];
257
257
  return content.content.map((node, index) => {
258
258
  switch (node.type) {
259
259
  case "paragraph":
@@ -316,17 +316,14 @@ var Content = ({
316
316
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "prose min-w-full", children: renderTiptapContent(content, classNames) });
317
317
  };
318
318
  function renderChildren(nodes, classNames) {
319
- if (!nodes)
320
- return [];
319
+ if (!nodes) return [];
321
320
  return nodes.map((child, idx) => {
322
321
  if (child.type === "text") {
323
322
  let text = child.text;
324
323
  if (child.marks) {
325
324
  child.marks.forEach((mark) => {
326
- if (mark.type === "bold")
327
- text = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: text }, idx);
328
- if (mark.type === "italic")
329
- text = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("em", { children: text }, idx);
325
+ if (mark.type === "bold") text = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: text }, idx);
326
+ if (mark.type === "italic") text = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("em", { children: text }, idx);
330
327
  if (mark.type === "link")
331
328
  text = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
332
329
  import_link.default,
@@ -351,8 +348,7 @@ var import_image = __toESM(require("next/image"), 1);
351
348
  // src/components/blog-post/_helpers/extract-headings.ts
352
349
  function extractHeadings(content) {
353
350
  const headings = [];
354
- if (!content?.content)
355
- return headings;
351
+ if (!content?.content) return headings;
356
352
  const walk = (nodes) => {
357
353
  for (const node of nodes) {
358
354
  if (node.type === "heading" && node.content?.[0]?.text) {
@@ -360,8 +356,7 @@ function extractHeadings(content) {
360
356
  const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
361
357
  headings.push({ text, slug });
362
358
  }
363
- if (node.content)
364
- walk(node.content);
359
+ if (node.content) walk(node.content);
365
360
  }
366
361
  };
367
362
  walk(content.content);
@@ -0,0 +1,17 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { Opinly } from '@opinly/backend';
3
+
4
+ declare const OpinlyBlog: ({ blog, page, }: {
5
+ blog: Awaited<ReturnType<Opinly["content"]["blog"]>>;
6
+ page: number | null | undefined;
7
+ }) => react_jsx_runtime.JSX.Element;
8
+
9
+ type HomePage = Extract<Awaited<ReturnType<Opinly["content"]["blog"]>>, {
10
+ type: "home";
11
+ }>["data"];
12
+ declare const HomePage: ({ homePage, page, }: {
13
+ homePage: HomePage;
14
+ page: number | null | undefined;
15
+ }) => react_jsx_runtime.JSX.Element;
16
+
17
+ export { HomePage, OpinlyBlog };
package/dist/index.js CHANGED
@@ -43,8 +43,7 @@ function replaceImageNameSpace(fileKey) {
43
43
 
44
44
  // src/components/blog-post/_helpers/json-ld.ts
45
45
  function generateArticleBodyHtml(content) {
46
- if (!content?.content)
47
- return "";
46
+ if (!content?.content) return "";
48
47
  let htmlString = "";
49
48
  content.content.forEach((node) => {
50
49
  switch (node.type) {
@@ -64,6 +63,14 @@ function generateArticleBodyHtml(content) {
64
63
  case "listItem":
65
64
  htmlString += `<li>${generateChildrenHtml(node.content)}</li>`;
66
65
  break;
66
+ // Add other node types if they contain text content relevant for articleBody
67
+ // For images, you might just skip them or include an <img> tag if semantic
68
+ // For example:
69
+ // case "image":
70
+ // if (node.attrs?.src) {
71
+ // htmlString += `<img src="${replaceImageHostname(node.attrs.src, opinlyConfig.imagesPrefix)}" alt="${node.attrs?.alt || ''}" />`;
72
+ // }
73
+ // break;
67
74
  default:
68
75
  break;
69
76
  }
@@ -71,18 +78,15 @@ function generateArticleBodyHtml(content) {
71
78
  return htmlString;
72
79
  }
73
80
  function generateChildrenHtml(nodes) {
74
- if (!nodes)
75
- return "";
81
+ if (!nodes) return "";
76
82
  let childrenHtml = "";
77
83
  nodes.forEach((child) => {
78
84
  if (child.type === "text") {
79
85
  let text = child.text || "";
80
86
  if (child.marks) {
81
87
  child.marks.forEach((mark) => {
82
- if (mark.type === "bold")
83
- text = `<strong>${text}</strong>`;
84
- if (mark.type === "italic")
85
- text = `<em>${text}</em>`;
88
+ if (mark.type === "bold") text = `<strong>${text}</strong>`;
89
+ if (mark.type === "italic") text = `<em>${text}</em>`;
86
90
  if (mark.type === "link" && mark.attrs?.href) {
87
91
  text = `<a href="${mark.attrs.href}">${text}</a>`;
88
92
  }
@@ -176,8 +180,7 @@ var genBreadcrumbJSONLD = (post) => {
176
180
  // src/components/blog-post/_components/faq.tsx
177
181
  import { jsx, jsxs } from "react/jsx-runtime";
178
182
  var FAQS = ({ faqs }) => {
179
- if (!faqs)
180
- return null;
183
+ if (!faqs) return null;
181
184
  return /* @__PURE__ */ jsxs("div", { children: [
182
185
  /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold mb-2", children: "FAQS" }),
183
186
  /* @__PURE__ */ jsx("ul", { children: faqs.map((faq) => /* @__PURE__ */ jsxs("li", { children: [
@@ -189,8 +192,7 @@ var FAQS = ({ faqs }) => {
189
192
 
190
193
  // src/components/blog-post/_helpers/reading-time.ts
191
194
  var countWords = (content) => {
192
- if (!content?.content)
193
- return 0;
195
+ if (!content?.content) return 0;
194
196
  let wordCount = 0;
195
197
  const countWords2 = (nodes) => {
196
198
  for (const node of nodes) {
@@ -198,8 +200,7 @@ var countWords = (content) => {
198
200
  const words = node.text.trim().split(/\s+/).filter(Boolean);
199
201
  wordCount += words.length;
200
202
  }
201
- if (node.content)
202
- countWords2(node.content);
203
+ if (node.content) countWords2(node.content);
203
204
  }
204
205
  };
205
206
  countWords2(content.content);
@@ -215,8 +216,7 @@ function calculateReadingTime(content) {
215
216
  import Link from "next/link";
216
217
  import { jsx as jsx2 } from "react/jsx-runtime";
217
218
  function renderTiptapContent(content, classNames) {
218
- if (!content?.content)
219
- return [];
219
+ if (!content?.content) return [];
220
220
  return content.content.map((node, index) => {
221
221
  switch (node.type) {
222
222
  case "paragraph":
@@ -279,17 +279,14 @@ var Content = ({
279
279
  return /* @__PURE__ */ jsx2("div", { className: "prose min-w-full", children: renderTiptapContent(content, classNames) });
280
280
  };
281
281
  function renderChildren(nodes, classNames) {
282
- if (!nodes)
283
- return [];
282
+ if (!nodes) return [];
284
283
  return nodes.map((child, idx) => {
285
284
  if (child.type === "text") {
286
285
  let text = child.text;
287
286
  if (child.marks) {
288
287
  child.marks.forEach((mark) => {
289
- if (mark.type === "bold")
290
- text = /* @__PURE__ */ jsx2("strong", { children: text }, idx);
291
- if (mark.type === "italic")
292
- text = /* @__PURE__ */ jsx2("em", { children: text }, idx);
288
+ if (mark.type === "bold") text = /* @__PURE__ */ jsx2("strong", { children: text }, idx);
289
+ if (mark.type === "italic") text = /* @__PURE__ */ jsx2("em", { children: text }, idx);
293
290
  if (mark.type === "link")
294
291
  text = /* @__PURE__ */ jsx2(
295
292
  Link,
@@ -314,8 +311,7 @@ import Image from "next/image";
314
311
  // src/components/blog-post/_helpers/extract-headings.ts
315
312
  function extractHeadings(content) {
316
313
  const headings = [];
317
- if (!content?.content)
318
- return headings;
314
+ if (!content?.content) return headings;
319
315
  const walk = (nodes) => {
320
316
  for (const node of nodes) {
321
317
  if (node.type === "heading" && node.content?.[0]?.text) {
@@ -323,8 +319,7 @@ function extractHeadings(content) {
323
319
  const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
324
320
  headings.push({ text, slug });
325
321
  }
326
- if (node.content)
327
- walk(node.content);
322
+ if (node.content) walk(node.content);
328
323
  }
329
324
  };
330
325
  walk(content.content);
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@opinly/next",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "private": false,
6
+ "main": "dist/index.cjs.js",
7
+ "module": "dist/index.esm.js",
8
+ "types": "dist/index.d.ts",
6
9
  "devDependencies": {
7
10
  "@tsconfig/next": "^2.0.3",
8
11
  "@types/node": "^20.0.0",
@@ -25,11 +28,14 @@
25
28
  "react-syntax-highlighter": "^15.6.1",
26
29
  "schema-dts": "^1.1.5",
27
30
  "tailwind-merge": "^1.14.0",
28
- "@opinly/backend": "0.1.2"
31
+ "@opinly/backend": "0.1.4"
29
32
  },
30
33
  "engines": {
31
34
  "node": ">=16"
32
35
  },
36
+ "files": [
37
+ "dist/**"
38
+ ],
33
39
  "publishConfig": {
34
40
  "access": "public"
35
41
  },
@@ -1,14 +0,0 @@
1
-
2
- 
3
- > @opinly/next@ lint /Users/kieronmckenna/repos/opinly/packages/sdks/next
4
- > eslint src/
5
-
6
-
7
- Oops! Something went wrong! :(
8
-
9
- ESLint: 8.29.0
10
-
11
- No files matching the pattern "src/" were found.
12
- Please check for typing mistakes in the pattern.
13
-
14
-  ELIFECYCLE  Command failed with exit code 2.
package/CHANGELOG.md DELETED
@@ -1,21 +0,0 @@
1
- # @opinly/next
2
-
3
- ## 0.1.2
4
-
5
- ### Patch Changes
6
-
7
- - more tsconfig changes
8
- - Updated dependencies
9
- - @opinly/backend@0.1.2
10
-
11
- ## 0.1.1
12
-
13
- ### Patch Changes
14
-
15
- - updating tsconfigs
16
-
17
- ## 0.1.0
18
-
19
- ### Minor Changes
20
-
21
- - broke out sdks from internal use
File without changes
@@ -1,62 +0,0 @@
1
- import { Opinly } from "@opinly/backend";
2
- import Image from "next/image";
3
- import { opinlyConfig } from "../../sdk-config";
4
- import { replaceImageNameSpace } from "../../utils/images";
5
- import { PostPreview } from "../post-preview";
6
- import { BreadCrumbs } from "../bread-crumbs";
7
-
8
- export const AuthorPage = ({
9
- author,
10
- }: {
11
- author: Extract<
12
- Awaited<ReturnType<Opinly["content"]["blog"]>>,
13
- { type: "author" }
14
- >["data"];
15
- }) => {
16
- return (
17
- <div className="max-w-5xl mx-auto px-4 py-10">
18
- <BreadCrumbs
19
- breadcrumbs={[
20
- {
21
- name: "Authors",
22
- url: `${opinlyConfig.blogPrefix}/authors`,
23
- },
24
- ]}
25
- />
26
- {/* Author Header */}
27
- <div className="flex items-center gap-6 mb-10">
28
- {author.image.fileKey && (
29
- <Image
30
- src={`${opinlyConfig.imagesPrefix}/${replaceImageNameSpace(author.image.fileKey)}`}
31
- alt={author.image.alt || author.name}
32
- width={150}
33
- height={150}
34
- className="rounded-full border"
35
- />
36
- )}
37
- <div>
38
- <h1 className="text-3xl font-bold mb-1">{author.name}</h1>
39
- {author.bio && (
40
- <p className="text-muted-foreground text-base max-w-2xl">
41
- {author.bio}
42
- </p>
43
- )}
44
- </div>
45
- </div>
46
-
47
- {/* Posts Grid */}
48
- <h2 className="text-xl font-semibold mb-6">Posts by {author.name}</h2>
49
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
50
- {author.posts && author.posts.length > 0 ? (
51
- author.posts.map(
52
- (post) => post && <PostPreview key={post.slug} post={post} />
53
- )
54
- ) : (
55
- <div className="text-muted-foreground col-span-full">
56
- No posts yet.
57
- </div>
58
- )}
59
- </div>
60
- </div>
61
- );
62
- };
@@ -1,90 +0,0 @@
1
- import type { Opinly } from "@opinly/backend";
2
- import Image from "next/image";
3
- import { opinlyConfig } from "../../sdk-config";
4
- import { replaceImageNameSpace } from "../../utils/images";
5
- import { PostPreview } from "../post-preview";
6
- import { BreadCrumbs } from "../bread-crumbs";
7
- import Link from "next/link";
8
-
9
- export const AuthorsPage = ({
10
- authors,
11
- }: {
12
- authors: Extract<
13
- Awaited<ReturnType<Opinly["content"]["blog"]>>,
14
- { type: "authors" }
15
- >["data"];
16
- }) => {
17
- return (
18
- <div className="max-w-5xl mx-auto px-4 py-10">
19
- <BreadCrumbs
20
- breadcrumbs={[
21
- {
22
- name: "Authors",
23
- },
24
- ]}
25
- />
26
- <h1 className="text-3xl font-bold mb-10">Authors</h1>
27
- <div className="space-y-16">
28
- {authors.map((author, idx) => {
29
- const authorSlug = `${opinlyConfig.blogPrefix}/authors/${author.slug}`;
30
-
31
- return (
32
- <div
33
- key={author.name + idx}
34
- className="rounded-lg p-6 flex flex-col gap-6"
35
- >
36
- <div className="flex items-center gap-6 mb-4">
37
- {author.image?.fileKey && (
38
- <Link href={authorSlug}>
39
- <Image
40
- src={`${opinlyConfig.imagesPrefix}/${replaceImageNameSpace(author.image.fileKey)}`}
41
- alt={author.image.alt || author.name}
42
- width={100}
43
- height={100}
44
- className="rounded-full border"
45
- />
46
- </Link>
47
- )}
48
- <div>
49
- <Link href={authorSlug}>
50
- <h2 className="text-2xl font-semibold mb-1">
51
- {author.name}
52
- </h2>
53
- </Link>
54
-
55
- {author.bio && (
56
- <p className="text-muted-foreground text-base max-w-2xl">
57
- {author.bio}
58
- </p>
59
- )}
60
- </div>
61
- </div>
62
- <div>
63
- <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-4">
64
- {author.posts && author.posts.length > 0 ? (
65
- author.posts.map(
66
- (post) =>
67
- post && <PostPreview key={post.slug} post={post} />
68
- )
69
- ) : (
70
- <div className="text-muted-foreground col-span-full">
71
- No posts yet.
72
- </div>
73
- )}
74
- </div>
75
- <div className="flex justify-end">
76
- <Link
77
- href={`${opinlyConfig.blogPrefix}/authors/${author.slug}`}
78
- className="text-bold"
79
- >
80
- View more posts from {author.name}
81
- </Link>
82
- </div>
83
- </div>
84
- </div>
85
- );
86
- })}
87
- </div>
88
- </div>
89
- );
90
- };
@@ -1,141 +0,0 @@
1
- import type { JSONContent } from "@tiptap/core";
2
- import { BlogComponentClassNames } from "../_types/class-names";
3
- import { cn } from "../../../utils/merge";
4
- import Link from "next/link";
5
- import { JSX } from "react";
6
-
7
- function renderTiptapContent(
8
- content: JSONContent,
9
- classNames: BlogComponentClassNames
10
- ): React.ReactNode[] {
11
- if (!content?.content) return [];
12
-
13
- return content.content
14
- .map((node, index) => {
15
- switch (node.type) {
16
- case "paragraph":
17
- return (
18
- <p key={index} className={classNames.paragraph || "mb-4"}>
19
- {renderChildren(node.content, classNames)}
20
- </p>
21
- );
22
- case "heading": {
23
- const text = node.content?.[0]?.text ?? "";
24
- const slug = text
25
- .toLowerCase()
26
- .replace(/[^a-z0-9]+/g, "-")
27
- .replace(/^-+|-+$/g, "");
28
- const Tag = `h${
29
- node.attrs?.level || 1
30
- }` as keyof JSX.IntrinsicElements;
31
- return (
32
- <Tag
33
- key={index}
34
- id={slug}
35
- className={
36
- classNames.heading || "mt-8 mb-4 font-bold text-gray-800"
37
- }
38
- >
39
- {renderChildren(node.content, classNames)}
40
- </Tag>
41
- );
42
- }
43
- case "image":
44
- return (
45
- <div
46
- key={index}
47
- className={cn(
48
- "relative w-full max-w-[800px] my-6",
49
- classNames.image
50
- )}
51
- style={{ aspectRatio: "2 / 1" }}
52
- >
53
- {/* <Image
54
- src={replaceImageHostname(
55
- node.attrs?.src || "",
56
- opinlyConfig.imagesPrefix
57
- )}
58
- alt={node.attrs?.alt || ""}
59
- fill
60
- className="object-contain rounded-lg"
61
- sizes="(max-width: 800px) 100vw, 800px"
62
- /> */}
63
- </div>
64
- );
65
- case "bulletList":
66
- return (
67
- <ul
68
- key={index}
69
- className={classNames.bulletList || "list-disc pl-6 my-4"}
70
- >
71
- {renderChildren(node.content, classNames)}
72
- </ul>
73
- );
74
- case "orderedList":
75
- return (
76
- <ol
77
- key={index}
78
- className={classNames.orderedList || "list-decimal pl-6 my-4"}
79
- >
80
- {renderChildren(node.content, classNames)}
81
- </ol>
82
- );
83
- case "listItem":
84
- return (
85
- <li key={index} className={classNames.listItem || "my-1"}>
86
- {renderChildren(node.content, classNames)}
87
- </li>
88
- );
89
- default:
90
- return null;
91
- }
92
- })
93
- .filter(Boolean);
94
- }
95
-
96
- export const Content = ({
97
- content,
98
- classNames,
99
- }: {
100
- content: JSONContent;
101
- classNames: BlogComponentClassNames;
102
- }) => {
103
- return (
104
- <div className="prose min-w-full">
105
- {renderTiptapContent(content, classNames)}
106
- </div>
107
- );
108
- };
109
-
110
- function renderChildren(
111
- nodes: JSONContent["content"] | undefined,
112
- classNames: BlogComponentClassNames
113
- ): JSX.Element[] {
114
- if (!nodes) return [];
115
-
116
- return nodes
117
- .map((child, idx) => {
118
- if (child.type === "text") {
119
- let text: React.ReactNode = child.text;
120
- if (child.marks) {
121
- child.marks.forEach((mark) => {
122
- if (mark.type === "bold") text = <strong key={idx}>{text}</strong>;
123
- if (mark.type === "italic") text = <em key={idx}>{text}</em>;
124
- if (mark.type === "link")
125
- text = (
126
- <Link
127
- key={idx}
128
- href={mark.attrs?.href}
129
- className={classNames.link || "text-blue-600 underline"}
130
- >
131
- {text}
132
- </Link>
133
- );
134
- });
135
- }
136
- return <span key={idx}>{text}</span>;
137
- }
138
- return null;
139
- })
140
- .filter(Boolean) as JSX.Element[];
141
- }
@@ -1,18 +0,0 @@
1
- import { BlogPostRes } from "../_types/blog-post";
2
-
3
- export const FAQS = ({ faqs }: { faqs: BlogPostRes["faqs"] }) => {
4
- if (!faqs) return null;
5
- return (
6
- <div>
7
- <h2 className="text-lg font-semibold mb-2">FAQS</h2>
8
- <ul>
9
- {faqs.map((faq) => (
10
- <li key={faq.question}>
11
- <h3 className="text-lg font-semibold mb-2">{faq.question}</h3>
12
- <p className="text-sm text-gray-700">{faq.answer}</p>
13
- </li>
14
- ))}
15
- </ul>
16
- </div>
17
- );
18
- };