Overview
The README Editor example demonstrates how to build a specialized Markdown editor for creating README files. It includes section templates, live preview, and download functionality.Live Demo
Try the interactive README editor demo
Features
Section Templates
Pre-built sections for common README parts
Live Preview
Toggle between edit and preview modes
Markdown Export
Download as README.md with proper formatting
GitHub Style
Preview with GitHub-flavored Markdown styling
Implementation
README Editor Component
import { useMemo, useState } from 'react';
import YooptaEditor, { createYooptaEditor, Blocks } from '@yoopta/editor';
import { README_PLUGINS, README_MARKS } from './config';
import { Button } from '@/components/ui/button';
import { Download, Eye, FileText, Plus } from 'lucide-react';
function ReadmeEditor() {
const [previewMode, setPreviewMode] = useState(false);
const editor = useMemo(() => {
return createYooptaEditor({
plugins: README_PLUGINS,
marks: README_MARKS,
});
}, []);
const handleDownload = () => {
const markdown = editor.getMarkdown();
const blob = new Blob([markdown], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'README.md';
a.click();
URL.revokeObjectURL(url);
};
return (
<div className="h-screen flex flex-col">
{/* Toolbar */}
<div className="border-b p-4 flex justify-between items-center">
<div className="flex gap-2">
<SectionTemplates />
<Button
variant={previewMode ? 'default' : 'outline'}
onClick={() => setPreviewMode(!previewMode)}
>
<Eye className="h-4 w-4 mr-2" />
{previewMode ? 'Edit' : 'Preview'}
</Button>
</div>
<Button onClick={handleDownload}>
<Download className="h-4 w-4 mr-2" />
Download README.md
</Button>
</div>
{/* Editor/Preview Area */}
<div className="flex-1 overflow-auto">
{previewMode ? (
<MarkdownPreview markdown={editor.getMarkdown()} />
) : (
<div className="max-w-4xl mx-auto p-8">
<YooptaEditor
editor={editor}
placeholder="Start writing your README..."
/>
</div>
)}
</div>
</div>
);
}
Section Templates
Pre-built sections for common README parts:import { useYooptaEditor, Blocks } from '@yoopta/editor';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
} from '@/components/ui/dropdown-menu';
const README_SECTIONS = [
{
name: 'Installation',
icon: 'Package',
blocks: [
{ type: 'HeadingTwo', text: 'Installation' },
{ type: 'Code', language: 'bash', text: 'npm install package-name' },
],
},
{
name: 'Usage',
icon: 'BookOpen',
blocks: [
{ type: 'HeadingTwo', text: 'Usage' },
{ type: 'Paragraph', text: 'Here\'s a quick example:' },
{ type: 'Code', language: 'javascript', text: 'import Package from "package-name";' },
],
},
{
name: 'Features',
icon: 'Zap',
blocks: [
{ type: 'HeadingTwo', text: 'Features' },
{ type: 'BulletedList', items: ['Feature 1', 'Feature 2', 'Feature 3'] },
],
},
{
name: 'Contributing',
icon: 'Users',
blocks: [
{ type: 'HeadingTwo', text: 'Contributing' },
{ type: 'Paragraph', text: 'Contributions are welcome! Please feel free to submit a Pull Request.' },
],
},
{
name: 'License',
icon: 'FileText',
blocks: [
{ type: 'HeadingTwo', text: 'License' },
{ type: 'Paragraph', text: 'This project is licensed under the MIT License.' },
],
},
];
function SectionTemplates() {
const editor = useYooptaEditor();
const insertSection = (section: typeof README_SECTIONS[0]) => {
const currentPath = editor.path.current;
const lastBlock = Object.values(editor.getEditorValue()).pop();
const insertAt = lastBlock ? lastBlock.meta.order + 1 : 0;
section.blocks.forEach((block, index) => {
editor.insertBlock(block.type, {
at: insertAt + index,
elements: editor.y(block.type.toLowerCase(), {
children: [editor.y.text(block.text || '')],
}),
});
});
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<Plus className="h-4 w-4 mr-2" />
Add Section
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{README_SECTIONS.map((section) => (
<DropdownMenuItem
key={section.name}
onClick={() => insertSection(section)}
>
{section.name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
Plugin Configuration
import Paragraph from '@yoopta/paragraph';
import Headings from '@yoopta/headings';
import Lists from '@yoopta/lists';
import Blockquote from '@yoopta/blockquote';
import Code from '@yoopta/code';
import Table from '@yoopta/table';
import Divider from '@yoopta/divider';
import Image from '@yoopta/image';
import Link from '@yoopta/link';
import { Bold, Italic, Strike, CodeMark } from '@yoopta/marks';
export const README_PLUGINS = [
Paragraph,
Headings.HeadingOne,
Headings.HeadingTwo,
Headings.HeadingThree,
Lists.BulletedList,
Lists.NumberedList,
Lists.TodoList,
Blockquote,
Code.extend({
options: {
languages: ['javascript', 'typescript', 'bash', 'python', 'json', 'yaml'],
},
}),
Table,
Divider,
Image,
Link,
];
export const README_MARKS = [Bold, Italic, Strike, CodeMark];
Markdown Preview
import { useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import remarkGfm from 'remark-gfm';
function MarkdownPreview({ markdown }: { markdown: string }) {
return (
<div className="max-w-4xl mx-auto p-8">
<div className="prose prose-neutral max-w-none">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
style={vscDarkPlus}
language={match[1]}
PreTag="div"
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{markdown}
</ReactMarkdown>
</div>
</div>
);
}
Initial README Template
const INITIAL_README = {
'block-1': {
type: 'HeadingOne',
value: [{
type: 'heading-one',
children: [{ text: 'My Awesome Project' }],
}],
meta: { order: 0, depth: 0 },
},
'block-2': {
type: 'Paragraph',
value: [{
type: 'paragraph',
children: [
{ text: 'A brief description of what this project does and who it\'s for. Built with ' },
{ text: 'Yoopta Editor', bold: true },
{ text: ' - the most powerful rich-text editor.' },
],
}],
meta: { order: 1, depth: 0 },
},
'block-3': {
type: 'HeadingTwo',
value: [{
type: 'heading-two',
children: [{ text: 'Features' }],
}],
meta: { order: 2, depth: 0 },
},
'block-4': {
type: 'BulletedList',
value: [{
type: 'bulleted-list',
children: [
{ type: 'list-item', children: [{ text: 'Easy to use' }] },
{ type: 'list-item', children: [{ text: 'Fast and reliable' }] },
{ type: 'list-item', children: [{ text: 'Well documented' }] },
],
}],
meta: { order: 3, depth: 0 },
},
};
Badges Component
function BadgeInserter() {
const editor = useYooptaEditor();
const BADGES = [
{
name: 'Build Status',
markdown: '',
},
{
name: 'License',
markdown: '',
},
{
name: 'Version',
markdown: '',
},
];
const insertBadge = (badge: typeof BADGES[0]) => {
const currentBlock = Blocks.getBlock(editor, { id: editor.path.current?.blockId });
if (currentBlock) {
// Insert badge image
editor.insertBlock('Image', {
at: currentBlock.meta.order,
props: { src: badge.markdown, alt: badge.name },
});
}
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
Add Badge
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{BADGES.map((badge) => (
<DropdownMenuItem
key={badge.name}
onClick={() => insertBadge(badge)}
>
{badge.name}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
);
}
GitHub Flavored Markdown
The Markdown export supports:- Headings (
# H1,## H2,### H3) - Lists (bulleted, numbered, task lists)
- Code blocks with syntax highlighting
- Tables with alignment
- Blockquotes
- Emphasis (bold, italic, strikethrough)
- Links and Images
- Horizontal rules
Source Code
View Full Source
Complete README editor implementation on GitHub
Use Cases
Project Documentation
Create README files for GitHub projects
Technical Writing
Write technical documentation in Markdown
Blog Posts
Draft blog posts in Markdown format
Knowledge Base
Build documentation sites and wikis
Next Steps
Chat Applications
See messaging interface examples
Markdown Export
Learn about Markdown serialization