@san-siva/blogkit-md 0.2.1 → 0.2.2

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.
@@ -0,0 +1,33 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { WithContext, Thing } from 'schema-dts';
3
+ import React from 'react';
4
+
5
+ type BlogPostProperties = {
6
+ filePath: string;
7
+ jsonLd?: WithContext<Thing>;
8
+ };
9
+ declare const BlogPost: ({ filePath, jsonLd }: BlogPostProperties) => Promise<react_jsx_runtime.JSX.Element>;
10
+
11
+ type Frontmatter = {
12
+ title?: string;
13
+ description?: string;
14
+ };
15
+
16
+ type RenderedMarkdown = {
17
+ sections: React.ReactNode[];
18
+ };
19
+ declare const MarkdownSections: ({ rendered, }: {
20
+ rendered: RenderedMarkdown;
21
+ }) => React.ReactNode;
22
+
23
+ type MarkdownFileResult = {
24
+ success: true;
25
+ rendered: RenderedMarkdown;
26
+ frontmatter: Frontmatter;
27
+ } | {
28
+ success: false;
29
+ error: string;
30
+ };
31
+ declare const readMarkdownFile: (filePath: string | undefined) => Promise<MarkdownFileResult>;
32
+
33
+ export { BlogPost, MarkdownSections, readMarkdownFile };
package/dist/index.mjs ADDED
@@ -0,0 +1,413 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+
21
+ // src/components/BlogPost.tsx
22
+ import { Blog, BlogHeader, Callout as Callout2 } from "@san-siva/blogkit";
23
+ import { readFile } from "fs/promises";
24
+ import path from "path";
25
+
26
+ // src/utils/parseMarkdown.ts
27
+ import remarkFrontmatter from "remark-frontmatter";
28
+ import remarkGfm from "remark-gfm";
29
+ import remarkParse from "remark-parse";
30
+ import { unified } from "unified";
31
+ import { parse as parseYaml } from "yaml";
32
+ var parseMarkdown = (content) => {
33
+ var _a, _b;
34
+ const processor = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter, ["yaml"]);
35
+ const ast = processor.parse(content);
36
+ let frontmatter = {};
37
+ if (((_a = ast.children[0]) == null ? void 0 : _a.type) === "yaml") {
38
+ const raw = ast.children[0].value;
39
+ const parsed = parseYaml(raw);
40
+ const title = (_b = parsed.title) != null ? _b : parsed.name;
41
+ frontmatter = {
42
+ title: typeof title === "string" ? title : void 0,
43
+ description: typeof parsed.description === "string" ? parsed.description : void 0
44
+ };
45
+ ast.children.shift();
46
+ }
47
+ return { ast, frontmatter };
48
+ };
49
+
50
+ // src/utils/renderMarkdown.tsx
51
+ import {
52
+ BlogSection,
53
+ Callout,
54
+ CodeBlock,
55
+ Mermaid,
56
+ Table
57
+ } from "@san-siva/blogkit";
58
+
59
+ // src/utils/extractText.ts
60
+ var extractText = (nodes) => nodes.map((node) => {
61
+ if (node.type === "text") return node.value;
62
+ if ("children" in node)
63
+ return extractText(node.children);
64
+ return "";
65
+ }).join("");
66
+
67
+ // src/utils/groupSections.ts
68
+ var consumeNode = (nodes, index, sections, section) => {
69
+ const node = nodes.at(index);
70
+ if (!node) {
71
+ return sections;
72
+ }
73
+ const isHeading = node.type === "heading";
74
+ if (!isHeading) {
75
+ section.nodes.push(node);
76
+ return consumeNode(nodes, index + 1, sections, section);
77
+ }
78
+ const headingLevel = node.depth;
79
+ const isIncrementingHeading = headingLevel > section.headingLevel;
80
+ if (isIncrementingHeading) {
81
+ const subsection = {
82
+ title: extractText(node.children),
83
+ headingLevel,
84
+ nodes: [],
85
+ subsections: [],
86
+ previousSection: section
87
+ };
88
+ section.subsections.push(subsection);
89
+ return consumeNode(nodes, index + 1, sections, subsection);
90
+ }
91
+ if (!section.previousSection) {
92
+ const subSection = {
93
+ title: extractText(node.children),
94
+ headingLevel,
95
+ nodes: [],
96
+ subsections: [],
97
+ previousSection: void 0
98
+ };
99
+ sections.push(subSection);
100
+ return consumeNode(nodes, index + 1, sections, subSection);
101
+ }
102
+ return consumeNode(nodes, index, sections, section.previousSection);
103
+ };
104
+ var groupSections = (nodes) => {
105
+ const initialSection = {
106
+ title: "",
107
+ headingLevel: Infinity,
108
+ nodes: [],
109
+ subsections: [],
110
+ previousSection: void 0
111
+ };
112
+ const sections = [initialSection];
113
+ consumeNode(nodes, 0, sections, initialSection);
114
+ return sections.filter((s) => s.title !== "" || s.nodes.length > 0);
115
+ };
116
+
117
+ // src/utils/renderPhrasingContent.tsx
118
+ import styles from "@san-siva/stylekit/styles/index.module.scss";
119
+ import { jsx } from "react/jsx-runtime";
120
+ function escapeHtml(text) {
121
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
122
+ }
123
+ function toHtmlString(nodes) {
124
+ return nodes.map((node) => {
125
+ var _a;
126
+ switch (node.type) {
127
+ case "text": {
128
+ return escapeHtml(node.value);
129
+ }
130
+ case "html": {
131
+ return node.value;
132
+ }
133
+ case "strong": {
134
+ return `<strong>${toHtmlString(node.children)}</strong>`;
135
+ }
136
+ case "emphasis": {
137
+ return `<em>${toHtmlString(node.children)}</em>`;
138
+ }
139
+ case "inlineCode": {
140
+ return `<code>${escapeHtml(node.value)}</code>`;
141
+ }
142
+ case "link": {
143
+ return `<a href="${escapeHtml(node.url)}">${toHtmlString(node.children)}</a>`;
144
+ }
145
+ case "break": {
146
+ return "<br>";
147
+ }
148
+ case "image": {
149
+ return `<img src="${escapeHtml(node.url)}" alt="${escapeHtml((_a = node.alt) != null ? _a : "")}" style="max-width:300px">`;
150
+ }
151
+ default: {
152
+ return "";
153
+ }
154
+ }
155
+ }).join("");
156
+ }
157
+ function renderPhrasingContent(nodes) {
158
+ if (nodes.some((node) => node.type === "html")) {
159
+ return /* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: toHtmlString(nodes) } });
160
+ }
161
+ return nodes.map((node, index) => {
162
+ var _a;
163
+ switch (node.type) {
164
+ case "text": {
165
+ return node.value;
166
+ }
167
+ case "strong": {
168
+ return /* @__PURE__ */ jsx("strong", { children: renderPhrasingContent(node.children) }, index);
169
+ }
170
+ case "emphasis": {
171
+ return /* @__PURE__ */ jsx("em", { children: renderPhrasingContent(node.children) }, index);
172
+ }
173
+ case "inlineCode": {
174
+ return /* @__PURE__ */ jsx("code", { children: node.value }, index);
175
+ }
176
+ case "link": {
177
+ return /* @__PURE__ */ jsx("a", { href: node.url, className: styles["a--highlighted"], children: renderPhrasingContent(node.children) }, index);
178
+ }
179
+ case "break": {
180
+ return /* @__PURE__ */ jsx("br", {}, index);
181
+ }
182
+ case "image": {
183
+ return /* @__PURE__ */ jsx(
184
+ "img",
185
+ {
186
+ src: node.url,
187
+ alt: (_a = node.alt) != null ? _a : "",
188
+ style: { maxWidth: "300px" }
189
+ },
190
+ index
191
+ );
192
+ }
193
+ default: {
194
+ return null;
195
+ }
196
+ }
197
+ });
198
+ }
199
+
200
+ // src/utils/renderMarkdown.tsx
201
+ import styles2 from "@san-siva/stylekit/styles/index.module.scss";
202
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
203
+ function renderNode({
204
+ node,
205
+ key,
206
+ nextNode,
207
+ inList = false,
208
+ inCallout = false
209
+ }) {
210
+ var _a;
211
+ switch (node.type) {
212
+ case "paragraph": {
213
+ if (inList) {
214
+ return /* @__PURE__ */ jsx2("p", { children: renderPhrasingContent(node.children) }, key);
215
+ }
216
+ const isFollowedByParagraph = (nextNode == null ? void 0 : nextNode.type) === "paragraph";
217
+ const isLastInCallout = !nextNode && inCallout;
218
+ const marginClass = isFollowedByParagraph ? styles2["margin-bottom--1"] : isLastInCallout ? void 0 : styles2["margin-bottom--2"];
219
+ return /* @__PURE__ */ jsx2("p", { className: marginClass, children: renderPhrasingContent(node.children) }, key);
220
+ }
221
+ case "code": {
222
+ if (node.lang === "mermaid") {
223
+ return /* @__PURE__ */ jsx2(
224
+ Mermaid,
225
+ {
226
+ id: `mermaid-${key}`,
227
+ code: node.value,
228
+ hasMarginUp: true,
229
+ hasMarginDown: true
230
+ },
231
+ key
232
+ );
233
+ }
234
+ return /* @__PURE__ */ jsx2(
235
+ CodeBlock,
236
+ {
237
+ language: (_a = node.lang) != null ? _a : "text",
238
+ code: node.value,
239
+ hasMarginUp: true,
240
+ hasMarginDown: true
241
+ },
242
+ key
243
+ );
244
+ }
245
+ case "heading": {
246
+ return /* @__PURE__ */ jsx2("p", { className: styles2["margin-bottom--2"], children: /* @__PURE__ */ jsx2("strong", { children: renderPhrasingContent(node.children) }) }, key);
247
+ }
248
+ case "thematicBreak": {
249
+ return /* @__PURE__ */ jsx2("hr", { className: styles2["margin-bottom--2"] }, key);
250
+ }
251
+ case "table": {
252
+ const [headerRow, ...bodyRows] = node.children;
253
+ const headers = headerRow == null ? void 0 : headerRow.children.map(
254
+ (cell) => renderPhrasingContent(cell.children)
255
+ );
256
+ const rows = bodyRows.map(
257
+ (row) => row.children.map((cell, index) => /* @__PURE__ */ jsx2("p", { children: renderPhrasingContent(cell.children) }, index))
258
+ );
259
+ return /* @__PURE__ */ jsx2(
260
+ Table,
261
+ {
262
+ headers,
263
+ rows,
264
+ hasMarginUp: true,
265
+ hasMarginDown: true
266
+ },
267
+ key
268
+ );
269
+ }
270
+ case "blockquote": {
271
+ const children = node.children;
272
+ let calloutType = "info";
273
+ let strippedChildren = children;
274
+ const firstChild = children[0];
275
+ if ((firstChild == null ? void 0 : firstChild.type) === "paragraph") {
276
+ const firstInline = firstChild.children[0];
277
+ if ((firstInline == null ? void 0 : firstInline.type) === "text") {
278
+ if (firstInline.value.startsWith("!")) {
279
+ calloutType = "error";
280
+ const trimmed = firstInline.value.slice(1).trimStart();
281
+ strippedChildren = [
282
+ __spreadProps(__spreadValues({}, firstChild), {
283
+ children: [
284
+ __spreadProps(__spreadValues({}, firstInline), { value: trimmed }),
285
+ ...firstChild.children.slice(1)
286
+ ]
287
+ }),
288
+ ...children.slice(1)
289
+ ];
290
+ } else if (firstInline.value.startsWith("~")) {
291
+ calloutType = "warning";
292
+ const trimmed = firstInline.value.slice(1).trimStart();
293
+ strippedChildren = [
294
+ __spreadProps(__spreadValues({}, firstChild), {
295
+ children: [
296
+ __spreadProps(__spreadValues({}, firstInline), { value: trimmed }),
297
+ ...firstChild.children.slice(1)
298
+ ]
299
+ }),
300
+ ...children.slice(1)
301
+ ];
302
+ }
303
+ }
304
+ }
305
+ return /* @__PURE__ */ jsx2(Callout, { type: calloutType, hasMarginUp: true, hasMarginDown: true, children: strippedChildren.map(
306
+ (child, index) => renderNode({
307
+ node: child,
308
+ key: index,
309
+ nextNode: strippedChildren[index + 1],
310
+ inCallout: true
311
+ })
312
+ ) }, key);
313
+ }
314
+ case "list": {
315
+ const Tag = node.ordered ? "ol" : "ul";
316
+ return /* @__PURE__ */ jsx2(Tag, { className: styles2["margin-bottom--2"], children: node.children.map((item, index) => /* @__PURE__ */ jsx2("li", { children: item.children.map(
317
+ (child, index2) => renderNode({
318
+ node: child,
319
+ key: index2,
320
+ inList: true
321
+ })
322
+ ) }, index)) }, key);
323
+ }
324
+ default: {
325
+ return null;
326
+ }
327
+ }
328
+ }
329
+ function renderNodes(nodes) {
330
+ return nodes.map(
331
+ (node, index) => renderNode({ node, key: index, nextNode: nodes[index + 1] })
332
+ );
333
+ }
334
+ function renderSection(section, key = -1) {
335
+ var _a;
336
+ return /* @__PURE__ */ jsxs(BlogSection, { title: (_a = section == null ? void 0 : section.title) != null ? _a : "", children: [
337
+ renderNodes(section.nodes),
338
+ section.subsections.map(
339
+ (subsection, index) => renderSection(subsection, index)
340
+ )
341
+ ] }, key);
342
+ }
343
+ var renderMarkdownAst = (ast) => {
344
+ const grouped = groupSections(ast.children);
345
+ return {
346
+ sections: grouped.map((section, index) => renderSection(section, index))
347
+ };
348
+ };
349
+ var MarkdownSections = ({
350
+ rendered
351
+ }) => /* @__PURE__ */ jsx2(Fragment, { children: rendered.sections });
352
+
353
+ // src/components/BlogPost.tsx
354
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
355
+ var BlogPost = async ({ filePath, jsonLd }) => {
356
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
357
+ let content;
358
+ try {
359
+ content = await readFile(absolutePath, "utf8");
360
+ } catch (e) {
361
+ return /* @__PURE__ */ jsx3(Blog, { children: /* @__PURE__ */ jsxs2(Callout2, { type: "warning", children: [
362
+ 'Could not read file: "',
363
+ filePath,
364
+ '". Make sure the path is correct and the file exists.'
365
+ ] }) });
366
+ }
367
+ if (!content.trim()) {
368
+ return /* @__PURE__ */ jsx3(Blog, { children: /* @__PURE__ */ jsxs2(Callout2, { type: "warning", children: [
369
+ 'File "',
370
+ filePath,
371
+ '" is empty.'
372
+ ] }) });
373
+ }
374
+ const { ast, frontmatter } = parseMarkdown(content);
375
+ const rendered = renderMarkdownAst(ast);
376
+ const title = frontmatter.title;
377
+ const desc = frontmatter.description;
378
+ return /* @__PURE__ */ jsxs2(Blog, { jsonLd, children: [
379
+ title && /* @__PURE__ */ jsx3(BlogHeader, { title: [title], desc: desc ? [desc] : [] }),
380
+ /* @__PURE__ */ jsx3(MarkdownSections, { rendered })
381
+ ] });
382
+ };
383
+ var BlogPost_default = BlogPost;
384
+
385
+ // src/hooks/readMarkdownFile.ts
386
+ import { readFile as readFile2 } from "fs/promises";
387
+ import path2 from "path";
388
+ var readMarkdownFile = async (filePath) => {
389
+ if (!filePath) {
390
+ return { success: false, error: "No file path provided." };
391
+ }
392
+ const absolutePath = path2.isAbsolute(filePath) ? filePath : path2.join(process.cwd(), filePath);
393
+ let content;
394
+ try {
395
+ content = await readFile2(absolutePath, "utf8");
396
+ } catch (e) {
397
+ return {
398
+ success: false,
399
+ error: `Could not read file: "${filePath}". Make sure the path is correct and the file exists.`
400
+ };
401
+ }
402
+ if (!content.trim()) {
403
+ return { success: false, error: `File "${filePath}" is empty.` };
404
+ }
405
+ const { ast, frontmatter } = parseMarkdown(content);
406
+ const rendered = renderMarkdownAst(ast);
407
+ return { success: true, rendered, frontmatter };
408
+ };
409
+ export {
410
+ BlogPost_default as BlogPost,
411
+ MarkdownSections,
412
+ readMarkdownFile
413
+ };
package/package.json CHANGED
@@ -1,12 +1,21 @@
1
1
  {
2
2
  "name": "@san-siva/blogkit-md",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Converts markdown files into JSX blog posts for Blogkit",
5
- "main": "index.ts",
5
+ "main": "./dist/index.js",
6
6
  "exports": {
7
- ".": "./index.ts"
7
+ ".": {
8
+ "import": "./dist/index.js",
9
+ "types": "./dist/index.d.ts"
10
+ }
8
11
  },
12
+ "files": [
13
+ "dist",
14
+ "src"
15
+ ],
9
16
  "scripts": {
17
+ "build": "tsup",
18
+ "prepublishOnly": "npm ci && npm run lint && npm run test && npm run build",
10
19
  "test": "vitest run",
11
20
  "lint": "eslint . --config eslint.config.ts",
12
21
  "fix": "eslint . --config eslint.config.ts --fix"
@@ -30,6 +39,7 @@
30
39
  "@types/eslint": "^9.6.1",
31
40
  "@types/eslint__js": "^9.14.0",
32
41
  "@types/mdast": "^4.0.4",
42
+ "tsup": "^8.0.0",
33
43
  "@types/node": "^22",
34
44
  "@types/react": "^19",
35
45
  "@types/react-dom": "^19",
@@ -0,0 +1,4 @@
1
+ declare module '*.module.scss' {
2
+ const styles: Record<string, string>;
3
+ export default styles;
4
+ }
@@ -24,8 +24,9 @@ export const parseMarkdown = (content: string): ParseResult => {
24
24
  if (ast.children[0]?.type === 'yaml') {
25
25
  const raw = (ast.children[0] as Yaml).value;
26
26
  const parsed = parseYaml(raw) as Record<string, unknown>;
27
+ const title = parsed.title ?? parsed.name;
27
28
  frontmatter = {
28
- title: typeof parsed.title === 'string' ? parsed.title : undefined,
29
+ title: typeof title === 'string' ? title : undefined,
29
30
  description: typeof parsed.description === 'string' ? parsed.description : undefined,
30
31
  };
31
32
  ast.children.shift();
package/.prettierrc.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "printWidth": 80,
3
- "tabWidth": 2,
4
- "singleQuote": true,
5
- "trailingComma": "es5",
6
- "useTabs": true,
7
- "arrowParens": "avoid",
8
- "semi": true
9
- }
@@ -1,29 +0,0 @@
1
- import tseslint, { configs as tseslintConfigs } from 'typescript-eslint';
2
-
3
- import {
4
- defaultExtends,
5
- defaultPlugins,
6
- defaultRules,
7
- defaultSettings,
8
- languageOptions,
9
- testFiles,
10
- } from './utilities';
11
-
12
- export default tseslint.config(
13
- {
14
- ignores: ['node_modules/**', 'dist/**', '.next/**', 'next-env.d.ts'],
15
- },
16
- {
17
- files: ['**/*.ts', '**/*.js'],
18
- ignores: testFiles,
19
- plugins: defaultPlugins,
20
- extends: defaultExtends,
21
- rules: defaultRules,
22
- settings: defaultSettings,
23
- languageOptions,
24
- },
25
- {
26
- files: ['eslint.config.ts', 'eslint/**/*.ts'],
27
- extends: [tseslintConfigs.disableTypeChecked],
28
- }
29
- );
@@ -1,20 +0,0 @@
1
- import tseslint from 'typescript-eslint';
2
-
3
- import {
4
- languageOptions,
5
- reactExtends,
6
- reactPlugins,
7
- reactRules,
8
- reactSettings,
9
- testFiles,
10
- } from './utilities';
11
-
12
- export default tseslint.config({
13
- files: ['**/*.tsx', '**/*.jsx'],
14
- ignores: testFiles,
15
- plugins: reactPlugins,
16
- extends: reactExtends,
17
- rules: reactRules,
18
- settings: reactSettings,
19
- languageOptions,
20
- });