Skip to content

Commit 7fad13b

Browse files
committed
feat(docs): add markdown to Storyblok richtext conversion documentation
WIP TEST
1 parent 3a28562 commit 7fad13b

File tree

3 files changed

+293
-2
lines changed

3 files changed

+293
-2
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
# Markdown to Storyblok Richtext Conversion
2+
3+
The `@storyblok/richtext` package now includes a powerful utility to convert Markdown content to Storyblok's Rich Text format. This feature allows you to seamlessly transform Markdown documents into Storyblok-compatible rich text documents that can be used with the existing `richTextResolver`.
4+
5+
## Installation
6+
7+
The markdown-to-richtext functionality is included in the `@storyblok/richtext` package:
8+
9+
```bash
10+
npm install @storyblok/richtext@latest
11+
```
12+
13+
## Basic Usage
14+
15+
### Simple Conversion
16+
17+
```typescript
18+
import { markdownToStoryblokRichtext } from '@storyblok/richtext';
19+
20+
const markdown = `
21+
# Main Heading
22+
23+
This is a **bold** paragraph with *italic* text and [a link](https://example.com).
24+
25+
- List item 1
26+
- List item 2
27+
28+
> This is a blockquote
29+
`;
30+
31+
const richtextDoc = markdownToStoryblokRichtext(markdown);
32+
console.log(richtextDoc);
33+
```
34+
35+
### Using with richTextResolver
36+
37+
```typescript
38+
import { markdownToStoryblokRichtext, richTextResolver } from '@storyblok/richtext';
39+
40+
const markdown = '# Hello World\nThis is a paragraph with [a link](https://storyblok.com).';
41+
const richtextDoc = markdownToStoryblokRichtext(markdown);
42+
43+
// Convert to HTML using the existing richTextResolver
44+
const html = richTextResolver().render(richtextDoc);
45+
document.getElementById('content').innerHTML = html;
46+
```
47+
48+
## Supported Markdown Elements
49+
50+
The markdown parser supports all standard Markdown elements:
51+
52+
- **Text formatting**: `**bold**`, `*italic*`, `~~strikethrough~~`, `` `code` ``
53+
- **Links**: `[text](url)` and `[text](url "title")`
54+
- **Headings**: `# H1` through `###### H6`
55+
- **Lists**: `- unordered` and `1. ordered` lists with nesting
56+
- **Code blocks**: ``` ```fenced``` ``` and indented blocks
57+
- **Blockquotes**: `> quoted text`
58+
- **Images**: `![alt](src "title")`
59+
- **Tables**: Standard markdown table syntax
60+
- **Horizontal rules**: `---`
61+
- **Line breaks**: ` ` (two spaces) for hard breaks
62+
63+
## Custom Resolvers
64+
65+
You can customize how specific Markdown elements are converted by providing custom resolvers:
66+
67+
```typescript
68+
import { markdownToStoryblokRichtext, MarkdownTokenTypes, BlockTypes } from '@storyblok/richtext';
69+
70+
const markdown = '# Custom Heading\nThis is a paragraph with [a link](https://example.com).';
71+
72+
const richtextDoc = markdownToStoryblokRichtext(markdown, {
73+
resolvers: {
74+
// Custom heading resolver
75+
[MarkdownTokenTypes.HEADING]: (token, children) => {
76+
const level = Number(token.tag.replace('h', ''));
77+
return {
78+
type: BlockTypes.HEADING,
79+
attrs: {
80+
level,
81+
custom: true // Add custom attributes
82+
},
83+
content: children,
84+
};
85+
},
86+
87+
// Custom link resolver
88+
[MarkdownTokenTypes.LINK]: (token, children) => {
89+
return {
90+
type: 'link',
91+
attrs: {
92+
href: token.attrGet('href'),
93+
title: token.attrGet('title') || null,
94+
target: '_blank', // Always open in new tab
95+
},
96+
content: children,
97+
};
98+
},
99+
},
100+
});
101+
```
102+
103+
## Advanced Examples
104+
105+
### Converting Markdown Files
106+
107+
```typescript
108+
import { markdownToStoryblokRichtext, richTextResolver } from '@storyblok/richtext';
109+
110+
// Read markdown file (in a browser environment)
111+
const response = await fetch('/content/article.md');
112+
const markdown = await response.text();
113+
114+
// Convert to Storyblok richtext
115+
const richtextDoc = markdownToStoryblokRichtext(markdown);
116+
117+
// Render to HTML with image optimization
118+
const html = richTextResolver({
119+
optimizeImages: {
120+
width: 800,
121+
height: 600,
122+
filters: {
123+
format: 'webp',
124+
quality: 80,
125+
},
126+
},
127+
}).render(richtextDoc);
128+
```
129+
130+
### Framework Integration
131+
132+
#### React
133+
134+
```typescript
135+
import React from 'react';
136+
import { markdownToStoryblokRichtext, richTextResolver } from '@storyblok/richtext';
137+
138+
const MarkdownRenderer = ({ markdown }: { markdown: string }) => {
139+
const richtextDoc = markdownToStoryblokRichtext(markdown);
140+
141+
const options = {
142+
renderFn: React.createElement,
143+
keyedResolvers: true,
144+
};
145+
146+
const html = richTextResolver(options).render(richtextDoc);
147+
148+
return <div dangerouslySetInnerHTML={{ __html: html }} />;
149+
};
150+
```
151+
152+
#### Vue
153+
154+
```vue
155+
<script setup>
156+
import { markdownToStoryblokRichtext, richTextResolver } from '@storyblok/richtext';
157+
import { h, createTextVNode } from 'vue';
158+
159+
const props = defineProps<{
160+
markdown: string;
161+
}>();
162+
163+
const richtextDoc = markdownToStoryblokRichtext(props.markdown);
164+
165+
const options = {
166+
renderFn: h,
167+
textFn: createTextVNode,
168+
keyedResolvers: true,
169+
};
170+
171+
const root = () => richTextResolver(options).render(richtextDoc);
172+
</script>
173+
174+
<template>
175+
<root />
176+
</template>
177+
```
178+
179+
## API Reference
180+
181+
### `markdownToStoryblokRichtext(markdown, options?)`
182+
183+
Converts a Markdown string to a Storyblok Rich Text document node.
184+
185+
**Parameters:**
186+
- `markdown` (string): The Markdown content to convert
187+
- `options` (MarkdownParserOptions, optional): Configuration options
188+
189+
**Returns:**
190+
- `StoryblokRichTextDocumentNode`: A Storyblok-compatible rich text document
191+
192+
### `MarkdownParserOptions`
193+
194+
```typescript
195+
interface MarkdownParserOptions {
196+
resolvers?: Partial<Record<string, MarkdownNodeResolver>>;
197+
}
198+
```
199+
200+
### `MarkdownNodeResolver`
201+
202+
```typescript
203+
type MarkdownNodeResolver = (
204+
token: MarkdownToken,
205+
children: StoryblokRichTextDocumentNode[] | undefined
206+
) => StoryblokRichTextDocumentNode | null;
207+
```
208+
209+
### `MarkdownTokenTypes`
210+
211+
Constants for supported Markdown token types:
212+
213+
```typescript
214+
export const MarkdownTokenTypes = {
215+
HEADING: 'heading_open',
216+
PARAGRAPH: 'paragraph_open',
217+
TEXT: 'text',
218+
STRONG: 'strong_open',
219+
EMP: 'em_open',
220+
ORDERED_LIST: 'ordered_list_open',
221+
BULLET_LIST: 'bullet_list_open',
222+
LIST_ITEM: 'list_item_open',
223+
IMAGE: 'image',
224+
BLOCKQUOTE: 'blockquote_open',
225+
CODE_INLINE: 'code_inline',
226+
CODE_BLOCK: 'code_block',
227+
FENCE: 'fence',
228+
LINK: 'link_open',
229+
HR: 'hr',
230+
DEL: 'del_open',
231+
HARD_BREAK: 'hardbreak',
232+
SOFT_BREAK: 'softbreak',
233+
TABLE: 'table_open',
234+
THEAD: 'thead_open',
235+
TBODY: 'tbody_open',
236+
TR: 'tr_open',
237+
TH: 'th_open',
238+
TD: 'td_open',
239+
S: 's_open',
240+
} as const;
241+
```
242+
243+
## Migration from Other Markdown Parsers
244+
245+
If you're currently using other Markdown parsers, the `markdownToStoryblokRichtext` function provides a seamless migration path:
246+
247+
```typescript
248+
// Before: Using a different markdown parser
249+
import marked from 'marked';
250+
const html = marked(markdown);
251+
252+
// After: Using Storyblok's markdown-to-richtext
253+
import { markdownToStoryblokRichtext, richTextResolver } from '@storyblok/richtext';
254+
const richtextDoc = markdownToStoryblokRichtext(markdown);
255+
const html = richTextResolver().render(richtextDoc);
256+
```
257+
258+
## Best Practices
259+
260+
1. **Content Validation**: Always validate your Markdown content before conversion
261+
2. **Custom Resolvers**: Use custom resolvers to maintain consistency with your existing content structure
262+
3. **Image Optimization**: Leverage the `optimizeImages` option in `richTextResolver` for better performance
263+
4. **Error Handling**: Wrap conversion in try-catch blocks for robust error handling
264+
265+
```typescript
266+
try {
267+
const richtextDoc = markdownToStoryblokRichtext(markdown);
268+
// Process the converted document
269+
} catch (error) {
270+
console.error('Failed to convert markdown:', error);
271+
// Handle the error appropriately
272+
}
273+
```
274+
275+
## Browser Support
276+
277+
The markdown-to-richtext functionality works in all modern browsers and Node.js environments. It uses the `markdown-it` library internally, which provides excellent compatibility and performance.
278+
279+
## Contributing
280+
281+
The markdown-to-richtext functionality is part of the `@storyblok/richtext` package. For issues, feature requests, or contributions, please visit the [monoblok repository](https://github.com/storyblok/monoblok).

packages/cli/src/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ const createMapiClient = (options: ManagementApiClientOptions): MapiClient => {
8585

8686
// Prepare request data for interceptor
8787
const requestData = {
88+
url: `${state.url}/${path}`,
8889
path,
8990
method: fetchOptions?.method || 'GET',
9091
headers: {

packages/cli/src/commands/create/index.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ export const createCommand = program
3939
mapiClient({
4040
token: password,
4141
region,
42+
onRequest: (request) => {
43+
console.log(request);
44+
},
45+
onResponse: (response) => {
46+
console.log(response);
47+
},
4248
});
4349

4450
const spinnerBlueprints = new Spinner({
@@ -127,8 +133,11 @@ export const createCommand = program
127133
const blueprintDomain = selectedBlueprint?.location || 'https://localhost:3000/';
128134

129135
createdSpace = await createSpace({
130-
name: toHumanReadable(projectName),
131-
domain: blueprintDomain,
136+
space: {
137+
name: toHumanReadable(projectName),
138+
domain: blueprintDomain,
139+
},
140+
in_org: false,
132141
});
133142
spinnerSpace.succeed(`Space "${chalk.hex(colorPalette.PRIMARY)(toHumanReadable(projectName))}" created successfully`);
134143
}

0 commit comments

Comments
 (0)