/* global React, useI18n, ToolHead, StatusBar, Dropzone, OptCheckbox, OptRadio, OptDivider, EmptyOutput, TOOL_BY_ID, convertCliToMarkdown, convertChatToMarkdown, renderMarkdown, pdfToMarkdown, pdfToTables, tableToCsv, docxToMarkdown, excelToSheets, sheetsToXlsxBlob, ocrImage, downloadText, downloadBlob, readFileAsText, fmtBytes, addToHistory */ /* ============================================================ tools.jsx — Tool view components (one per converter) ============================================================ */ const { useState: useS, useEffect: useE, useMemo: useM, useRef: useR, useCallback: useC } = React; // Restore a text-input tool from a history entry id (?h=). Sets the input // when the matching entry carries a stored payload. No-op for file/binary tools. function useRestoreInput(restoreId, setInput) { useE(() => { if (!restoreId) return; const e = window.getHistoryEntry(restoreId); if (e && e.payload && typeof e.payload.input === 'string') setInput(e.payload.input); }, [restoreId]); } const CLI_SAMPLE = `✅ gitnexus 重新索引完成 📊 4 个 EnterNet 仓库已注册(新路径) ┌──────────────────────┬────────┬────────┬───────┐ │ 仓库 │ nodes │ edges │ flows │ ├──────────────────────┼────────┼────────┼───────┤ │ enternet-center │ 3,794 │ 6,967 │ 158 │ ├──────────────────────┼────────┼────────┼───────┤ │ enternet-agent-suite │ 5,515 │ 12,329 │ 300 │ ├──────────────────────┼────────┼────────┼───────┤ │ enternet-pc-api │ 536 │ 1,216 │ 46 │ ├──────────────────────┼────────┼────────┼───────┤ │ enternet-agent-qt │ 602 │ 1,107 │ 25 │ ├──────────────────────┼────────┼────────┼───────┤ │ 合计 │ 10,447 │ 21,619 │ 529 │ └──────────────────────┴────────┴────────┴───────┘ 数字与旧索引完全一致(之前的 .gitnexus/ 数据本来就在仓库里,只是注册表路径过时;用 gitnexus index 重注册即可,无需重跑 analyze)。 🗂 Group 修正 - ✅ 新建 enternet group(4 个仓库) - ✅ gitnexus group sync enternet → 93 contracts 已抽取 - ✅ 删掉旧 byrust group 目录 🔍 现在可以直接用 MCP 查询 mcp__gitnexus__route_map({ repo: "enternet-center" }) mcp__gitnexus__group_query({ name: "enternet", query: "user login flow" }) mcp__gitnexus__api_impact({ repo: "@enternet", route: "/api/v1/admin/..." }) `; const MD_SAMPLE = `# Welcome to Convertly A document conversion suite running **entirely in your browser**. ## Features - 🚀 Fast and lightweight - 🔒 Private (no uploads) - 🌐 Bilingual interface ## Example table | Format | Direction | Status | | --- | --- | --- | | PDF | → Markdown | ✅ | | PDF | → Excel | ✅ | | DOCX | → Markdown | ✅ | | Image | → Text (OCR) | ✅ | > Tip: paste any Markdown here and see it rendered live. \`\`\`bash # Install npm i convertly \`\`\` `; // Common copy button with feedback function CopyButton({ text, label, className = 'btn btn-primary' }) { const { t } = useI18n(); const [copied, setCopied] = useS(false); const doCopy = async () => { try { await navigator.clipboard.writeText(text); } catch (e) { const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); } setCopied(true); setTimeout(() => setCopied(false), 1400); }; return ( ); } // ================================================================= // TOOL: CLI → Markdown // ================================================================= function CliToMarkdown({ restoreId }) { const { t } = useI18n(); const tool = TOOL_BY_ID['cli-to-markdown']; const [input, setInput] = useS(CLI_SAMPLE); const [opts, setOpts] = useS({ headerStyle: 'auto', fenceCode: true, tightLists: true, stripEmojis: false, }); const [view, setView] = useS('markdown'); const tweaks = window.__cv_tweaks || {}; const layout = tweaks.layout || 'horiz'; const mdStyle = tweaks.mdStyle || 'gfm'; const optsResolved = useM(() => { const o = { ...opts }; if (mdStyle === 'plain') { o.headerStyle = 'bold'; o.fenceCode = false; } if (mdStyle === 'emoji') { o.stripEmojis = false; } if (mdStyle === 'plain') { o.stripEmojis = true; } return o; }, [opts, mdStyle]); const markdown = useM(() => convertCliToMarkdown(input, optsResolved), [input, optsResolved]); const tableCount = useM(() => { return (markdown.match(/^\|[\s\-:|]+\|\s*$/gm) || []).length; }, [markdown]); const recordHistory = useC(() => { if (!input.trim()) return; addToHistory({ toolId: 'cli-to-markdown', title: input.split('\n')[0].slice(0, 60) || t('untitled'), size: new Blob([input]).size, payload: { input }, }); }, [input, t]); useRestoreInput(restoreId, setInput); return (
} />
setOpts(o => ({ ...o, headerStyle: v }))} label={t('opt_headers')} options={[ { value: 'auto', label: t('opt_headers_auto') }, { value: 'bold', label: t('opt_headers_bold') }, { value: 'none', label: t('opt_headers_none') }, ]} /> setOpts(o => ({ ...o, fenceCode: v }))} label={t('opt_fence_code')} hint={t('opt_fence_code_hint')} /> setOpts(o => ({ ...o, tightLists: v }))} label={t('opt_tight_lists')} /> setOpts(o => ({ ...o, stripEmojis: v }))} label={t('opt_strip_emojis')} />
{t('common_input')} {input.length} {t('common_chars')}