tuzi-markdown-to-html

安装量: 42
排名: #17341

安装

npx skills add https://github.com/tuziapi/tuzi-skills --skill tuzi-markdown-to-html
Markdown to HTML Converter
Converts Markdown files to beautifully styled HTML with inline CSS, optimized for WeChat Official Account and other platforms.
Script Directory
Agent Execution
Determine this SKILL.md directory as SKILL_DIR , then use ${SKILL_DIR}/scripts/.ts . Script Purpose scripts/main.ts Main entry point Preferences (EXTEND.md) Use Bash to check EXTEND.md existence (priority order):

Check project-level first

test -f .tuzi-skills/tuzi-markdown-to-html/EXTEND.md && echo "project"

Then user-level (cross-platform: $HOME works on macOS/Linux/WSL)

test
-f
"
$HOME
/.tuzi-skills/tuzi-markdown-to-html/EXTEND.md"
&&
echo
"user"
┌──────────────────────────────────────────────────────────────┬───────────────────┐
│ Path │ Location │
├──────────────────────────────────────────────────────────────┼───────────────────┤
│ .tuzi-skills/tuzi-markdown-to-html/EXTEND.md │ Project directory │
├──────────────────────────────────────────────────────────────┼───────────────────┤
│ $HOME/.tuzi-skills/tuzi-markdown-to-html/EXTEND.md │ User home │
└──────────────────────────────────────────────────────────────┴───────────────────┘
┌───────────┬───────────────────────────────────────────────────────────────────────────┐
│ Result │ Action │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Found │ Read, parse, apply settings │
├───────────┼───────────────────────────────────────────────────────────────────────────┤
│ Not found │ Use defaults │
└───────────┴───────────────────────────────────────────────────────────────────────────┘
EXTEND.md Supports
Default theme | Custom CSS variables | Code block style
Workflow
Step 0: Pre-check (Chinese Content)
Condition
Only execute if input file contains Chinese text.
Detection
:
Read input markdown file
Check if content contains CJK characters (Chinese/Japanese/Korean)
If no CJK content → skip to Step 1
Format Suggestion
:
If CJK content detected AND
tuzi-format-markdown
skill is available:
Use
AskUserQuestion
to ask whether to format first. Formatting can fix:
Bold markers with punctuation inside causing
**
parse failures
CJK/English spacing issues
If user agrees
Invoke
tuzi-format-markdown
skill to format the file, then use formatted file as input.
If user declines
Continue with original file. Step 1: Determine Theme Theme resolution order (first match wins): User explicitly specified theme (CLI --theme or conversation) EXTEND.md default_theme (this skill's own EXTEND.md, checked in Step 0) tuzi-post-to-wechat EXTEND.md default_theme (cross-skill fallback) If none found → use AskUserQuestion to confirm Cross-skill EXTEND.md check (only if this skill's EXTEND.md has no default_theme ):

Check tuzi-post-to-wechat EXTEND.md for default_theme

test
-f
"
$HOME
/.tuzi-skills/tuzi-post-to-wechat/EXTEND.md"
&&
grep
-o
'default_theme:.*'
"
$HOME
/.tuzi-skills/tuzi-post-to-wechat/EXTEND.md"
If theme is resolved from EXTEND.md
Use it directly, do NOT ask the user.
If no default found
Use AskUserQuestion to confirm: Theme Description default (Recommended) 经典主题 - 传统排版,标题居中带底边,二级标题白字彩底 grace 优雅主题 - 文字阴影,圆角卡片,精致引用块 simple 简洁主题 - 现代极简风,不对称圆角,清爽留白 modern 现代主题 - 大圆角、药丸形标题、宽松行距(搭配 --color red 还原传统红金风格) Step 2: Convert npx -y bun ${SKILL_DIR} /scripts/main.ts < markdown_file

--theme < theme

Step 3: Report Result Display the output path from JSON result. If backup was created, mention it. Usage npx -y bun ${SKILL_DIR} /scripts/main.ts < markdown_file

[ options ] Options: Option Description Default --theme Theme name (default, grace, simple, modern) default --color Primary color: preset name or hex value theme default --font-family Font: sans, serif, serif-cjk, mono, or CSS value theme default --font-size Font size: 14px, 15px, 16px, 17px, 18px 16px --title Override title from frontmatter --keep-title Keep the first heading in content false (removed) --help Show help Color Presets: Name Hex Label blue</p> </blockquote> </dd> </dl> </dd> </dl> <h1 id="0f4c81">0F4C81</h1> <p>经典蓝 green</p> <h1 id="009874">009874</h1> <p>翡翠绿 vermilion</p> <h1 id="fa5151">FA5151</h1> <p>活力橘 yellow</p> <h1 id="fece00">FECE00</h1> <p>柠檬黄 purple</p> <h1 id="92617e">92617E</h1> <p>薰衣紫 sky</p> <h1 id="55c9ea">55C9EA</h1> <p>天空蓝 rose</p> <h1 id="b76e79">B76E79</h1> <p>玫瑰金 olive</p> <h1 id="556b2f">556B2F</h1> <p>橄榄绿 black</p> <h1 id="333333">333333</h1> <p>石墨黑 gray</p> <h1 id="a9a9a9">A9A9A9</h1> <p>雾烟灰 pink</p> <h1 id="ffb7c5">FFB7C5</h1> <p>樱花粉 red</p> <h1 id="a93226">A93226</h1> <p>中国红 orange</p> <h1 id="d97757">D97757</h1> <p>暖橘 (modern default) Examples:</p> <h1 id="basic-conversion-uses-default-theme-removes-first-heading">Basic conversion (uses default theme, removes first heading)</h1> <p>npx -y bun ${SKILL_DIR} /scripts/main.ts article.md</p> <h1 id="with-specific-theme">With specific theme</h1> <p>npx -y bun ${SKILL_DIR} /scripts/main.ts article.md --theme grace</p> <h1 id="theme-with-custom-color">Theme with custom color</h1> <p>npx -y bun ${SKILL_DIR} /scripts/main.ts article.md --theme modern --color red</p> <h1 id="keep-the-first-heading-in-content">Keep the first heading in content</h1> <p>npx -y bun ${SKILL_DIR} /scripts/main.ts article.md --keep-title</p> <h1 id="override-title">Override title</h1> <dl> <dt>npx</dt> <dt>-y</dt> <dt>bun</dt> <dt>${SKILL_DIR}</dt> <dt>/scripts/main.ts article.md</dt> <dt>--title</dt> <dt>"My Article"</dt> <dt>Output</dt> <dt>File location</dt> <dd> <dl> <dt>Same directory as input markdown file.</dt> <dt>Input:</dt> <dt>/path/to/article.md</dt> <dt>Output:</dt> <dt>/path/to/article.html</dt> <dt>Conflict handling</dt> <dd>If HTML file already exists, it will be backed up first: Backup: /path/to/article.html.bak-YYYYMMDDHHMMSS JSON output to stdout: { "title" : "Article Title" , "author" : "Author Name" , "summary" : "Article summary..." , "htmlPath" : "/path/to/article.html" , "backupPath" : "/path/to/article.html.bak-20260128180000" , "contentImages" : [ { "placeholder" : "MDTOHTMLIMGPH_1" , "localPath" : "/path/to/img.png" , "originalPath" : "imgs/image.png" } ] } Themes Theme Description default 经典主题 - 传统排版,标题居中带底边,二级标题白字彩底 grace 优雅主题 - 文字阴影,圆角卡片,精致引用块 (by @brzhang) simple 简洁主题 - 现代极简风,不对称圆角,清爽留白 (by @okooo5km) modern 现代主题 - 大圆角、药丸形标题、宽松行距(搭配 --color red 还原传统红金风格) Supported Markdown Features Feature Syntax Headings</dd> </dl> </dd> </dl> <h1 id="h1">H1</h1> <p>to</p> <h6 id="h6">H6</h6> <p>Bold/Italic <strong>bold</strong> , <em>italic</em> Code blocks <code>``lang with syntax highlighting Inline code</code>code` Tables GitHub-flavored markdown tables Images <img alt="alt" src="src" /> Links <a href="url">text</a> with footnote references Blockquotes</p> <blockquote> <p>quote Lists - unordered, 1. ordered Alerts [!NOTE] , [!WARNING] , etc. Footnotes [^1] references Ruby text `{base Mermaid <code>mermaid diagrams PlantUML</code>plantuml diagrams Frontmatter Supports YAML frontmatter for metadata:</p> </blockquote> <hr /> <p>title : Article Title author : Author Name description : Article summary</p> <hr /> <p>If no title is found, extracts from first H1/H2 heading or uses filename. Extension Support Custom configurations via EXTEND.md. See Preferences section for paths and supported options.</p> </article> <a href="/" class="back-link">← <span data-i18n="detail.backToLeaderboard">返回排行榜</span></a> </div> <aside class="sidebar"> <section class="related-skills" id="relatedSkillsSection"> <h2 class="related-title" data-i18n="detail.relatedSkills">相关 Skills</h2> <div class="related-list" id="relatedSkillsList"> <div class="skeleton-card"></div> <div class="skeleton-card"></div> <div class="skeleton-card"></div> </div> </section> </aside> </div> </div> <script src="https://unpkg.com/i18next@23.11.5/i18next.min.js" defer></script> <script src="https://unpkg.com/i18next-browser-languagedetector@7.2.1/i18nextBrowserLanguageDetector.min.js" defer></script> <script defer> // Language resources - same pattern as index page const resources = { 'zh-CN': null, 'en': null, 'ja': null, 'ko': null, 'zh-TW': null, 'es': null, 'fr': null }; // Load language files (only current + fallback for performance) async function loadLanguageResources() { const savedLang = localStorage.getItem('i18nextLng') || 'en'; const langsToLoad = new Set([savedLang, 'en']); // current + fallback await Promise.all([...langsToLoad].map(async (lang) => { try { const response = await fetch(`/locales/${lang}.json`); if (response.ok) { resources[lang] = { translation: await response.json() }; } } catch (error) { console.warn(`Failed to load ${lang} language file:`, error); } })); } // Load a single language on demand (for language switching) async function loadLanguage(lang) { if (resources[lang]) return; try { const response = await fetch(`/locales/${lang}.json`); if (response.ok) { resources[lang] = { translation: await response.json() }; i18next.addResourceBundle(lang, 'translation', resources[lang].translation); } } catch (error) { console.warn(`Failed to load ${lang} language file:`, error); } } // Initialize i18next async function initI18n() { try { await loadLanguageResources(); // Filter out null values from resources const validResources = {}; for (const [lang, data] of Object.entries(resources)) { if (data !== null) { validResources[lang] = data; } } console.log('Loaded languages:', Object.keys(validResources)); console.log('zh-CN resource:', validResources['zh-CN']); console.log('detail.home in resource:', validResources['zh-CN']?.translation?.detail?.home); // 检查是否有保存的语言偏好 const savedLang = localStorage.getItem('i18nextLng'); // 如果没有保存的语言偏好,默认使用英文 const defaultLang = savedLang && ['zh-CN', 'en', 'ja', 'ko', 'zh-TW', 'es', 'fr'].includes(savedLang) ? savedLang : 'en'; await i18next .use(i18nextBrowserLanguageDetector) .init({ lng: defaultLang, // 强制设置初始语言 fallbackLng: 'en', supportedLngs: ['zh-CN', 'en', 'ja', 'ko', 'zh-TW', 'es', 'fr'], resources: validResources, detection: { order: ['localStorage'], // 只使用 localStorage,不检测浏览器语言 caches: ['localStorage'], lookupLocalStorage: 'i18nextLng' }, interpolation: { escapeValue: false } }); console.log('i18next initialized, language:', i18next.language); console.log('Test translation:', i18next.t('detail.home')); // Set initial language in selector const langSwitcher = document.getElementById('langSwitcher'); langSwitcher.value = i18next.language; // Update page language updatePageLanguage(); // Language switch event langSwitcher.addEventListener('change', async (e) => { await loadLanguage(e.target.value); // load on demand i18next.changeLanguage(e.target.value).then(() => { updatePageLanguage(); localStorage.setItem('i18nextLng', e.target.value); }); }); } catch (error) { console.error('i18next init failed:', error); } } // Translation helper function t(key, options = {}) { return i18next.t(key, options); } // Update all translatable elements function updatePageLanguage() { // Update HTML lang attribute document.documentElement.lang = i18next.language; // Update elements with data-i18n attribute document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.getAttribute('data-i18n'); el.textContent = t(key); }); } // Copy command function function copyCommand() { const command = document.getElementById('installCommand').textContent; const btn = document.getElementById('copyBtn'); navigator.clipboard.writeText(command).then(() => { btn.textContent = t('copied'); btn.classList.add('copied'); setTimeout(() => { btn.textContent = t('copy'); btn.classList.remove('copied'); }, 2000); }).catch(() => { // Fallback for non-HTTPS const textArea = document.createElement('textarea'); textArea.value = command; textArea.style.position = 'fixed'; textArea.style.left = '-9999px'; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); btn.textContent = t('copied'); btn.classList.add('copied'); setTimeout(() => { btn.textContent = t('copy'); btn.classList.remove('copied'); }, 2000); }); } // Initialize document.getElementById('copyBtn').addEventListener('click', copyCommand); initI18n(); // 异步加载相关 Skills async function loadRelatedSkills() { const owner = 'tuziapi'; const skillName = 'tuzi-markdown-to-html'; const currentLang = 'en'; const listContainer = document.getElementById('relatedSkillsList'); const section = document.getElementById('relatedSkillsSection'); try { const response = await fetch(`/api/related-skills/${encodeURIComponent(owner)}/${encodeURIComponent(skillName)}?limit=6`); if (!response.ok) { throw new Error('Failed to load'); } const data = await response.json(); const relatedSkills = data.related_skills || []; if (relatedSkills.length === 0) { // 没有相关推荐时隐藏整个区域 section.style.display = 'none'; return; } // 渲染相关 Skills listContainer.innerHTML = relatedSkills.map(skill => { const desc = skill.description || ''; const truncatedDesc = desc.length > 60 ? desc.substring(0, 60) + '...' : desc; return ` <a href="${currentLang === 'en' ? '' : '/' + currentLang}/skill/${skill.owner}/${skill.repo}/${skill.skill_name}" class="related-card"> <div class="related-name">${escapeHtml(skill.skill_name)}</div> <div class="related-meta"> <span class="related-owner">${escapeHtml(skill.owner)}</span> <span class="related-installs">${skill.installs}</span> </div> <div class="related-desc">${escapeHtml(truncatedDesc)}</div> </a> `; }).join(''); } catch (error) { console.error('Failed to load related skills:', error); // 加载失败时显示提示或隐藏 listContainer.innerHTML = '<div class="related-empty">暂无相关推荐</div>'; } } // HTML 转义 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 页面加载完成后异步加载相关 Skills if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', loadRelatedSkills); } else { loadRelatedSkills(); } </script> </body> </html>