mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-07 06:52:28 +00:00
feat(web): markdown Source | Preview toggle in session file pane (#957)
* feat(web): markdown Source | Preview toggle in session file pane Add Source | Preview toggle for .md/.mdx files in the session file route, defaulting to preview with localStorage persistence. Reuse chat markdown pipeline via MarkdownRenderer standalone mode (no assistant-ui thread). Includes unit tests, Playwright smoke, and e2e fixture. Closes tiann/hapi#954 Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): cast standalone MarkdownRenderer components for react-markdown Soup verify gate: defaultComponents merge type is wider than react-markdown Components; standalone file-pane path needs explicit cast. * fix(web): route file-pane markdown fences through SyntaxHighlighter Standalone file preview now mirrors chat code-block rendering: fenced blocks use SyntaxHighlighter and MARKDOWN_COMPONENTS_BY_LANGUAGE (mermaid included) without requiring ThreadPrimitive context. Addresses HAPI Bot Major on tiann/hapi#957. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): detect fenced vs inline code in standalone markdown preview Move block detection to the pre override (react-markdown v10 does not pass inline to custom code components). Add inline-code regression test. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Standalone Vite-served fixture for the file-pane markdown Source |
|
||||
* Preview Playwright smoke. Mounts the same toggle + MarkdownRenderer
|
||||
* path as `file.tsx` without the HAPI auth / git / socket stack.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import '../src/index.css'
|
||||
import { I18nProvider } from '../src/lib/i18n-context'
|
||||
import { MarkdownRenderer } from '../src/components/MarkdownRenderer'
|
||||
import {
|
||||
getInitialMarkdownPreviewMode,
|
||||
persistMarkdownPreviewMode,
|
||||
type MarkdownPreviewMode,
|
||||
} from '../src/lib/file-markdown-preview'
|
||||
import { useTranslation } from '../src/lib/use-translation'
|
||||
|
||||
const SAMPLE_MARKDOWN = `# Teams and channels
|
||||
|
||||
| Channel | Purpose |
|
||||
| --- | --- |
|
||||
| general | Day-to-day coordination |
|
||||
| incidents | Outage response |
|
||||
|
||||
\`\`\`ts
|
||||
export const ok = true
|
||||
\`\`\`
|
||||
|
||||
> Preview uses the same markdown pipeline as chat.
|
||||
`
|
||||
|
||||
function FileMarkdownPreviewFixture() {
|
||||
const { t } = useTranslation()
|
||||
const [mode, setMode] = React.useState<MarkdownPreviewMode>(() => getInitialMarkdownPreviewMode())
|
||||
const showSource = mode === 'source'
|
||||
|
||||
const setMarkdownPreviewMode = (next: MarkdownPreviewMode) => {
|
||||
setMode(next)
|
||||
persistMarkdownPreviewMode(next)
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid="file-md-preview-fixture" className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="markdown-mode-source"
|
||||
onClick={() => setMarkdownPreviewMode('source')}
|
||||
className={`rounded px-3 py-1 text-xs font-semibold ${showSource ? 'bg-[var(--app-button)] text-[var(--app-button-text)] opacity-80' : 'bg-[var(--app-subtle-bg)] text-[var(--app-hint)]'}`}
|
||||
>
|
||||
{t('file.page.tab.source')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="markdown-mode-preview"
|
||||
onClick={() => setMarkdownPreviewMode('preview')}
|
||||
className={`rounded px-3 py-1 text-xs font-semibold ${!showSource ? 'bg-[var(--app-button)] text-[var(--app-button-text)] opacity-80' : 'bg-[var(--app-subtle-bg)] text-[var(--app-hint)]'}`}
|
||||
>
|
||||
{t('file.page.tab.preview')}
|
||||
</button>
|
||||
</div>
|
||||
{showSource ? (
|
||||
<pre
|
||||
data-testid="markdown-source-view"
|
||||
className="overflow-auto rounded-md bg-[var(--app-code-bg)] p-3 text-xs font-mono"
|
||||
>
|
||||
<code>{SAMPLE_MARKDOWN}</code>
|
||||
</pre>
|
||||
) : (
|
||||
<div data-testid="markdown-preview-view" className="markdown-content">
|
||||
<MarkdownRenderer content={SAMPLE_MARKDOWN} standalone />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const rootEl = document.getElementById('root')
|
||||
if (rootEl) {
|
||||
ReactDOM.createRoot(rootEl).render(
|
||||
<React.StrictMode>
|
||||
<I18nProvider>
|
||||
<FileMarkdownPreviewFixture />
|
||||
</I18nProvider>
|
||||
</React.StrictMode>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user