@se-studio/markdown-renderer 1.0.10 → 1.0.11

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/CHANGELOG.md ADDED
@@ -0,0 +1,89 @@
1
+ # @se-studio/markdown-renderer
2
+
3
+ ## 1.0.11
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @se-studio/contentful-rest-api@1.0.47
9
+ - @se-studio/core-data-types@1.0.47
10
+
11
+ ## 1.0.10
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies
16
+ - @se-studio/contentful-rest-api@1.0.46
17
+ - @se-studio/core-data-types@1.0.46
18
+
19
+ ## 1.0.9
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+ - @se-studio/contentful-rest-api@1.0.45
25
+ - @se-studio/core-data-types@1.0.45
26
+
27
+ ## 1.0.8
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies
32
+ - @se-studio/contentful-rest-api@1.0.44
33
+ - @se-studio/core-data-types@1.0.44
34
+
35
+ ## 1.0.7
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies
40
+ - @se-studio/contentful-rest-api@1.0.43
41
+ - @se-studio/core-data-types@1.0.43
42
+
43
+ ## 1.0.6
44
+
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies
48
+ - @se-studio/contentful-rest-api@1.0.42
49
+ - @se-studio/core-data-types@1.0.42
50
+
51
+ ## 1.0.5
52
+
53
+ ### Patch Changes
54
+
55
+ - Updated dependencies
56
+ - @se-studio/contentful-rest-api@1.0.41
57
+ - @se-studio/core-data-types@1.0.41
58
+
59
+ ## 1.0.4
60
+
61
+ ### Patch Changes
62
+
63
+ - Updated dependencies
64
+ - @se-studio/contentful-rest-api@1.0.40
65
+ - @se-studio/core-data-types@1.0.40
66
+
67
+ ## 1.0.3
68
+
69
+ ### Patch Changes
70
+
71
+ - Updated dependencies
72
+ - @se-studio/contentful-rest-api@1.0.39
73
+ - @se-studio/core-data-types@1.0.39
74
+
75
+ ## 1.0.2
76
+
77
+ ### Patch Changes
78
+
79
+ - Updated dependencies
80
+ - @se-studio/contentful-rest-api@1.0.38
81
+ - @se-studio/core-data-types@1.0.38
82
+
83
+ ## 1.0.1
84
+
85
+ ### Patch Changes
86
+
87
+ - Updated dependencies
88
+ - @se-studio/contentful-rest-api@1.0.37
89
+ - @se-studio/core-data-types@1.0.37
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # @se-studio/markdown-renderer
2
+
3
+ Utilities for converting Contentful content into Markdown format. This package provides tools to export pages, articles, and custom types from Contentful and convert them into structured Markdown, suitable for LLM consumption or static site generation.
4
+
5
+ ## Features
6
+
7
+ - **Content Export**: Fetch content from Contentful (Pages, Articles, Custom Types) with full context.
8
+ - **Markdown Conversion**: Convert structured Contentful data into clean, readable Markdown.
9
+ - **Rich Text Support**: Automatically converts Contentful Rich Text fields to Markdown.
10
+ - **Component & Collection Handling**: Recursively processes nested components and collections.
11
+ - **Frontmatter Generation**: meaningful YAML frontmatter for exported content.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add @se-studio/markdown-renderer
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Basic Export & Conversion
22
+
23
+ ```typescript
24
+ import { createContentfulClient } from '@se-studio/contentful-rest-api';
25
+ import { MarkdownExporter, MarkdownConverter } from '@se-studio/markdown-renderer';
26
+
27
+ // 1. Setup Client and Exporter
28
+ const config = {
29
+ spaceId: process.env.CONTENTFUL_SPACE_ID!,
30
+ accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
31
+ environment: 'master'
32
+ };
33
+
34
+ const exporter = new MarkdownExporter(config);
35
+ const converter = new MarkdownConverter();
36
+
37
+ // 2. Fetch Content
38
+ // Supported types: 'page', 'article', 'blogPost', 'customType'
39
+ const contentData = await exporter.fetchContent('page', 'home');
40
+
41
+ if (contentData) {
42
+ // 3. Convert to Markdown
43
+ const markdown = await converter.convert(contentData, {
44
+ contentContext: contentData.context,
45
+ config: config
46
+ });
47
+
48
+ console.log(markdown);
49
+ }
50
+ ```
51
+
52
+ ## API Reference
53
+
54
+ ### `MarkdownExporter`
55
+
56
+ Handles fetching data from Contentful and preparing the context.
57
+
58
+ #### `constructor(config: ContentfulConfig, preview?: boolean)`
59
+ - `config`: Contentful configuration object.
60
+ - `preview`: Boolean to enable Preview API (default: `false`).
61
+
62
+ #### `fetchContent(type, slug, params?)`
63
+ Fetches content by slug and returns a `ContentData` object.
64
+ - `type`: `'page' | 'article' | 'blogPost' | 'customType'`
65
+ - `slug`: The slug of the entry.
66
+ - `params`: Optional parameters (e.g., `{ articleType: 'blog' }`).
67
+
68
+ ### `MarkdownConverter`
69
+
70
+ Handles the transformation of `ContentData` into a Markdown string.
71
+
72
+ #### `convert(contentData, context)`
73
+ - `contentData`: The data object returned by `MarkdownExporter`.
74
+ - `context`: Context object containing `contentContext` and `config`.
75
+
76
+ ### Types
77
+
78
+ ```typescript
79
+ interface ContentData {
80
+ contentType: 'page' | 'article' | 'customType';
81
+ data: IBasePage | IBaseArticle | IBaseCustomType;
82
+ context: IContentContext;
83
+ }
84
+
85
+ interface MarkdownConverterContext {
86
+ contentContext: IContentContext;
87
+ config: ContentfulConfig;
88
+ }
89
+ ```
90
+
91
+ ## License
92
+
93
+ MIT
@@ -0,0 +1,15 @@
1
+ import type { ContentData, MarkdownConverterContext } from './types';
2
+ export declare class MarkdownConverter {
3
+ convert(contentData: ContentData, context: MarkdownConverterContext): Promise<string>;
4
+ private generateFrontmatter;
5
+ private convertModule;
6
+ private convertGenericComponent;
7
+ private convertGenericCollection;
8
+ private renderPreHeading;
9
+ private renderPostHeading;
10
+ private renderBody;
11
+ private renderAdditionalCopy;
12
+ private renderVisual;
13
+ private renderLinks;
14
+ }
15
+ //# sourceMappingURL=MarkdownConverter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownConverter.d.ts","sourceRoot":"","sources":["../src/MarkdownConverter.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAGrE,qBAAa,iBAAiB;IACtB,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC;IA0B3F,OAAO,CAAC,mBAAmB;YAkBb,aAAa;IAgB3B,OAAO,CAAC,uBAAuB;YAwBjB,wBAAwB;IAwBtC,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,oBAAoB;IAI5B,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,WAAW;CAYpB"}
@@ -0,0 +1,122 @@
1
+ import yaml from 'js-yaml';
2
+ import { convertRichTextToMarkdown } from './utils/richTextConverter';
3
+ export class MarkdownConverter {
4
+ async convert(contentData, context) {
5
+ const { data, contentType } = contentData;
6
+ const frontmatter = this.generateFrontmatter(data, contentType);
7
+ let markdown = `---\n${yaml.dump(frontmatter)}---\n\n`;
8
+ // Add Title and basic body
9
+ if (data.title) {
10
+ markdown += `# ${data.title}\n\n`;
11
+ }
12
+ if (data.description) {
13
+ markdown += `> ${data.description}\n\n`;
14
+ }
15
+ // Page modules / contents
16
+ if (data.contents) {
17
+ for (const item of data.contents) {
18
+ if (!item)
19
+ continue;
20
+ markdown += await this.convertModule(item, context);
21
+ }
22
+ }
23
+ return markdown;
24
+ }
25
+ generateFrontmatter(data, type) {
26
+ const slug = data.slug;
27
+ return {
28
+ contentType: type,
29
+ title: data.title,
30
+ slug: slug,
31
+ id: data.id,
32
+ description: data.description,
33
+ // Add more specific fields for Article if needed (date, author, etc)
34
+ ...(type === 'article'
35
+ ? {
36
+ date: data.date,
37
+ articleType: data.articleType?.name,
38
+ }
39
+ : {}),
40
+ };
41
+ }
42
+ async convertModule(item, context) {
43
+ if (item.type === 'Component') {
44
+ const component = item;
45
+ // Use generic fallback for all components
46
+ return this.convertGenericComponent(component, context);
47
+ }
48
+ if (item.type === 'Collection') {
49
+ const collection = item;
50
+ // Use generic fallback for all collections
51
+ return this.convertGenericCollection(collection, context);
52
+ }
53
+ return '';
54
+ }
55
+ convertGenericComponent(component, context) {
56
+ let md = '';
57
+ // Add heading hierarchy
58
+ if (component.heading) {
59
+ md += `## ${component.heading}\n\n`;
60
+ }
61
+ else if (component.componentType) {
62
+ // Fallback to component type if no heading, but NOT cmsLabel
63
+ md += `## ${component.componentType}\n\n`;
64
+ }
65
+ md += this.renderPreHeading(component.preHeading);
66
+ md += this.renderPostHeading(component.postHeading);
67
+ md += this.renderBody(component.body, context);
68
+ md += this.renderAdditionalCopy(component.additionalCopy, context);
69
+ md += this.renderVisual(component.visual);
70
+ md += this.renderLinks(component.links);
71
+ return md;
72
+ }
73
+ async convertGenericCollection(collection, context) {
74
+ let md = `## ${collection.heading || collection.collectionType || 'Collection'}\n\n`;
75
+ md += this.renderPreHeading(collection.preHeading);
76
+ md += this.renderPostHeading(collection.postHeading);
77
+ md += this.renderBody(collection.body, context);
78
+ md += this.renderLinks(collection.links);
79
+ if (collection.contents) {
80
+ for (const item of collection.contents) {
81
+ if (!item)
82
+ continue;
83
+ md += await this.convertModule(item, context);
84
+ }
85
+ }
86
+ md += this.renderAdditionalCopy(collection.additionalCopy, context);
87
+ md += this.renderVisual(collection.visual);
88
+ return md;
89
+ }
90
+ renderPreHeading(preHeading) {
91
+ return preHeading ? `**${preHeading}**\n\n` : '';
92
+ }
93
+ renderPostHeading(postHeading) {
94
+ return postHeading ? `*${postHeading}*\n\n` : '';
95
+ }
96
+ // biome-ignore lint/suspicious/noExplicitAny: generic content body
97
+ renderBody(body, context) {
98
+ return body ? convertRichTextToMarkdown(body, context) : '';
99
+ }
100
+ // biome-ignore lint/suspicious/noExplicitAny: generic additional copy
101
+ renderAdditionalCopy(additionalCopy, context) {
102
+ return additionalCopy ? `\n\n${convertRichTextToMarkdown(additionalCopy, context)}` : '';
103
+ }
104
+ renderVisual(visual) {
105
+ const image = visual?.visual?.image;
106
+ if (!image)
107
+ return '';
108
+ const title = image.name || 'Image';
109
+ const url = image.src || image.svgSrc;
110
+ return url ? `![${title}](${url})\n\n` : '';
111
+ }
112
+ renderLinks(links) {
113
+ if (!links?.length)
114
+ return '';
115
+ let md = '\n';
116
+ for (const link of links) {
117
+ md += `- [${link.text || link.name}](${link.href || '#'}) \n`;
118
+ }
119
+ return `${md}\n`;
120
+ }
121
+ }
122
+ //# sourceMappingURL=MarkdownConverter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownConverter.js","sourceRoot":"","sources":["../src/MarkdownConverter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { type ContentfulConfig } from '@se-studio/contentful-rest-api';
2
+ import type { ContentData } from './types';
3
+ export declare class MarkdownExporter {
4
+ private config;
5
+ private preview;
6
+ constructor(config: ContentfulConfig, preview?: boolean);
7
+ fetchContent(type: 'page' | 'article' | 'blogPost' | 'customType', slug: string, params?: {
8
+ articleType?: string;
9
+ }): Promise<ContentData | null>;
10
+ private createContext;
11
+ }
12
+ //# sourceMappingURL=MarkdownExporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownExporter.d.ts","sourceRoot":"","sources":["../src/MarkdownExporter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAMtB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,OAAO,CAAU;gBAEb,MAAM,EAAE,gBAAgB,EAAE,OAAO,UAAQ;IAK/C,YAAY,CAChB,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,EACpD,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAChC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAoE9B,OAAO,CAAC,aAAa;CAetB"}
@@ -0,0 +1,84 @@
1
+ import { contentfulArticleRest, contentfulCustomTypeRest, contentfulPageRest, createBaseConverterContext, } from '@se-studio/contentful-rest-api';
2
+ export class MarkdownExporter {
3
+ config;
4
+ preview;
5
+ constructor(config, preview = false) {
6
+ this.config = config;
7
+ this.preview = preview;
8
+ }
9
+ async fetchContent(type, slug, params) {
10
+ const context = this.createContext();
11
+ if (type === 'page') {
12
+ const response = await contentfulPageRest(context, this.config, slug, {
13
+ preview: this.preview,
14
+ });
15
+ if (!response.data)
16
+ return null;
17
+ const page = response.data;
18
+ return {
19
+ contentType: 'page',
20
+ data: page,
21
+ context: {
22
+ pageContext: {
23
+ page,
24
+ },
25
+ current: { type: 'Page', id: page.id },
26
+ },
27
+ };
28
+ }
29
+ if (type === 'article' || type === 'blogPost') {
30
+ const articleType = params?.articleType || 'blog';
31
+ const response = await contentfulArticleRest(context, this.config, slug, articleType, {
32
+ preview: this.preview,
33
+ });
34
+ if (!response.data)
35
+ return null;
36
+ const article = response.data;
37
+ return {
38
+ contentType: 'article',
39
+ data: article,
40
+ context: {
41
+ pageContext: {
42
+ article,
43
+ articleType: article.articleType,
44
+ },
45
+ current: { type: 'Article', id: article.id },
46
+ },
47
+ };
48
+ }
49
+ if (type === 'customType') {
50
+ const response = await contentfulCustomTypeRest(context, this.config, slug, {
51
+ preview: this.preview,
52
+ });
53
+ if (!response.data)
54
+ return null;
55
+ const customType = response.data;
56
+ return {
57
+ contentType: 'customType',
58
+ data: customType,
59
+ context: {
60
+ pageContext: {
61
+ customType,
62
+ },
63
+ current: { type: 'CustomType', id: customType.id },
64
+ },
65
+ };
66
+ }
67
+ return null;
68
+ }
69
+ createContext() {
70
+ // Basic context for fetching - we might need to make this configurable if URL calculation differs per app
71
+ // For now using simple defaults that produce relative paths
72
+ const urlCalculators = {
73
+ page: (slug) => `/${slug}`,
74
+ pageVariant: (slug) => `/${slug}`,
75
+ article: (type, slug) => `/${type}/${slug}`,
76
+ articleType: (slug) => `/${slug}`,
77
+ tag: (slug) => `/topics/${slug}`,
78
+ person: (slug) => `/team/${slug}`,
79
+ customType: (slug) => `/${slug}`,
80
+ };
81
+ return createBaseConverterContext(urlCalculators);
82
+ }
83
+ }
84
+ //# sourceMappingURL=MarkdownExporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownExporter.js","sourceRoot":"","sources":["../src/MarkdownExporter.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,42 +1,4 @@
1
- import { ContentfulConfig } from '@se-studio/contentful-rest-api';
2
- import { IBasePage, IBaseArticle, IBaseCustomType, IContentContext } from '@se-studio/core-data-types';
3
-
4
- interface ContentData {
5
- contentType: 'page' | 'article' | 'customType';
6
- data: IBasePage | IBaseArticle | IBaseCustomType;
7
- context: IContentContext;
8
- }
9
- interface MarkdownConverterContext {
10
- contentContext: IContentContext;
11
- config: ContentfulConfig;
12
- }
13
- interface ConverterResult {
14
- markdown: string;
15
- }
16
- type ConverterFunction<T = any> = (data: T, context: MarkdownConverterContext) => Promise<string> | string;
17
-
18
- declare class MarkdownConverter {
19
- convert(contentData: ContentData, context: MarkdownConverterContext): Promise<string>;
20
- private generateFrontmatter;
21
- private convertModule;
22
- private convertGenericComponent;
23
- private convertGenericCollection;
24
- private renderPreHeading;
25
- private renderPostHeading;
26
- private renderBody;
27
- private renderAdditionalCopy;
28
- private renderVisual;
29
- private renderLinks;
30
- }
31
-
32
- declare class MarkdownExporter {
33
- private config;
34
- private preview;
35
- constructor(config: ContentfulConfig, preview?: boolean);
36
- fetchContent(type: 'page' | 'article' | 'blogPost' | 'customType', slug: string, params?: {
37
- articleType?: string;
38
- }): Promise<ContentData | null>;
39
- private createContext;
40
- }
41
-
42
- export { type ContentData, type ConverterFunction, type ConverterResult, MarkdownConverter, type MarkdownConverterContext, MarkdownExporter };
1
+ export * from './MarkdownConverter';
2
+ export * from './MarkdownExporter';
3
+ export * from './types';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,333 +1,4 @@
1
- // src/MarkdownConverter.ts
2
- import yaml from "js-yaml";
3
-
4
- // src/utils/richTextConverter.ts
5
- import {
6
- BLOCKS,
7
- INLINES,
8
- MARKS
9
- } from "@contentful/rich-text-types";
10
- function convertRichTextToMarkdown(richText, context) {
11
- if (!richText) return "";
12
- const document = "json" in richText ? richText.json : richText;
13
- if (!document) return "";
14
- return convertNode(document, context);
15
- }
16
- function convertNode(node, context) {
17
- if (node.nodeType === "text") {
18
- return convertText(node);
19
- }
20
- const content = (node.content || []).map((child) => convertNode(child, context)).join("");
21
- switch (node.nodeType) {
22
- case BLOCKS.DOCUMENT:
23
- return content;
24
- case BLOCKS.PARAGRAPH:
25
- return `${content}
26
-
27
- `;
28
- case BLOCKS.HEADING_1:
29
- return `# ${content}
30
-
31
- `;
32
- case BLOCKS.HEADING_2:
33
- return `## ${content}
34
-
35
- `;
36
- case BLOCKS.HEADING_3:
37
- return `### ${content}
38
-
39
- `;
40
- case BLOCKS.HEADING_4:
41
- return `#### ${content}
42
-
43
- `;
44
- case BLOCKS.HEADING_5:
45
- return `##### ${content}
46
-
47
- `;
48
- case BLOCKS.HEADING_6:
49
- return `###### ${content}
50
-
51
- `;
52
- case BLOCKS.UL_LIST:
53
- return `${content}
54
- `;
55
- case BLOCKS.OL_LIST:
56
- return `${content}
57
- `;
58
- case BLOCKS.LIST_ITEM:
59
- return `- ${content.trim()}
60
- `;
61
- case BLOCKS.QUOTE:
62
- return `> ${content.replace(/\n/g, "\n> ")}
63
-
64
- `;
65
- case BLOCKS.HR:
66
- return `---
67
-
68
- `;
69
- case INLINES.HYPERLINK:
70
- return `[${content}](${node.data.uri})`;
71
- case INLINES.ENTRY_HYPERLINK:
72
- return `[${content}](#${node.data.target?.sys?.id || "entry-link"})`;
73
- case INLINES.ASSET_HYPERLINK:
74
- return `[${content}](${node.data.target?.fields?.file?.url || "#"})`;
75
- case BLOCKS.EMBEDDED_ASSET: {
76
- const title = node.data.target?.fields?.title || "Image";
77
- const url = node.data.target?.fields?.file?.url;
78
- return url ? `![${title}](${url})
79
-
80
- ` : "";
81
- }
82
- case BLOCKS.EMBEDDED_ENTRY:
83
- return `
84
-
85
- [Embedded Entry: ${node.data.target?.sys?.id}]
86
-
87
- `;
88
- default:
89
- return content;
90
- }
91
- }
92
- function convertText(node) {
93
- let value = node.value;
94
- if (node.marks) {
95
- node.marks.forEach((mark) => {
96
- switch (mark.type) {
97
- case MARKS.BOLD:
98
- value = `**${value}**`;
99
- break;
100
- case MARKS.ITALIC:
101
- value = `*${value}*`;
102
- break;
103
- case MARKS.CODE:
104
- value = `\`${value}\``;
105
- break;
106
- case MARKS.UNDERLINE:
107
- break;
108
- }
109
- });
110
- }
111
- return value;
112
- }
113
-
114
- // src/MarkdownConverter.ts
115
- var MarkdownConverter = class {
116
- async convert(contentData, context) {
117
- const { data, contentType } = contentData;
118
- const frontmatter = this.generateFrontmatter(data, contentType);
119
- let markdown = `---
120
- ${yaml.dump(frontmatter)}---
121
-
122
- `;
123
- if (data.title) {
124
- markdown += `# ${data.title}
125
-
126
- `;
127
- }
128
- if (data.description) {
129
- markdown += `> ${data.description}
130
-
131
- `;
132
- }
133
- if (data.contents) {
134
- for (const item of data.contents) {
135
- if (!item) continue;
136
- markdown += await this.convertModule(item, context);
137
- }
138
- }
139
- return markdown;
140
- }
141
- generateFrontmatter(data, type) {
142
- const slug = data.slug;
143
- return {
144
- contentType: type,
145
- title: data.title,
146
- slug,
147
- id: data.id,
148
- description: data.description,
149
- // Add more specific fields for Article if needed (date, author, etc)
150
- ...type === "article" ? {
151
- date: data.date,
152
- articleType: data.articleType?.name
153
- } : {}
154
- };
155
- }
156
- async convertModule(item, context) {
157
- if (item.type === "Component") {
158
- const component = item;
159
- return this.convertGenericComponent(component, context);
160
- }
161
- if (item.type === "Collection") {
162
- const collection = item;
163
- return this.convertGenericCollection(collection, context);
164
- }
165
- return "";
166
- }
167
- convertGenericComponent(component, context) {
168
- let md = "";
169
- if (component.heading) {
170
- md += `## ${component.heading}
171
-
172
- `;
173
- } else if (component.componentType) {
174
- md += `## ${component.componentType}
175
-
176
- `;
177
- }
178
- md += this.renderPreHeading(component.preHeading);
179
- md += this.renderPostHeading(component.postHeading);
180
- md += this.renderBody(component.body, context);
181
- md += this.renderAdditionalCopy(component.additionalCopy, context);
182
- md += this.renderVisual(component.visual);
183
- md += this.renderLinks(component.links);
184
- return md;
185
- }
186
- async convertGenericCollection(collection, context) {
187
- let md = `## ${collection.heading || collection.collectionType || "Collection"}
188
-
189
- `;
190
- md += this.renderPreHeading(collection.preHeading);
191
- md += this.renderPostHeading(collection.postHeading);
192
- md += this.renderBody(collection.body, context);
193
- md += this.renderLinks(collection.links);
194
- if (collection.contents) {
195
- for (const item of collection.contents) {
196
- if (!item) continue;
197
- md += await this.convertModule(item, context);
198
- }
199
- }
200
- md += this.renderAdditionalCopy(collection.additionalCopy, context);
201
- md += this.renderVisual(collection.visual);
202
- return md;
203
- }
204
- renderPreHeading(preHeading) {
205
- return preHeading ? `**${preHeading}**
206
-
207
- ` : "";
208
- }
209
- renderPostHeading(postHeading) {
210
- return postHeading ? `*${postHeading}*
211
-
212
- ` : "";
213
- }
214
- // biome-ignore lint/suspicious/noExplicitAny: generic content body
215
- renderBody(body, context) {
216
- return body ? convertRichTextToMarkdown(body, context) : "";
217
- }
218
- // biome-ignore lint/suspicious/noExplicitAny: generic additional copy
219
- renderAdditionalCopy(additionalCopy, context) {
220
- return additionalCopy ? `
221
-
222
- ${convertRichTextToMarkdown(additionalCopy, context)}` : "";
223
- }
224
- renderVisual(visual) {
225
- const image = visual?.visual?.image;
226
- if (!image) return "";
227
- const title = image.name || "Image";
228
- const url = image.src || image.svgSrc;
229
- return url ? `![${title}](${url})
230
-
231
- ` : "";
232
- }
233
- renderLinks(links) {
234
- if (!links?.length) return "";
235
- let md = "\n";
236
- for (const link of links) {
237
- md += `- [${link.text || link.name}](${link.href || "#"})
238
- `;
239
- }
240
- return `${md}
241
- `;
242
- }
243
- };
244
-
245
- // src/MarkdownExporter.ts
246
- import {
247
- contentfulArticleRest,
248
- contentfulCustomTypeRest,
249
- contentfulPageRest,
250
- createBaseConverterContext
251
- } from "@se-studio/contentful-rest-api";
252
- var MarkdownExporter = class {
253
- config;
254
- preview;
255
- constructor(config, preview = false) {
256
- this.config = config;
257
- this.preview = preview;
258
- }
259
- async fetchContent(type, slug, params) {
260
- const context = this.createContext();
261
- if (type === "page") {
262
- const response = await contentfulPageRest(context, this.config, slug, {
263
- preview: this.preview
264
- });
265
- if (!response.data) return null;
266
- const page = response.data;
267
- return {
268
- contentType: "page",
269
- data: page,
270
- context: {
271
- pageContext: {
272
- page
273
- },
274
- current: { type: "Page", id: page.id }
275
- }
276
- };
277
- }
278
- if (type === "article" || type === "blogPost") {
279
- const articleType = params?.articleType || "blog";
280
- const response = await contentfulArticleRest(context, this.config, slug, articleType, {
281
- preview: this.preview
282
- });
283
- if (!response.data) return null;
284
- const article = response.data;
285
- return {
286
- contentType: "article",
287
- data: article,
288
- context: {
289
- pageContext: {
290
- article,
291
- articleType: article.articleType
292
- },
293
- current: { type: "Article", id: article.id }
294
- }
295
- };
296
- }
297
- if (type === "customType") {
298
- const response = await contentfulCustomTypeRest(context, this.config, slug, {
299
- preview: this.preview
300
- });
301
- if (!response.data) return null;
302
- const customType = response.data;
303
- return {
304
- contentType: "customType",
305
- data: customType,
306
- context: {
307
- pageContext: {
308
- customType
309
- },
310
- current: { type: "CustomType", id: customType.id }
311
- }
312
- };
313
- }
314
- return null;
315
- }
316
- createContext() {
317
- const urlCalculators = {
318
- page: (slug) => `/${slug}`,
319
- pageVariant: (slug) => `/${slug}`,
320
- article: (type, slug) => `/${type}/${slug}`,
321
- articleType: (slug) => `/${slug}`,
322
- tag: (slug) => `/topics/${slug}`,
323
- person: (slug) => `/team/${slug}`,
324
- customType: (slug) => `/${slug}`
325
- };
326
- return createBaseConverterContext(urlCalculators);
327
- }
328
- };
329
- export {
330
- MarkdownConverter,
331
- MarkdownExporter
332
- };
1
+ export * from './MarkdownConverter';
2
+ export * from './MarkdownExporter';
3
+ export * from './types';
333
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/MarkdownConverter.ts","../src/utils/richTextConverter.ts","../src/MarkdownExporter.ts"],"sourcesContent":["import type { IContentfulCollection, IContentfulComponent } from '@se-studio/contentful-rest-api';\nimport type {\n IBaseArticle,\n IBaseCustomType,\n IBasePage,\n IPicture,\n IResponsiveVisual,\n ISvgImage,\n ITyped,\n} from '@se-studio/core-data-types';\nimport yaml from 'js-yaml';\nimport type { ContentData, MarkdownConverterContext } from './types';\nimport { convertRichTextToMarkdown } from './utils/richTextConverter';\n\nexport class MarkdownConverter {\n async convert(contentData: ContentData, context: MarkdownConverterContext): Promise<string> {\n const { data, contentType } = contentData;\n const frontmatter = this.generateFrontmatter(data, contentType);\n\n let markdown = `---\\n${yaml.dump(frontmatter)}---\\n\\n`;\n\n // Add Title and basic body\n if (data.title) {\n markdown += `# ${data.title}\\n\\n`;\n }\n\n if (data.description) {\n markdown += `> ${data.description}\\n\\n`;\n }\n\n // Page modules / contents\n if (data.contents) {\n for (const item of data.contents) {\n if (!item) continue;\n markdown += await this.convertModule(item, context);\n }\n }\n\n return markdown;\n }\n\n private generateFrontmatter(data: IBasePage | IBaseArticle | IBaseCustomType, type: string) {\n const slug = data.slug;\n return {\n contentType: type,\n title: data.title,\n slug: slug,\n id: data.id,\n description: data.description,\n // Add more specific fields for Article if needed (date, author, etc)\n ...(type === 'article'\n ? {\n date: (data as IBaseArticle).date,\n articleType: (data as IBaseArticle).articleType?.name,\n }\n : {}),\n };\n }\n\n private async convertModule(item: ITyped, context: MarkdownConverterContext): Promise<string> {\n if (item.type === 'Component') {\n const component = item as IContentfulComponent & { componentType: string };\n // Use generic fallback for all components\n return this.convertGenericComponent(component, context);\n }\n\n if (item.type === 'Collection') {\n const collection = item as IContentfulCollection & { collectionType: string };\n // Use generic fallback for all collections\n return this.convertGenericCollection(collection, context);\n }\n\n return '';\n }\n\n private convertGenericComponent(\n component: IContentfulComponent & { componentType: string },\n context: MarkdownConverterContext,\n ): string {\n let md = '';\n\n // Add heading hierarchy\n if (component.heading) {\n md += `## ${component.heading}\\n\\n`;\n } else if (component.componentType) {\n // Fallback to component type if no heading, but NOT cmsLabel\n md += `## ${component.componentType}\\n\\n`;\n }\n\n md += this.renderPreHeading(component.preHeading);\n md += this.renderPostHeading(component.postHeading);\n md += this.renderBody(component.body, context);\n md += this.renderAdditionalCopy(component.additionalCopy, context);\n md += this.renderVisual(component.visual);\n md += this.renderLinks(component.links);\n\n return md;\n }\n\n private async convertGenericCollection(\n collection: IContentfulCollection & { collectionType: string },\n context: MarkdownConverterContext,\n ): Promise<string> {\n let md = `## ${collection.heading || collection.collectionType || 'Collection'}\\n\\n`;\n\n md += this.renderPreHeading(collection.preHeading);\n md += this.renderPostHeading(collection.postHeading);\n md += this.renderBody(collection.body, context);\n md += this.renderLinks(collection.links);\n\n if (collection.contents) {\n for (const item of collection.contents) {\n if (!item) continue;\n md += await this.convertModule(item, context);\n }\n }\n\n md += this.renderAdditionalCopy(collection.additionalCopy, context);\n md += this.renderVisual(collection.visual);\n\n return md;\n }\n\n private renderPreHeading(preHeading: string | null | undefined): string {\n return preHeading ? `**${preHeading}**\\n\\n` : '';\n }\n\n private renderPostHeading(postHeading: string | null | undefined): string {\n return postHeading ? `*${postHeading}*\\n\\n` : '';\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: generic content body\n private renderBody(body: any, context: MarkdownConverterContext): string {\n return body ? convertRichTextToMarkdown(body, context) : '';\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: generic additional copy\n private renderAdditionalCopy(additionalCopy: any, context: MarkdownConverterContext): string {\n return additionalCopy ? `\\n\\n${convertRichTextToMarkdown(additionalCopy, context)}` : '';\n }\n\n private renderVisual(visual: IResponsiveVisual | undefined): string {\n const image = visual?.visual?.image;\n if (!image) return '';\n const title = image.name || 'Image';\n const url = (image as IPicture).src || (image as ISvgImage).svgSrc;\n return url ? `![${title}](${url})\\n\\n` : '';\n }\n\n private renderLinks(\n links:\n | readonly { text?: string | null; name?: string | null; href?: string | null }[]\n | undefined,\n ): string {\n if (!links?.length) return '';\n let md = '\\n';\n for (const link of links) {\n md += `- [${link.text || link.name}](${link.href || '#'}) \\n`;\n }\n return `${md}\\n`;\n }\n}\n","import {\n BLOCKS,\n type Block,\n type Document,\n INLINES,\n type Inline,\n MARKS,\n type Text,\n} from '@contentful/rich-text-types';\nimport type { MarkdownConverterContext } from '../types';\n\nexport function convertRichTextToMarkdown(\n richText: Document | { json: Document } | null | undefined,\n context: MarkdownConverterContext,\n): string {\n if (!richText) return '';\n\n // Handle IContentfulRichText wrapper\n const document = 'json' in richText ? richText.json : richText;\n if (!document) return '';\n\n return convertNode(document, context);\n}\n\nfunction convertNode(\n node: Block | Inline | Text | Document,\n context: MarkdownConverterContext,\n): string {\n if (node.nodeType === 'text') {\n return convertText(node as Text);\n }\n\n const content = ((node as Block | Inline | Document).content || [])\n .map((child) => convertNode(child, context))\n .join('');\n\n switch (node.nodeType) {\n case BLOCKS.DOCUMENT:\n return content;\n case BLOCKS.PARAGRAPH:\n return `${content}\\n\\n`;\n case BLOCKS.HEADING_1:\n return `# ${content}\\n\\n`;\n case BLOCKS.HEADING_2:\n return `## ${content}\\n\\n`;\n case BLOCKS.HEADING_3:\n return `### ${content}\\n\\n`;\n case BLOCKS.HEADING_4:\n return `#### ${content}\\n\\n`;\n case BLOCKS.HEADING_5:\n return `##### ${content}\\n\\n`;\n case BLOCKS.HEADING_6:\n return `###### ${content}\\n\\n`;\n case BLOCKS.UL_LIST:\n return `${content}\\n`;\n case BLOCKS.OL_LIST:\n return `${content}\\n`;\n case BLOCKS.LIST_ITEM:\n // This is a simplification. Proper nested list handling requires tracking depth.\n // For now, assuming direct child of list.\n // The parent UL/OL doesn't output anything, just its children.\n // But we need to know if it's UL or OL to prefix with - or 1.\n // Since we don't have parent context here easily without passing state,\n // we might need to adjust the structure or accept standard bullet for now.\n return `- ${content.trim()}\\n`;\n case BLOCKS.QUOTE:\n return `> ${content.replace(/\\n/g, '\\n> ')}\\n\\n`;\n case BLOCKS.HR:\n return `---\\n\\n`;\n case INLINES.HYPERLINK:\n return `[${content}](${node.data.uri})`;\n case INLINES.ENTRY_HYPERLINK:\n // TODO: Resolve entry link using context if possible\n // For now, falling back to a placeholder or just the content\n return `[${content}](#${node.data.target?.sys?.id || 'entry-link'})`;\n case INLINES.ASSET_HYPERLINK:\n return `[${content}](${node.data.target?.fields?.file?.url || '#'})`;\n case BLOCKS.EMBEDDED_ASSET: {\n const title = node.data.target?.fields?.title || 'Image';\n const url = node.data.target?.fields?.file?.url;\n return url ? `![${title}](${url})\\n\\n` : '';\n }\n case BLOCKS.EMBEDDED_ENTRY:\n return `\\n\\n[Embedded Entry: ${node.data.target?.sys?.id}]\\n\\n`;\n default:\n return content;\n }\n}\n\nfunction convertText(node: Text): string {\n let value = node.value;\n\n if (node.marks) {\n node.marks.forEach((mark) => {\n switch (mark.type) {\n case MARKS.BOLD:\n value = `**${value}**`;\n break;\n case MARKS.ITALIC:\n value = `*${value}*`;\n break;\n case MARKS.CODE:\n value = `\\`${value}\\``;\n break;\n case MARKS.UNDERLINE:\n // Markdown doesn't support underline natively, usually ignored or HTML\n break;\n }\n });\n }\n return value;\n}\n","import {\n type ContentfulConfig,\n contentfulArticleRest,\n contentfulCustomTypeRest,\n contentfulPageRest,\n createBaseConverterContext,\n type UrlCalculators,\n} from '@se-studio/contentful-rest-api';\nimport type { IContentContext } from '@se-studio/core-data-types';\nimport type { ContentData } from './types';\n\nexport class MarkdownExporter {\n private config: ContentfulConfig;\n private preview: boolean;\n\n constructor(config: ContentfulConfig, preview = false) {\n this.config = config;\n this.preview = preview;\n }\n\n async fetchContent(\n type: 'page' | 'article' | 'blogPost' | 'customType',\n slug: string,\n params?: { articleType?: string },\n ): Promise<ContentData | null> {\n const context = this.createContext();\n\n if (type === 'page') {\n const response = await contentfulPageRest(context, this.config, slug, {\n preview: this.preview,\n });\n\n if (!response.data) return null;\n\n const page = response.data;\n return {\n contentType: 'page',\n data: page,\n context: {\n pageContext: {\n page,\n },\n current: { type: 'Page', id: page.id },\n } as IContentContext,\n };\n }\n\n if (type === 'article' || type === 'blogPost') {\n const articleType = params?.articleType || 'blog';\n const response = await contentfulArticleRest(context, this.config, slug, articleType, {\n preview: this.preview,\n });\n\n if (!response.data) return null;\n\n const article = response.data;\n return {\n contentType: 'article',\n data: article,\n context: {\n pageContext: {\n article,\n articleType: article.articleType,\n },\n current: { type: 'Article', id: article.id },\n } as IContentContext,\n };\n }\n\n if (type === 'customType') {\n const response = await contentfulCustomTypeRest(context, this.config, slug, {\n preview: this.preview,\n });\n\n if (!response.data) return null;\n\n const customType = response.data;\n return {\n contentType: 'customType',\n data: customType,\n context: {\n pageContext: {\n customType,\n },\n current: { type: 'CustomType', id: customType.id },\n } as IContentContext,\n };\n }\n\n return null;\n }\n\n private createContext() {\n // Basic context for fetching - we might need to make this configurable if URL calculation differs per app\n // For now using simple defaults that produce relative paths\n const urlCalculators: UrlCalculators = {\n page: (slug) => `/${slug}`,\n pageVariant: (slug) => `/${slug}`,\n article: (type, slug) => `/${type}/${slug}`,\n articleType: (slug) => `/${slug}`,\n tag: (slug) => `/topics/${slug}`,\n person: (slug) => `/team/${slug}`,\n customType: (slug) => `/${slug}`,\n };\n\n return createBaseConverterContext(urlCalculators);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,SAAS,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { ContentfulConfig } from '@se-studio/contentful-rest-api';
2
+ import type { IBaseArticle, IBaseCustomType, IBasePage, IContentContext } from '@se-studio/core-data-types';
3
+ export interface ContentData {
4
+ contentType: 'page' | 'article' | 'customType';
5
+ data: IBasePage | IBaseArticle | IBaseCustomType;
6
+ context: IContentContext;
7
+ }
8
+ export interface MarkdownConverterContext {
9
+ contentContext: IContentContext;
10
+ config: ContentfulConfig;
11
+ }
12
+ export interface ConverterResult {
13
+ markdown: string;
14
+ }
15
+ export type ConverterFunction<T = any> = (data: T, context: MarkdownConverterContext) => Promise<string> | string;
16
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EACf,SAAS,EACT,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;IAC/C,IAAI,EAAE,SAAS,GAAG,YAAY,GAAG,eAAe,CAAC;IACjD,OAAO,EAAE,eAAe,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,eAAe,CAAC;IAChC,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,iBAAiB,CAAC,CAAC,GAAG,GAAG,IAAI,CACvC,IAAI,EAAE,CAAC,EACP,OAAO,EAAE,wBAAwB,KAC9B,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import { type Document } from '@contentful/rich-text-types';
2
+ import type { MarkdownConverterContext } from '../types';
3
+ export declare function convertRichTextToMarkdown(richText: Document | {
4
+ json: Document;
5
+ } | null | undefined, context: MarkdownConverterContext): string;
6
+ //# sourceMappingURL=richTextConverter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"richTextConverter.d.ts","sourceRoot":"","sources":["../../src/utils/richTextConverter.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EAKd,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAEzD,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,QAAQ,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EAC1D,OAAO,EAAE,wBAAwB,GAChC,MAAM,CAQR"}
@@ -0,0 +1,92 @@
1
+ import { BLOCKS, INLINES, MARKS, } from '@contentful/rich-text-types';
2
+ export function convertRichTextToMarkdown(richText, context) {
3
+ if (!richText)
4
+ return '';
5
+ // Handle IContentfulRichText wrapper
6
+ const document = 'json' in richText ? richText.json : richText;
7
+ if (!document)
8
+ return '';
9
+ return convertNode(document, context);
10
+ }
11
+ function convertNode(node, context) {
12
+ if (node.nodeType === 'text') {
13
+ return convertText(node);
14
+ }
15
+ const content = (node.content || [])
16
+ .map((child) => convertNode(child, context))
17
+ .join('');
18
+ switch (node.nodeType) {
19
+ case BLOCKS.DOCUMENT:
20
+ return content;
21
+ case BLOCKS.PARAGRAPH:
22
+ return `${content}\n\n`;
23
+ case BLOCKS.HEADING_1:
24
+ return `# ${content}\n\n`;
25
+ case BLOCKS.HEADING_2:
26
+ return `## ${content}\n\n`;
27
+ case BLOCKS.HEADING_3:
28
+ return `### ${content}\n\n`;
29
+ case BLOCKS.HEADING_4:
30
+ return `#### ${content}\n\n`;
31
+ case BLOCKS.HEADING_5:
32
+ return `##### ${content}\n\n`;
33
+ case BLOCKS.HEADING_6:
34
+ return `###### ${content}\n\n`;
35
+ case BLOCKS.UL_LIST:
36
+ return `${content}\n`;
37
+ case BLOCKS.OL_LIST:
38
+ return `${content}\n`;
39
+ case BLOCKS.LIST_ITEM:
40
+ // This is a simplification. Proper nested list handling requires tracking depth.
41
+ // For now, assuming direct child of list.
42
+ // The parent UL/OL doesn't output anything, just its children.
43
+ // But we need to know if it's UL or OL to prefix with - or 1.
44
+ // Since we don't have parent context here easily without passing state,
45
+ // we might need to adjust the structure or accept standard bullet for now.
46
+ return `- ${content.trim()}\n`;
47
+ case BLOCKS.QUOTE:
48
+ return `> ${content.replace(/\n/g, '\n> ')}\n\n`;
49
+ case BLOCKS.HR:
50
+ return `---\n\n`;
51
+ case INLINES.HYPERLINK:
52
+ return `[${content}](${node.data.uri})`;
53
+ case INLINES.ENTRY_HYPERLINK:
54
+ // TODO: Resolve entry link using context if possible
55
+ // For now, falling back to a placeholder or just the content
56
+ return `[${content}](#${node.data.target?.sys?.id || 'entry-link'})`;
57
+ case INLINES.ASSET_HYPERLINK:
58
+ return `[${content}](${node.data.target?.fields?.file?.url || '#'})`;
59
+ case BLOCKS.EMBEDDED_ASSET: {
60
+ const title = node.data.target?.fields?.title || 'Image';
61
+ const url = node.data.target?.fields?.file?.url;
62
+ return url ? `![${title}](${url})\n\n` : '';
63
+ }
64
+ case BLOCKS.EMBEDDED_ENTRY:
65
+ return `\n\n[Embedded Entry: ${node.data.target?.sys?.id}]\n\n`;
66
+ default:
67
+ return content;
68
+ }
69
+ }
70
+ function convertText(node) {
71
+ let value = node.value;
72
+ if (node.marks) {
73
+ node.marks.forEach((mark) => {
74
+ switch (mark.type) {
75
+ case MARKS.BOLD:
76
+ value = `**${value}**`;
77
+ break;
78
+ case MARKS.ITALIC:
79
+ value = `*${value}*`;
80
+ break;
81
+ case MARKS.CODE:
82
+ value = `\`${value}\``;
83
+ break;
84
+ case MARKS.UNDERLINE:
85
+ // Markdown doesn't support underline natively, usually ignored or HTML
86
+ break;
87
+ }
88
+ });
89
+ }
90
+ return value;
91
+ }
92
+ //# sourceMappingURL=richTextConverter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"richTextConverter.js","sourceRoot":"","sources":["../../src/utils/richTextConverter.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@se-studio/markdown-renderer",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "Markdown renderer for Contentful content",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,24 +18,25 @@
18
18
  }
19
19
  },
20
20
  "files": [
21
- "dist"
21
+ "dist",
22
+ "*.md"
22
23
  ],
23
24
  "dependencies": {
24
25
  "@contentful/rich-text-types": "^17.2.5",
25
26
  "js-yaml": "^4.1.1",
26
- "@se-studio/contentful-rest-api": "1.0.46",
27
- "@se-studio/core-data-types": "1.0.46"
27
+ "@se-studio/contentful-rest-api": "1.0.47",
28
+ "@se-studio/core-data-types": "1.0.47"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@biomejs/biome": "^2.3.10",
31
32
  "@types/js-yaml": "^4.0.9",
32
- "@types/node": "^24.10.4",
33
+ "@types/node": "^22.19.3",
33
34
  "tsup": "^8.5.1",
34
35
  "typescript": "^5.9.3"
35
36
  },
36
37
  "scripts": {
37
- "build": "tsup",
38
- "dev": "tsup --watch --no-clean",
38
+ "build": "tsc --project tsconfig.build.json",
39
+ "dev": "tsc --project tsconfig.build.json --watch",
39
40
  "type-check": "tsc --noEmit",
40
41
  "lint": "biome lint .",
41
42
  "clean": "rm -rf dist .turbo *.tsbuildinfo"