Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/platforms/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
"diff": "^8.0.0",
"dompurify": "^3.3.1",
"highlight.js": "^11.11.1",
"katex": "^0.17.0",
"marked-katex-extension": "^5.1.10",
"react-icons": "^5.5.0"
}
}
2 changes: 1 addition & 1 deletion packages/platforms/vscode/src/chat-view-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; style-src ${webview.cspSource} 'nonce-${nonce}'; script-src 'nonce-${nonce}';" />
content="default-src 'none'; style-src ${webview.cspSource} 'nonce-${nonce}'; style-src-attr 'unsafe-inline'; style-src-elem ${webview.cspSource} 'nonce-${nonce}'; script-src 'nonce-${nonce}';" />
<link rel="stylesheet" href="${styleUri}" nonce="${nonce}" />
</head>
<body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe("TextPartView", () => {
it("HTML コンテンツをレンダリングすること", () => {
const part = createTextPart("Hello world");
const { container } = render(<TextPartView part={part} />);
expect(container.querySelector("span")).toBeInTheDocument();
expect(container.querySelector(".markdown")).toBeInTheDocument();
});

// renders the text
Expand Down Expand Up @@ -150,4 +150,21 @@ describe("TextPartView", () => {
spy.mockRestore();
});
});

// KaTeX display math with \tag{} renders a .tag element inside .katex-html
context("\\tag{} 付きディスプレイ数式の場合", () => {
it("\\tag{} 要素がレンダリングされること", () => {
const spy = vi
.spyOn(Marked.prototype, "parse")
.mockReturnValueOnce(
'<span class="katex-display"><span class="katex"><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.0585em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0359em;">y</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="mord mathnormal mtight">x</span></span></span></span></span></span></span></span></span></span><span class="tag"><span class="strut" style="height:1.1141em;vertical-align:-0.25em;"></span><span class="mord text"><span class="mord">(</span><span class="mord"><span class="mord">1</span></span><span class="mord">)</span></span></span></span></span></span>',
);
const part = createTextPart("$$\\tag{1} x+y^{2x}$$");
const { container } = render(<TextPartView part={part} />);
expect(container.querySelector(".katex-display")).toBeInTheDocument();
expect(container.querySelector(".katex-display .katex .katex-html .tag")).toBeInTheDocument();
expect(container.querySelector(".tag")!.textContent).toBe("(1)");
spy.mockRestore();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { TextPart } from "@opencodegui/core";
import DOMPurify from "dompurify";
import hljs from "highlight.js/lib/common";
import { Marked, type Renderer, type Tokens } from "marked";
import markedKatex from "marked-katex-extension";
import { createElement, useCallback, useMemo } from "react";
import { flushSync } from "react-dom";
import { createRoot } from "react-dom/client";
Expand Down Expand Up @@ -154,7 +155,11 @@ function linkifyAbsolutePaths(html: string): string {
}

// marked インスタンス(グローバル状態を汚染しない)
const markdownParser = new Marked({ breaks: true }, { renderer: { ...codeRenderer, ...linkRenderer } });
const markdownParser = new Marked(
{ breaks: true },
{ renderer: { ...codeRenderer, ...linkRenderer } },
markedKatex({ throwOnError: false, nonStandard: true }),
);

type Props = {
part: TextPart;
Expand Down Expand Up @@ -206,5 +211,5 @@ export function TextPartView({ part }: Props) {

// biome-ignore lint/security/noDangerouslySetInnerHtml: DOMPurify でサニタイズ済みの HTML を描画する
// biome-ignore lint/a11y/useKeyWithClickEvents: コピーボタンとファイルリンクのイベント委譲
return <span className="markdown" onClick={handleClick} dangerouslySetInnerHTML={{ __html: html }} />;
return <div className="markdown" onClick={handleClick} dangerouslySetInnerHTML={{ __html: html }} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,41 @@
margin-top: 0;
}

/* --- KaTeX (math) --- */

.content :global(.markdown .katex) {
font-size: 1.05em;
line-height: 1.25;
text-rendering: auto;
}

.content :global(.markdown .katex-display) {
display: block;
margin: 10px 0;
padding: 4px 0 10px;
overflow-x: auto;
overflow-y: visible;
line-height: 1.25;
text-align: center;
}

.content :global(.markdown .katex-display > .katex) {
display: block;
max-width: none;
white-space: nowrap;
text-align: center;
vertical-align: baseline;
}

.content :global(.markdown .katex-display > .katex > .katex-html) {
width: max-content;
min-width: 100%;
}

.content :global(.markdown p > .katex) {
vertical-align: -0.08em;
}

/* --- Code block wrapper with header & copy button --- */

.content :global(.code-block-wrapper) {
Expand Down
1 change: 1 addition & 0 deletions packages/platforms/vscode/webview/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
import "katex/dist/katex.min.css";
import "./styles.css";

createRoot(document.getElementById("root")!).render(
Expand Down
49 changes: 32 additions & 17 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading