MD2Card Blog System Development Complete Guide
MD2Card is a Next.js-based Markdown to knowledge card tool that supports multiple themes and internationalization. This article will detail the design and implementation of its blog system.
Core Features
1. Internationalization Support
MD2Card supports 8 languages through a custom LanguageContext:
// i18n/LanguageContext.js
import { createContext, useContext, useState } from 'react';
const LanguageContext = createContext();
export function LanguageProvider({ children }) {
const [language, setLanguage] = useState('en');
return (
<LanguageContext.Provider value={{ language, setLanguage }}>
{children}
</LanguageContext.Provider>
);
}
2. Theme System
The theme system supports 20+ beautiful themes, each with complete style configuration:
// themes/themes.js
export const themes = {
glassmorphism: {
background: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255, 255, 255, 0.2)',
// ... other styles
},
// ... other themes
};
3. Markdown Rendering
Secure Markdown rendering using marked and DOMPurify:
import { marked } from 'marked';
import DOMPurify from 'dompurify';
const renderMarkdown = (content) => {
const rawHtml = marked(content);
return DOMPurify.sanitize(rawHtml);
};
Technology Stack
Frontend Framework
- Next.js 13.x (Pages Router)
- Material-UI + Tailwind CSS
- React Hooks state management
Core Libraries
- marked + DOMPurify: Markdown parsing
- html-to-image: Image export
- framer-motion: Animation effects
Project Structure
md2card/
├── pages/ # Next.js page routing
├── components/ # React components
├── themes/ # Theme configuration
├── i18n/ # Internationalization
└── public/ # Static assets
Development Standards
Code Style
- Use functional components
- Prefer React Hooks
- Follow ESLint standards
- Use TypeScript type checking
Performance Optimization
- Implement code splitting
- Use React.memo for render optimization
- Image lazy loading
- Proper caching strategies
Deployment & Maintenance
Build Optimization
- Enable production optimizations
- Check bundle size
- Implement static export
- Configure environment variables
Monitoring & Maintenance
- Error boundary handling
- Performance monitoring
- User feedback collection
- Regular updates and maintenance
Future Plans
- Support more themes
- Add more languages
- Optimize mobile experience
- Add more export formats
Conclusion
The development of MD2Card's blog system fully demonstrates modern frontend development best practices. Through reasonable architecture design and standardized code organization, we've achieved a high-performance, maintainable blog system. We will continue to optimize and expand features to provide users with a better experience. --- id: 1 title: "MD2Card Blog System Development Complete Guide" excerpt: "A comprehensive guide to the design and implementation of MD2Card's blog system, including internationalization, theme system, and Markdown rendering." date: "2025-01-15" readTime: 12 category: "Development Practice" tags: ["Next.js", "Internationalization", "Theme System", "Markdown", "React"] image: "https://images.unsplash.com/photo-1555066931-4365d14bab8c?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80" author: name: "JsonChao" bio: "MD2Card Project Developer"
MD2Card Blog System Development Complete Guide
MD2Card is a Next.js-based innovative Markdown to knowledge card tool that supports multiple themes and complete internationalization. This article provides an in-depth analysis of the blog system's design philosophy and implementation details, offering comprehensive technical guidance for developers.
🎯 Project Overview
Core Objectives
- User Experience First: Clean and intuitive interface design that lets users focus on content creation
- Highly Scalable: Flexible architecture design supporting rapid addition of new features and themes
- Performance Excellence: Optimized loading speeds and smooth interactive experiences
- Global Support: Complete multi-language support covering global user needs
Key Features
- Rich Theme Library - Provides 20+ professionally designed theme templates
- Multi-language Support - Supports 8 mainstream languages including English, Chinese, Japanese
- Real-time Preview - WYSIWYG editing experience
- High-quality Export - Supports high-definition export in multiple formats like PNG, SVG
🏗️ Technical Architecture Deep Dive
Core Technology Stack
// Complete technology stack configuration
const techStack = {
// Frontend framework
framework: "Next.js 13.x (Pages Router)",
// UI framework
ui: {
components: "Material-UI (MUI)",
styling: "Tailwind CSS",
icons: "@mui/icons-material"
},
// State management
stateManagement: {
global: "React Context API",
local: "React Hooks (useState, useEffect)",
forms: "React Hook Form"
},
// Internationalization
i18n: {
core: "Custom LanguageContext",
storage: "localStorage + URL params"
},
// Content processing
content: {
parser: "marked",
sanitizer: "DOMPurify",
highlighter: "Prism.js"
},
// Image processing
imaging: {
export: "html-to-image",
optimization: "Canvas API"
},
// Animation
animation: "framer-motion",
// Deployment
deployment: {
platform: "Vercel/Netlify",
type: "Static Site Generation"
}
};
Project Architecture Diagram
md2card/
├── pages/ # Next.js page routing
│ ├── index.js # Main page - core feature integration
│ ├── blog.js # Blog listing page
│ ├── blog/
│ │ └── [slug].js # Blog detail page
│ ├── app.js # Global app configuration
│ └── document.js # HTML document structure
├── components/ # React component library
│ ├── Introduction.js # Product introduction component
│ ├── CardPreview.js # Card preview component
│ ├── ThemeSelector.js # Theme selector
│ ├── MarkdownEditor.js # Markdown editor
│ ├── ExportOptions.js # Export functionality component
│ ├── LanguageSwitcher.js # Language switcher
│ └── MarkdownGuide.js # Markdown syntax guide
├── themes/ # Theme system
│ └── themes.js # Theme configuration file
├── i18n/ # Internationalization system
│ ├── LanguageContext.js # Language context
│ └── translations.js # Translation resources
├── data/ # Data layer
│ ├── blog/ # Blog article data
│ └── getAllPosts.js # Data fetching utilities
└── public/ # Static assets
├── images/
└── icons/
🌍 Internationalization System Implementation
LanguageContext Core Design
// i18n/LanguageContext.js - Complete implementation
import { createContext, useContext, useState, useEffect, useCallback } from 'react';
import { translations } from './translations';
const LanguageContext = createContext();
export const LanguageProvider = ({ children }) => {
const [currentLang, setCurrentLang] = useState('en');
const [isLoading, setIsLoading] = useState(true);
// Language detection logic
useEffect(() => {
const detectLanguage = () => {
// 1. Check language code in URL path
const pathLang = window.location.pathname.split('/')[1];
if (translations[pathLang]) {
return pathLang;
}
// 2. Check user preference saved in localStorage
const savedLang = localStorage.getItem('preferred-language');
if (savedLang && translations[savedLang]) {
return savedLang;
}
// 3. Check browser language settings
const browserLang = navigator.language.split('-')[0];
if (translations[browserLang]) {
return browserLang;
}
// 4. Default to English
return 'en';
};
const detectedLang = detectLanguage();
setCurrentLang(detectedLang);
setIsLoading(false);
}, []);
// Translation function
const t = useCallback((key, params = {}) => {
const keys = key.split('.');
let value = translations[currentLang];
for (const k of keys) {
if (value && typeof value === 'object') {
value = value[k];
} else {
console.warn(`Translation key "${key}" not found for language "${currentLang}"`);
return key;
}
}
// Parameter replacement
if (typeof value === 'string' && Object.keys(params).length > 0) {
return value.replace(/\{\{(\w+)\}\}/g, (match, param) => {
return params[param] || match;
});
}
return value || key;
}, [currentLang]);
// Set language
const setLanguage = useCallback((lang) => {
if (translations[lang]) {
setCurrentLang(lang);
localStorage.setItem('preferred-language', lang);
// Update URL path
const currentPath = window.location.pathname;
const pathSegments = currentPath.split('/').filter(Boolean);
// If first segment is language code, replace it
if (pathSegments.length > 0 && translations[pathSegments[0]]) {
pathSegments[0] = lang;
} else {
pathSegments.unshift(lang);
}
const newPath = '/' + pathSegments.join('/');
window.history.pushState({}, '', newPath);
}
}, []);
// Get supported languages list
const getSupportedLanguages = useCallback(() => {
return Object.keys(translations).map(code => ({
code,
name: translations[code].languageName,
nativeName: translations[code].languageNativeName
}));
}, []);
const contextValue = {
currentLang,
setLanguage,
t,
isLoading,
getSupportedLanguages
};
return (
<LanguageContext.Provider value={contextValue}>
{children}
</LanguageContext.Provider>
);
};
// Custom Hook
export const useLanguage = () => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};
Translation Resource Management
// i18n/translations.js - Complete translation configuration
export const translations = {
en: {
// Language info
languageName: 'English',
languageNativeName: 'English',
// App basic info
appName: 'MD2Card',
appDescription: 'Transform Markdown into beautiful knowledge cards',
// Navigation menu
navigation: {
home: 'Home',
blog: 'Blog',
about: 'About',
contact: 'Contact'
},
// Home content
home: {
hero: {
title: 'Transform Markdown into Beautiful Cards',
subtitle: 'Support multiple themes, generate high-quality images with one click',
ctaButton: 'Get Started',
learnMore: 'Learn More'
},
features: {
title: 'Core Features',
subtitle: 'Providing complete Markdown card solutions',
list: {
themes: {
title: 'Rich Themes',
description: '20+ professionally designed theme templates'
},
export: {
title: 'High-quality Export',
description: 'Support export in multiple formats like PNG, SVG'
},
realtime: {
title: 'Real-time Preview',
description: 'WYSIWYG editing experience'
},
i18n: {
title: 'Multi-language Support',
description: 'Support for 8 mainstream languages'
}
}
}
},
// Blog related
blog: {
title: 'Tech Blog',
subtitle: 'Share development experience and technical insights',
readMore: 'Read More',
readTime: 'Reading Time',
minutes: 'minutes',
tags: 'Tags',
author: 'Author',
publishedOn: 'Published on',
categories: {
development: 'Development Practice',
design: 'Design Philosophy',
tutorial: 'Tutorial',
news: 'Project News'
}
},
// Editor interface
editor: {
placeholder: 'Enter Markdown content here...',
preview: 'Preview',
edit: 'Edit',
themes: 'Themes',
export: 'Export',
copy: 'Copy',
download: 'Download',
settings: 'Settings'
},
// Theme selector
themes: {
title: 'Select Theme',
search: 'Search themes...',
categories: {
all: 'All',
business: 'Business',
creative: 'Creative',
academic: 'Academic',
modern: 'Modern',
vintage: 'Vintage'
},
descriptions: {
glassmorphism: 'Modern glassmorphism design',
neumorphism: 'Soft neumorphism style',
neon: 'Cool neon light effects',
executive: 'Professional business style',
paper: 'Classic paper texture'
}
},
// Export functionality
export: {
title: 'Export Settings',
format: 'Format',
quality: 'Quality',
size: 'Size',
background: 'Background',
transparent: 'Transparent',
white: 'White',
custom: 'Custom',
exporting: 'Exporting...',
success: 'Export successful!',
error: 'Export failed, please try again'
},
// Common buttons and actions
common: {
save: 'Save',
cancel: 'Cancel',
confirm: 'Confirm',
delete: 'Delete',
edit: 'Edit',
view: 'View',
close: 'Close',
loading: 'Loading...',
error: 'Error occurred',
retry: 'Retry',
success: 'Success'
},
// Error messages
errors: {
networkError: 'Network connection failed, please check network settings',
fileTooBig: 'File size exceeds limit',
invalidFormat: 'Unsupported file format',
exportFailed: 'Export failed, please try again'
}
},
zh: {
languageName: '中文',
languageNativeName: '中文',
appName: 'MD2Card',
appDescription: '将 Markdown 转换为精美的知识卡片',
// ... other Chinese translations
}
// ... other language configurations
};
🎨 Theme System Deep Analysis
Theme Architecture Design
// themes/themes.js - Complete theme system
export const themes = {
// Glassmorphism theme
glassmorphism: {
id: 'glassmorphism',
name: 'Glassmorphism',
category: 'modern',
description: 'Modern glass texture with blur background effects',
preview: '/themes/glassmorphism-preview.png',
// Main styles
styles: {
background: `
linear-gradient(135deg,
rgba(255, 255, 255, 0.1),
rgba(255, 255, 255, 0.05)
)
`,
color: '#ffffff',
fontFamily: `
"SF Pro Display",
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
sans-serif
`,
// Layout properties
borderRadius: '20px',
padding: '2.5rem',
margin: '1rem',
// Visual effects
backdropFilter: 'blur(20px) saturate(180%)',
boxShadow: `
0 8px 32px 0 rgba(31, 38, 135, 0.37),
inset 0 1px 0 rgba(255, 255, 255, 0.1),
0 0 0 1px rgba(255, 255, 255, 0.05)
`,
border: '1px solid rgba(255, 255, 255, 0.18)',
// Text styles
textShadow: '0 1px 2px rgba(0, 0, 0, 0.1)',
lineHeight: '1.6'
},
// Custom CSS
customCSS: `
.card-container {
position: relative;
overflow: hidden;
}
.card-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
45deg,
rgba(255, 255, 255, 0.1) 0%,
transparent 50%,
rgba(255, 255, 255, 0.1) 100%
);
pointer-events: none;
}
.card-header {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding: 1.5rem 2rem;
margin: -2.5rem -2.5rem 2rem -2.5rem;
border-radius: 20px 20px 0 0;
}
.card-title {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
background: linear-gradient(45deg, #ffffff, #f0f8ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: none;
}
.card-content {
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
padding: 1.5rem;
margin: 1rem 0;
backdrop-filter: blur(5px);
}
.card-footer {
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.1),
rgba(255, 255, 255, 0.05)
);
padding: 1rem 2rem;
margin: 2rem -2.5rem -2.5rem -2.5rem;
border-radius: 0 0 20px 20px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
/* Code block styles */
.card-content pre {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 1rem;
overflow-x: auto;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.card-content code {
background: rgba(255, 255, 255, 0.2);
padding: 0.2rem 0.4rem;
border-radius: 4px;
font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace;
}
/* Responsive design */
@media (max-width: 768px) {
.card-container {
margin: 0.5rem;
}
.card-header,
.card-footer {
padding: 1rem;
}
.card-title {
font-size: 1.5rem;
}
.card-content {
padding: 1rem;
}
}
`,
// Theme configuration
config: {
supportsDarkMode: true,
supportsCustomBackground: true,
animationDuration: '0.3s',
responsiveBreakpoints: {
mobile: '768px',
tablet: '1024px'
}
}
},
// Executive theme
executive: {
id: 'executive',
name: 'Executive',
category: 'business',
description: 'Professional business style for formal occasions',
preview: '/themes/executive-preview.png',
styles: {
background: `
linear-gradient(145deg,
#1a1a2e 0%,
#16213e 50%,
#0f3460 100%
)
`,
color: '#ffffff',
fontFamily: '"Georgia", "Times New Roman", serif',
borderRadius: '12px',
padding: '3rem',
boxShadow: `
0 25px 50px -12px rgba(0, 0, 0, 0.6),
0 0 0 1px rgba(255, 255, 255, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.1)
`,
textShadow: '0 1px 3px rgba(0, 0, 0, 0.3)',
lineHeight: '1.8'
},
customCSS: `
.card-title {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 1.5rem;
text-align: center;
background: linear-gradient(45deg, #ffd700, #ffed4e, #ffd700);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
position: relative;
}
.card-title::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 100px;
height: 3px;
background: linear-gradient(90deg, #ffd700, #ffed4e);
border-radius: 2px;
}
.card-content {
font-size: 1.1rem;
line-height: 1.8;
text-align: justify;
}
.card-content h1,
.card-content h2,
.card-content h3 {
color: #ffd700;
margin: 1.5rem 0 1rem 0;
}
.card-content strong {
color: #ffed4e;
font-weight: 600;
}
.card-content blockquote {
border-left: 4px solid #ffd700;
padding-left: 1.5rem;
margin: 1.5rem 0;
font-style: italic;
background: rgba(255, 215, 0, 0.1);
padding: 1rem 1rem 1rem 1.5rem;
border-radius: 0 8px 8px 0;
}
`
}
// ... other theme definitions
};
// Theme Manager
export class ThemeManager {
constructor() {
this.currentTheme = null;
this.observers = [];
}
// Apply theme
applyTheme(themeId) {
const theme = themes[themeId];
if (!theme) {
console.error(`Theme "${themeId}" not found`);
return;
}
this.currentTheme = theme;
this.injectThemeStyles(theme);
this.notifyObservers(theme);
}
// Inject theme styles
injectThemeStyles(theme) {
// Remove old theme styles
const existingStyle = document.getElementById('theme-styles');
if (existingStyle) {
existingStyle.remove();
}
// Create new style element
const styleElement = document.createElement('style');
styleElement.id = 'theme-styles';
styleElement.type = 'text/css';
// Generate CSS
const css = this.generateThemeCSS(theme);
styleElement.innerHTML = css;
// Add to page
document.head.appendChild(styleElement);
}
// Generate theme CSS
generateThemeCSS(theme) {
const { styles, customCSS } = theme;
let css = `
.card-preview {
background: ${styles.background};
color: ${styles.color};
font-family: ${styles.fontFamily};
border-radius: ${styles.borderRadius};
padding: ${styles.padding};
box-shadow: ${styles.boxShadow};
text-shadow: ${styles.textShadow || 'none'};
line-height: ${styles.lineHeight || '1.6'};
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
`;
if (styles.backdropFilter) {
css += `
.card-preview {
backdrop-filter: ${styles.backdropFilter};
}
`;
}
if (styles.border) {
css += `
.card-preview {
border: ${styles.border};
}
`;
}
// Add custom CSS
if (customCSS) {
css += customCSS;
}
return css;
}
// Subscribe to theme changes
subscribe(callback) {
this.observers.push(callback);
}
// Notify observers
notifyObservers(theme) {
this.observers.forEach(callback => callback(theme));
}
// Get themes by category
getThemesByCategory(category = null) {
const themeList = Object.values(themes);
if (category) {
return themeList.filter(theme => theme.category === category);
}
return themeList;
}
// Search themes
searchThemes(query) {
const lowercaseQuery = query.toLowerCase();
return Object.values(themes).filter(theme =>
theme.name.toLowerCase().includes(lowercaseQuery) ||
theme.description.toLowerCase().includes(lowercaseQuery)
);
}
}
📝 Markdown Rendering Engine
Secure Rendering Implementation
// utils/markdownRenderer.js - Complete renderer
import { marked } from 'marked';
import DOMPurify from 'dompurify';
import Prism from 'prismjs';
// Configure marked
marked.setOptions({
breaks: true,
gfm: true,
headerIds: true,
mangle: false,
sanitize: false
});
// Custom renderer
const renderer = new marked.Renderer();
// Custom code block rendering
renderer.code = function(code, language) {
const validLanguage = Prism.languages[language] ? language : 'plaintext';
const highlightedCode = Prism.highlight(code, Prism.languages[validLanguage], validLanguage);
return `
<pre class="language-${validLanguage}">
<code class="language-${validLanguage}">${highlightedCode}</code>
</pre>
`;
};
// Custom link rendering
renderer.link = function(href, title, text) {
const isExternal = href.startsWith('http') && !href.includes(window.location.hostname);
const target = isExternal ? ' target="_blank" rel="noopener noreferrer"' : '';
const titleAttr = title ? ` title="${title}"` : '';
return `<a href="${href}"${titleAttr}${target}>${text}</a>`;
};
// Custom image rendering
renderer.image = function(href, title, text) {
const titleAttr = title ? ` title="${title}"` : '';
const altAttr = text ? ` alt="${text}"` : '';
return `
<figure class="image-figure">
<img src="${href}"${altAttr}${titleAttr} loading="lazy" />
${text ? `<figcaption>${text}</figcaption>` : ''}
</figure>
`;
};
marked.use({ renderer });
// Markdown Renderer Class
export class MarkdownRenderer {
constructor(options = {}) {
this.options = {
sanitize: true,
highlightCode: true,
enableMath: false,
...options
};
this.setupDOMPurify();
}
// Configure DOMPurify
setupDOMPurify() {
this.sanitizerConfig = {
ALLOWED_TAGS: [
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'p', 'br', 'strong', 'em', 'u', 's', 'del',
'a', 'img', 'figure', 'figcaption',
'ul', 'ol', 'li',
'blockquote', 'pre', 'code',
'table', 'thead', 'tbody', 'tr', 'th', 'td',
'hr', 'div', 'span'
],
ALLOWED_ATTR: [
'href', 'title', 'alt', 'src', 'target', 'rel',
'class', 'id', 'style'
],
ALLOW_DATA_ATTR: false,
FORBID_SCRIPT: true,
FORBID_TAGS: ['script', 'object', 'embed', 'iframe'],
KEEP_CONTENT: true
};
}
// Render Markdown
render(markdown) {
try {
// Preprocess
const processedMarkdown = this.preprocess(markdown);
// Convert to HTML
let html = marked(processedMarkdown);
// Postprocess
html = this.postprocess(html);
// Sanitize
if (this.options.sanitize) {
html = DOMPurify.sanitize(html, this.sanitizerConfig);
}
return html;
} catch (error) {
console.error('Markdown rendering error:', error);
return '<p>Rendering error, please check Markdown syntax</p>';
}
}
// Preprocess Markdown
preprocess(markdown) {
let processed = markdown;
// Process math formulas (if enabled)
if (this.options.enableMath) {
processed = this.processMath(processed);
}
// Process custom syntax
processed = this.processCustomSyntax(processed);
return processed;
}
// Postprocess HTML
postprocess(html) {
let processed = html;
// Add responsive table wrapper
processed = processed.replace(
/<table/g,
'<div class="table-wrapper"><table'
).replace(
/<\/table>/g,
'</table></div>'
);
// Process checkboxes
processed = processed.replace(
/\[x\]/gi,
'<input type="checkbox" checked disabled class="task-checkbox" />'
).replace(
/\[ \]/g,
'<input type="checkbox" disabled class="task-checkbox" />'
);
return processed;
}
// Process custom syntax
processCustomSyntax(markdown) {
// Alert box syntax: :::warning content :::
markdown = markdown.replace(
/:::(\w+)\s*([\s\S]*?):::/g,
(match, type, content) => {
return `<div class="alert alert-${type}">${content.trim()}</div>`;
}
);
// Highlight syntax: ==highlighted text==
markdown = markdown.replace(
/==(.*?)==/g,
'<mark>$1</mark>'
);
return markdown;
}
// Process math formulas
processMath(markdown) {
// Inline formulas: $...$
markdown = markdown.replace(
/\$(?!\$)(.*?)\$/g,
'<span class="math-inline">$1</span>'
);
// Block formulas: $$...$$
markdown = markdown.replace(
/\$\$([\s\S]*?)\$\$/g,
'<div class="math-block">$1</div>'
);
return markdown;
}
// Generate table of contents
generateTOC(markdown) {
const tokens = marked.lexer(markdown);
const toc = [];
tokens.forEach(token => {
if (token.type === 'heading') {
toc.push({
level: token.depth,
text: token.text,
anchor: this.generateAnchor(token.text)
});
}
});
return toc;
}
// Generate anchor
generateAnchor(text) {
return text
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
}
// Default renderer instance
export const defaultRenderer = new MarkdownRenderer();
// Render function
export const renderMarkdown = (markdown, options = {}) => {
const renderer = new MarkdownRenderer(options);
return renderer.render(markdown);
};
🖼️ Image Export System
High-quality Export Implementation
// utils/imageExporter.js - Complete export system
import { toPng, toSvg, toJpeg } from 'html-to-image';
export class ImageExporter {
constructor() {
this.defaultOptions = {
quality: 1.0,
pixelRatio: 3,
backgroundColor: 'transparent',
skipFonts: false,
cacheBust: true
};
}
// Export to PNG
async exportToPNG(element, options = {}) {
const config = { ...this.defaultOptions, ...options };
try {
// Preprocess element
const processedElement = await this.preprocessElement(element);
// Generate image
const dataUrl = await toPng(processedElement, config);
// Postprocess
return this.postprocessImage(dataUrl, 'png');
} catch (error) {
console.error('PNG export failed:', error);
throw new Error('PNG export failed');
}
}
// Export to SVG
async exportToSVG(element, options = {}) {
const config = {
...this.defaultOptions,
...options,
backgroundColor: 'transparent' // SVG usually uses transparent background
};
try {
const processedElement = await this.preprocessElement(element);
const svgString = await toSvg(processedElement, config);
return this.postprocessImage(svgString, 'svg');
} catch (error) {
console.error('SVG export failed:', error);
throw new Error('SVG export failed');
}
}
// Export to JPEG
async exportToJPEG(element, options = {}) {
const config = {
...this.defaultOptions,
...options,
backgroundColor: '#ffffff' // JPEG needs background color
};
try {
const processedElement = await this.preprocessElement(element);
const dataUrl = await toJpeg(processedElement, config);
return this.postprocessImage(dataUrl, 'jpeg');
} catch (error) {
console.error('JPEG export failed:', error);
throw new Error('JPEG export failed');
}
}
// Preprocess element
async preprocessElement(element) {
// Clone element
const clonedElement = element.cloneNode(true);
// Ensure all fonts are loaded
await this.ensureFontsLoaded(clonedElement);
// Process images
await this.processImages(clonedElement);
// Optimize styles
this.optimizeStyles(clonedElement);
return clonedElement;
}
// Ensure fonts are loaded
async ensureFontsLoaded(element) {
const fonts = this.extractFonts(element);
if (fonts.length > 0) {
await Promise.all(
fonts.map(font => this.loadFont(font))
);
}
}
// Extract font information
extractFonts(element) {
const fonts = new Set();
const computedStyles = window.getComputedStyle(element);
// Add element's own font
fonts.add(computedStyles.fontFamily);
// Recursively check child elements
const children = element.querySelectorAll('*');
children.forEach(child => {
const childStyle = window.getComputedStyle(child);
fonts.add(childStyle.fontFamily);
});
return Array.from(fonts);
}
// Load font
async loadFont(fontFamily) {
try {
// Use Font Loading API
const font = new FontFace(fontFamily, `local(${fontFamily})`);
await font.load();
document.fonts.add(font);
} catch (error) {
console.warn(`Failed to load font: ${fontFamily}`);
}
}
// Process images
async processImages(element) {
const images = element.querySelectorAll('img');
await Promise.all(
Array.from(images).map(async (img) => {
try {
// Ensure image is fully loaded
if (!img.complete) {
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
});
}
// Convert to base64 (if needed)
if (img.src.startsWith('http')) {
const base64 = await this.imageToBase64(img.src);
img.src = base64;
}
} catch (error) {
console.warn(`Failed to process image: ${img.src}`);
}
})
);
}
// Image to base64
async imageToBase64(url) {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
resolve(canvas.toDataURL());
};
img.onerror = reject;
img.src = url;
});
}
// Optimize styles
optimizeStyles(element) {
// Remove unnecessary animations
const animatedElements = element.querySelectorAll('*');
animatedElements.forEach(el => {
el.style.animation = 'none';
el.style.transition = 'none';
});
// Ensure all elements are visible
element.style.visibility = 'visible';
element.style.opacity = '1';
// Fix possible layout issues
element.style.transform = 'none';
element.style.filter = 'none';
}
// Postprocess image
postprocessImage(dataUrl, format) {
return {
dataUrl,
format,
size: this.calculateImageSize(dataUrl),
timestamp: Date.now()
};
}
// Calculate image size
calculateImageSize(dataUrl) {
const base64String = dataUrl.split(',')[1];
const bytes = atob(base64String).length;
return {
bytes,
kb: Math.round(bytes / 1024 * 100) / 100,
mb: Math.round(bytes / 1024 / 1024 * 100) / 100
};
}
// Download image
downloadImage(dataUrl, filename = 'md2card') {
const link = document.createElement('a');
link.download = filename;
link.href = dataUrl;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Copy to clipboard
async copyToClipboard(dataUrl) {
try {
// Convert to Blob
const response = await fetch(dataUrl);
const blob = await response.blob();
// Copy to clipboard
await navigator.clipboard.write([
new ClipboardItem({ [blob.type]: blob })
]);
return true;
} catch (error) {
console.error('Failed to copy to clipboard:', error);
return false;
}
}
}
// Default exporter instance
export const defaultExporter = new ImageExporter();
// Convenient export function
export const exportCard = async (element, format = 'png', options = {}) => {
const exporter = new ImageExporter();
switch (format.toLowerCase()) {
case 'png':
return await exporter.exportToPNG(element, options);
case 'svg':
return await exporter.exportToSVG(element, options);
case 'jpeg':
case 'jpg':
return await exporter.exportToJPEG(element, options);
default:
throw new Error(`Unsupported format: ${format}`);
}
};
🚀 Performance Optimization Strategies
Component-level Optimization
// components/OptimizedCardPreview.js - Optimized preview component
import React, { memo, useMemo, useCallback, useRef, useEffect } from 'react';
import { useLanguage } from '../i18n/LanguageContext';
import { defaultRenderer } from '../utils/markdownRenderer';
const CardPreview = memo(({
markdown,
theme,
onPreviewReady,
enableLiveUpdate = true
}) => {
const { t } = useLanguage();
const previewRef = useRef(null);
const renderTimeoutRef = useRef(null);
// Use useMemo to cache rendering results
const renderedContent = useMemo(() => {
if (!markdown) return '';
try {
return defaultRenderer.render(markdown);
} catch (error) {
console.error('Rendering error:', error);
return `<p>${t('errors.renderingFailed')}</p>`;
}
}, [markdown, t]);
// Use useMemo to cache theme styles
const themeStyles = useMemo(() => {
if (!theme) return {};
return {
background: theme.styles.background,
color: theme.styles.color,
fontFamily: theme.styles.fontFamily,
borderRadius: theme.styles.borderRadius,
padding: theme.styles.padding,
boxShadow: theme.styles.boxShadow,
backdropFilter: theme.styles.backdropFilter,
border: theme.styles.border,
textShadow: theme.styles.textShadow,
lineHeight: theme.styles.lineHeight,
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
};
}, [theme]);
// Debounced update callback
const debouncedUpdate = useCallback(() => {
if (renderTimeoutRef.current) {
clearTimeout(renderTimeoutRef.current);
}
renderTimeoutRef.current = setTimeout(() => {
if (onPreviewReady && previewRef.current) {
onPreviewReady(previewRef.current);
}
}, 300);
}, [onPreviewReady]);
// Listen for content changes
useEffect(() => {
if (enableLiveUpdate) {
debouncedUpdate();
}
}, [renderedContent, themeStyles, enableLiveUpdate, debouncedUpdate]);
// Cleanup timers
useEffect(() => {
return () => {
if (renderTimeoutRef.current) {
clearTimeout(renderTimeoutRef.current);
}
};
}, []);
return (
<div
ref={previewRef}
className="card-preview"
style={themeStyles}
dangerouslySetInnerHTML={{ __html: renderedContent }}
/>
);
});
CardPreview.displayName = 'CardPreview';
export default CardPreview;
🔮 Future Development Planning
Technology Roadmap
// Future feature planning
const futureFeatures = {
// Phase 1: Core feature enhancement
phase1: {
aiThemeGeneration: 'AI-based intelligent theme generation',
collaborativeEditing: 'Real-time collaborative editing',
advancedExport: 'More export formats and options',
pluginSystem: 'Plugin system architecture'
},
// Phase 2: Platform development
phase2: {
userAccounts: 'User account system',
cloudSync: 'Cloud synchronization',
themeMarketplace: 'Theme marketplace',
apiAccess: 'Open API interface'
},
// Phase 3: Ecosystem building
phase3: {
mobileApp: 'Mobile application',
desktopApp: 'Desktop application',
enterpriseFeatures: 'Enterprise features',
aiAssistant: 'AI writing assistant'
}
};
Conclusion
The development of MD2Card's blog system fully demonstrates modern frontend development best practices. Through reasonable architecture design, complete internationalization support, rich theme system, and high-quality export functionality, we have created a powerful and user-friendly Markdown knowledge card platform.
Project Highlights
- Modular Architecture: Clear code organization and component division
- Internationalization Support: Complete multi-language solution
- Theme System: Flexible and extensible theme framework
- Performance Optimization: Multi-level performance optimization strategies
- Security and Reliability: Comprehensive security protection mechanisms
Technical Innovation
- Dynamic Theme Engine: Real-time theme switching and style injection
- Smart Caching System: Multi-level caching for performance improvement
- Secure Rendering: XSS-protected Markdown rendering
- High-quality Export: Professional-grade image export functionality
In the future, MD2Card will continue to evolve, providing users worldwide with even better knowledge management and content creation experiences. We believe that through continuous technological innovation and user experience optimization, MD2Card will become a benchmark product in the Markdown content creation field.