@san-siva/blogkit-md 0.3.5 → 0.3.7

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
@@ -3,6 +3,8 @@ title: blogkit-md
3
3
  description: A React component library that converts markdown files into rendered blog posts for `@san-siva/blogkit`.
4
4
  ---
5
5
 
6
+ # Hello world
7
+
6
8
  A React component library that converts markdown files into rendered blog posts for [`@san-siva/blogkit`](https://blogkit.santhoshsiva.dev).
7
9
 
8
10
  > Live preview of this README is available here [blogkit-md.santhoshsiva.dev](https://blogkit-md.santhoshsiva.dev)
package/dist/index.js CHANGED
@@ -369,9 +369,18 @@ function renderSection(section, counters, key = -1) {
369
369
  )
370
370
  ] }, key);
371
371
  }
372
- var renderMarkdownAst = (ast) => {
372
+ function stripRedundantSectionTitle(grouped, isTitleEmpty) {
373
+ if (!isTitleEmpty && grouped.length === 1) {
374
+ const [{ title, nodes, subsections }] = grouped;
375
+ if (title.length > 0 && (nodes.length > 0 || subsections.length > 0)) {
376
+ grouped[0].title = "";
377
+ }
378
+ }
379
+ }
380
+ var renderMarkdownAst = (ast, isTitleEmpty) => {
373
381
  const counters = { mermaid: 0 };
374
382
  const grouped = groupSections(ast.children);
383
+ stripRedundantSectionTitle(grouped, isTitleEmpty);
375
384
  return {
376
385
  sections: grouped.map(
377
386
  (section, index) => renderSection(section, counters, index)
@@ -404,9 +413,10 @@ var BlogPost = async ({ filePath, jsonLd }) => {
404
413
  ] }) });
405
414
  }
406
415
  const { ast, frontmatter } = parseMarkdown(content);
407
- const rendered = renderMarkdownAst(ast);
408
416
  const title = frontmatter.title;
409
417
  const desc = frontmatter.description;
418
+ const isTitleEmpty = !title || title.trim() === "";
419
+ const rendered = renderMarkdownAst(ast, isTitleEmpty);
410
420
  return /* @__PURE__ */ jsxs2(Blog, { jsonLd, children: [
411
421
  title && /* @__PURE__ */ jsx3(BlogHeader, { title: [title], desc: desc ? [desc] : [] }),
412
422
  /* @__PURE__ */ jsx3(MarkdownSections, { rendered })
@@ -435,7 +445,7 @@ var readMarkdownFile = async (filePath) => {
435
445
  return { success: false, error: `File "${filePath}" is empty.` };
436
446
  }
437
447
  const { ast, frontmatter } = parseMarkdown(content);
438
- const rendered = renderMarkdownAst(ast);
448
+ const rendered = renderMarkdownAst(ast, !frontmatter.title);
439
449
  return __spreadValues({ success: true, rendered }, frontmatter);
440
450
  };
441
451
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@san-siva/blogkit-md",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Converts markdown files into JSX blog posts for Blogkit",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -21,7 +21,7 @@
21
21
  "fix": "eslint . --config eslint.config.ts --fix"
22
22
  },
23
23
  "dependencies": {
24
- "@san-siva/blogkit": "^1.1.29",
24
+ "@san-siva/blogkit": "^1.1.30",
25
25
  "@san-siva/stylekit": "^1.0.12",
26
26
  "next": "^16.0.10",
27
27
  "react": "^19.0.0",
@@ -41,11 +41,13 @@ const BlogPost = async ({ filePath, jsonLd }: BlogPostProperties) => {
41
41
  }
42
42
 
43
43
  const { ast, frontmatter } = parseMarkdown(content);
44
- const rendered = renderMarkdownAst(ast);
45
-
46
44
  const title = frontmatter.title;
47
45
  const desc = frontmatter.description;
48
46
 
47
+ const isTitleEmpty = !title || title.trim() === '';
48
+
49
+ const rendered = renderMarkdownAst(ast, isTitleEmpty);
50
+
49
51
  return (
50
52
  <Blog jsonLd={jsonLd}>
51
53
  {title && <BlogHeader title={[title]} desc={desc ? [desc] : []} />}
@@ -36,7 +36,7 @@ export const readMarkdownFile = async (
36
36
  }
37
37
 
38
38
  const { ast, frontmatter } = parseMarkdown(content);
39
- const rendered = renderMarkdownAst(ast);
39
+ const rendered = renderMarkdownAst(ast, !frontmatter.title);
40
40
 
41
41
  return { success: true, rendered, ...frontmatter };
42
42
  };
@@ -0,0 +1,111 @@
1
+ import React from 'react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import remarkGfm from 'remark-gfm';
4
+ import remarkParse from 'remark-parse';
5
+ import { unified } from 'unified';
6
+ import type { Root } from 'mdast';
7
+
8
+ vi.mock('@san-siva/stylekit/styles/index.module.scss', () => ({ default: {} }));
9
+ vi.mock('@san-siva/blogkit', () => ({
10
+ BlogSection: 'BlogSection',
11
+ Callout: 'Callout',
12
+ CheckList: 'CheckList',
13
+ CodeBlock: 'CodeBlock',
14
+ Mermaid: 'Mermaid',
15
+ Table: 'Table',
16
+ }));
17
+
18
+ import { renderMarkdownAst, stripRedundantSectionTitle } from './renderMarkdown';
19
+ import type { Section } from './groupSections';
20
+
21
+ const makeSection = (
22
+ title: string,
23
+ nodes: Section['nodes'] = [],
24
+ subsections: Section[] = []
25
+ ): Section => ({
26
+ title,
27
+ headingLevel: 2,
28
+ nodes,
29
+ subsections,
30
+ });
31
+
32
+ const parse = (md: string): Root =>
33
+ unified().use(remarkParse).use(remarkGfm).parse(md) as Root;
34
+
35
+ describe('stripRedundantSectionTitle', () => {
36
+ it('clears the section title when isTitleEmpty is false and the section has nodes', () => {
37
+ const grouped = [makeSection('My Title', [{ type: 'paragraph', children: [] }])];
38
+ stripRedundantSectionTitle(grouped, false);
39
+ expect(grouped[0].title).toBe('');
40
+ });
41
+
42
+ it('clears the section title when isTitleEmpty is false and the section has subsections', () => {
43
+ const sub = makeSection('Sub', [{ type: 'paragraph', children: [] }]);
44
+ const grouped = [makeSection('My Title', [], [sub])];
45
+ stripRedundantSectionTitle(grouped, false);
46
+ expect(grouped[0].title).toBe('');
47
+ });
48
+
49
+ it('does not clear the title when isTitleEmpty is true', () => {
50
+ const grouped = [makeSection('My Title', [{ type: 'paragraph', children: [] }])];
51
+ stripRedundantSectionTitle(grouped, true);
52
+ expect(grouped[0].title).toBe('My Title');
53
+ });
54
+
55
+ it('does not clear the title when there are multiple sections', () => {
56
+ const grouped = [
57
+ makeSection('Title A', [{ type: 'paragraph', children: [] }]),
58
+ makeSection('Title B', [{ type: 'paragraph', children: [] }]),
59
+ ];
60
+ stripRedundantSectionTitle(grouped, false);
61
+ expect(grouped[0].title).toBe('Title A');
62
+ expect(grouped[1].title).toBe('Title B');
63
+ });
64
+
65
+ it('does not clear the title when the section has no nodes or subsections', () => {
66
+ const grouped = [makeSection('My Title')];
67
+ stripRedundantSectionTitle(grouped, false);
68
+ expect(grouped[0].title).toBe('My Title');
69
+ });
70
+
71
+ it('does not clear the title when it is already empty', () => {
72
+ const grouped = [makeSection('', [{ type: 'paragraph', children: [] }])];
73
+ stripRedundantSectionTitle(grouped, false);
74
+ expect(grouped[0].title).toBe('');
75
+ });
76
+ });
77
+
78
+ describe('renderMarkdownAst', () => {
79
+ it('returns an empty sections array for empty markdown', () => {
80
+ const result = renderMarkdownAst(parse(''), false);
81
+ expect(result.sections).toHaveLength(0);
82
+ });
83
+
84
+ it('returns one section for single-section markdown', () => {
85
+ const result = renderMarkdownAst(parse('## Section\n\nSome content'), false);
86
+ expect(result.sections).toHaveLength(1);
87
+ });
88
+
89
+ it('returns multiple sections for multi-section markdown', () => {
90
+ const result = renderMarkdownAst(parse('## One\n\n## Two\n\n## Three'), false);
91
+ expect(result.sections).toHaveLength(3);
92
+ });
93
+
94
+ it('strips the section title when isTitleEmpty is false and there is a single titled section', () => {
95
+ const result = renderMarkdownAst(parse('## Section\n\nSome content'), false);
96
+ const section = result.sections[0] as React.ReactElement;
97
+ expect(section.props.title).toBe('');
98
+ });
99
+
100
+ it('preserves the section title when isTitleEmpty is true', () => {
101
+ const result = renderMarkdownAst(parse('## Section\n\nSome content'), true);
102
+ const section = result.sections[0] as React.ReactElement;
103
+ expect(section.props.title).toBe('Section');
104
+ });
105
+
106
+ it('preserves titles of all sections when there are multiple sections', () => {
107
+ const result = renderMarkdownAst(parse('## One\n\n## Two'), false);
108
+ const titles = (result.sections as React.ReactElement[]).map(s => s.props.title);
109
+ expect(titles).toEqual(['One', 'Two']);
110
+ });
111
+ });
@@ -221,9 +221,27 @@ export type RenderedMarkdown = {
221
221
  sections: React.ReactNode[];
222
222
  };
223
223
 
224
- export const renderMarkdownAst = (ast: Root): RenderedMarkdown => {
224
+ export function stripRedundantSectionTitle(
225
+ grouped: Section[],
226
+ isTitleEmpty: boolean
227
+ ): void {
228
+ if (!isTitleEmpty && grouped.length === 1) {
229
+ const [{ title, nodes, subsections }] = grouped;
230
+ if (title.length > 0 && (nodes.length > 0 || subsections.length > 0)) {
231
+ grouped[0].title = '';
232
+ }
233
+ }
234
+ }
235
+
236
+ export const renderMarkdownAst = (
237
+ ast: Root,
238
+ isTitleEmpty: boolean
239
+ ): RenderedMarkdown => {
225
240
  const counters: Counters = { mermaid: 0 };
226
241
  const grouped = groupSections(ast.children);
242
+
243
+ stripRedundantSectionTitle(grouped, isTitleEmpty);
244
+
227
245
  return {
228
246
  sections: grouped.map((section, index) =>
229
247
  renderSection(section, counters, index)