c5aba094bd
- AI Tools moves from a Settings section into a modal behind a button at the Settings tab bar's right edge (AiToolsModal, `actions` on PageTabs, /settings#ai-tools); rows get an enable switch, Default badge, and an Update button for every tool whose catalog names an updater (no version lookup on open); not-installed tools fold behind a disclosure, a finished job's log folds to one line; shared AiToolIcon - codex updates through its own `codex update`, openclaw/hermes gain `update --yes`; update-all runs every installed updater and skips an npm one only when npm does not manage the binary - Add-lane tool picker is one button showing the chosen tool that opens the enabled tools as buttons - Claude Code / Codex AgentHub sub-tab folded into Overview (home editor, hooks, pricing); the path list drops CLAUDE_HOME/CODEX_HOME; HomeDirSetting shows the saved value only while the field differs - Preview/Raw toggle for markdown files in the config FileViewer and the Changes panel's whole-file view (MarkdownPreview: react-markdown + remark-gfm, raw HTML never run, frontmatter as a code block) - docs and the CLAUDE.md marker updated
45 lines
1.7 KiB
TypeScript
45 lines
1.7 KiB
TypeScript
/**
|
|
* @file MarkdownPreview.tsx
|
|
* @description A markdown file rendered for reading (GitHub-flavoured:
|
|
* tables, task lists, strikethrough, autolinks), shared by the config
|
|
* explorer's file viewer and the lane Changes panel. Raw HTML in the file is
|
|
* shown as text, never run (react-markdown without rehype-raw), and links
|
|
* open in a new tab. A leading YAML frontmatter block (skills, agents) is
|
|
* shown as a code block instead of being misread as a heading. `isMarkdown`
|
|
* says which paths get the preview toggle.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import ReactMarkdown from "react-markdown";
|
|
import remarkGfm from "remark-gfm";
|
|
|
|
/** Whether `path` names a markdown file. */
|
|
export function isMarkdown(path: string | null | undefined): boolean {
|
|
return !!path && /\.(md|markdown|mdx)$/i.test(path);
|
|
}
|
|
|
|
/** Split a leading `---` frontmatter block off the body. */
|
|
function splitFrontmatter(text: string): { front: string | null; body: string } {
|
|
const m = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(text);
|
|
return m ? { front: m[1]!, body: text.slice(m[0].length) } : { front: null, body: text };
|
|
}
|
|
|
|
export function MarkdownPreview({ text, className = "" }: { text: string; className?: string }) {
|
|
const { front, body } = splitFrontmatter(text);
|
|
return (
|
|
<div data-testid="markdown-preview" className={`md-preview ${className}`}>
|
|
{front !== null && <pre className="md-frontmatter">{front}</pre>}
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm]}
|
|
components={{
|
|
a: ({ node: _node, ...props }) => (
|
|
<a {...props} target="_blank" rel="noreferrer noopener" />
|
|
),
|
|
}}
|
|
>
|
|
{body}
|
|
</ReactMarkdown>
|
|
</div>
|
|
);
|
|
}
|