diff --git a/markdown-to-richtext-documentation.md b/markdown-to-richtext-documentation.md new file mode 100644 index 000000000..b8eee8dfa --- /dev/null +++ b/markdown-to-richtext-documentation.md @@ -0,0 +1,281 @@ +# Markdown to Storyblok Richtext Conversion + +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`. + +## Installation + +The markdown-to-richtext functionality is included in the `@storyblok/richtext` package: + +```bash +npm install @storyblok/richtext@latest +``` + +## Basic Usage + +### Simple Conversion + +```typescript +import { markdownToStoryblokRichtext } from '@storyblok/richtext'; + +const markdown = ` +# Main Heading + +This is a **bold** paragraph with *italic* text and [a link](https://example.com). + +- List item 1 +- List item 2 + +> This is a blockquote +`; + +const richtextDoc = markdownToStoryblokRichtext(markdown); +console.log(richtextDoc); +``` + +### Using with richTextResolver + +```typescript +import { markdownToStoryblokRichtext, richTextResolver } from '@storyblok/richtext'; + +const markdown = '# Hello World\nThis is a paragraph with [a link](https://storyblok.com).'; +const richtextDoc = markdownToStoryblokRichtext(markdown); + +// Convert to HTML using the existing richTextResolver +const html = richTextResolver().render(richtextDoc); +document.getElementById('content').innerHTML = html; +``` + +## Supported Markdown Elements + +The markdown parser supports all standard Markdown elements: + +- **Text formatting**: `**bold**`, `*italic*`, `~~strikethrough~~`, `` `code` `` +- **Links**: `[text](url)` and `[text](url "title")` +- **Headings**: `# H1` through `###### H6` +- **Lists**: `- unordered` and `1. ordered` lists with nesting +- **Code blocks**: ``` ```fenced``` ``` and indented blocks +- **Blockquotes**: `> quoted text` +- **Images**: `![alt](src "title")` +- **Tables**: Standard markdown table syntax +- **Horizontal rules**: `---` +- **Line breaks**: ` ` (two spaces) for hard breaks + +## Custom Resolvers + +You can customize how specific Markdown elements are converted by providing custom resolvers: + +```typescript +import { markdownToStoryblokRichtext, MarkdownTokenTypes, BlockTypes } from '@storyblok/richtext'; + +const markdown = '# Custom Heading\nThis is a paragraph with [a link](https://example.com).'; + +const richtextDoc = markdownToStoryblokRichtext(markdown, { + resolvers: { + // Custom heading resolver + [MarkdownTokenTypes.HEADING]: (token, children) => { + const level = Number(token.tag.replace('h', '')); + return { + type: BlockTypes.HEADING, + attrs: { + level, + custom: true // Add custom attributes + }, + content: children, + }; + }, + + // Custom link resolver + [MarkdownTokenTypes.LINK]: (token, children) => { + return { + type: 'link', + attrs: { + href: token.attrGet('href'), + title: token.attrGet('title') || null, + target: '_blank', // Always open in new tab + }, + content: children, + }; + }, + }, +}); +``` + +## Advanced Examples + +### Converting Markdown Files + +```typescript +import { markdownToStoryblokRichtext, richTextResolver } from '@storyblok/richtext'; + +// Read markdown file (in a browser environment) +const response = await fetch('/content/article.md'); +const markdown = await response.text(); + +// Convert to Storyblok richtext +const richtextDoc = markdownToStoryblokRichtext(markdown); + +// Render to HTML with image optimization +const html = richTextResolver({ + optimizeImages: { + width: 800, + height: 600, + filters: { + format: 'webp', + quality: 80, + }, + }, +}).render(richtextDoc); +``` + +### Framework Integration + +#### React + +```typescript +import React from 'react'; +import { markdownToStoryblokRichtext, richTextResolver } from '@storyblok/richtext'; + +const MarkdownRenderer = ({ markdown }: { markdown: string }) => { + const richtextDoc = markdownToStoryblokRichtext(markdown); + + const options = { + renderFn: React.createElement, + keyedResolvers: true, + }; + + const html = richTextResolver(options).render(richtextDoc); + + return
; +}; +``` + +#### Vue + +```vue + + + +``` + +## API Reference + +### `markdownToStoryblokRichtext(markdown, options?)` + +Converts a Markdown string to a Storyblok Rich Text document node. + +**Parameters:** +- `markdown` (string): The Markdown content to convert +- `options` (MarkdownParserOptions, optional): Configuration options + +**Returns:** +- `StoryblokRichTextDocumentNode`: A Storyblok-compatible rich text document + +### `MarkdownParserOptions` + +```typescript +interface MarkdownParserOptions { + resolvers?: Partial>; +} +``` + +### `MarkdownNodeResolver` + +```typescript +type MarkdownNodeResolver = ( + token: MarkdownToken, + children: StoryblokRichTextDocumentNode[] | undefined +) => StoryblokRichTextDocumentNode | null; +``` + +### `MarkdownTokenTypes` + +Constants for supported Markdown token types: + +```typescript +export const MarkdownTokenTypes = { + HEADING: 'heading_open', + PARAGRAPH: 'paragraph_open', + TEXT: 'text', + STRONG: 'strong_open', + EMP: 'em_open', + ORDERED_LIST: 'ordered_list_open', + BULLET_LIST: 'bullet_list_open', + LIST_ITEM: 'list_item_open', + IMAGE: 'image', + BLOCKQUOTE: 'blockquote_open', + CODE_INLINE: 'code_inline', + CODE_BLOCK: 'code_block', + FENCE: 'fence', + LINK: 'link_open', + HR: 'hr', + DEL: 'del_open', + HARD_BREAK: 'hardbreak', + SOFT_BREAK: 'softbreak', + TABLE: 'table_open', + THEAD: 'thead_open', + TBODY: 'tbody_open', + TR: 'tr_open', + TH: 'th_open', + TD: 'td_open', + S: 's_open', +} as const; +``` + +## Migration from Other Markdown Parsers + +If you're currently using other Markdown parsers, the `markdownToStoryblokRichtext` function provides a seamless migration path: + +```typescript +// Before: Using a different markdown parser +import marked from 'marked'; +const html = marked(markdown); + +// After: Using Storyblok's markdown-to-richtext +import { markdownToStoryblokRichtext, richTextResolver } from '@storyblok/richtext'; +const richtextDoc = markdownToStoryblokRichtext(markdown); +const html = richTextResolver().render(richtextDoc); +``` + +## Best Practices + +1. **Content Validation**: Always validate your Markdown content before conversion +2. **Custom Resolvers**: Use custom resolvers to maintain consistency with your existing content structure +3. **Image Optimization**: Leverage the `optimizeImages` option in `richTextResolver` for better performance +4. **Error Handling**: Wrap conversion in try-catch blocks for robust error handling + +```typescript +try { + const richtextDoc = markdownToStoryblokRichtext(markdown); + // Process the converted document +} catch (error) { + console.error('Failed to convert markdown:', error); + // Handle the error appropriately +} +``` + +## Browser Support + +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. + +## Contributing + +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). \ No newline at end of file diff --git a/packages/cli/src/api.ts b/packages/cli/src/api.ts index d5b110fd8..26edd1874 100644 --- a/packages/cli/src/api.ts +++ b/packages/cli/src/api.ts @@ -85,6 +85,7 @@ const createMapiClient = (options: ManagementApiClientOptions): MapiClient => { // Prepare request data for interceptor const requestData = { + url: `${state.url}/${path}`, path, method: fetchOptions?.method || 'GET', headers: { diff --git a/packages/cli/src/commands/create/index.ts b/packages/cli/src/commands/create/index.ts index 2d082a0b5..66d52457c 100644 --- a/packages/cli/src/commands/create/index.ts +++ b/packages/cli/src/commands/create/index.ts @@ -39,6 +39,12 @@ export const createCommand = program mapiClient({ token: password, region, + onRequest: (request) => { + console.log(request); + }, + onResponse: (response) => { + console.log(response); + }, }); const spinnerBlueprints = new Spinner({ @@ -127,8 +133,11 @@ export const createCommand = program const blueprintDomain = selectedBlueprint?.location || 'https://localhost:3000/'; createdSpace = await createSpace({ - name: toHumanReadable(projectName), - domain: blueprintDomain, + space: { + name: toHumanReadable(projectName), + domain: blueprintDomain, + }, + in_org: false, }); spinnerSpace.succeed(`Space "${chalk.hex(colorPalette.PRIMARY)(toHumanReadable(projectName))}" created successfully`); }