This commit is contained in:
@@ -392,7 +392,7 @@ export function ChatMessage({ comment, taskId, imageBaseUrl, isStaleThinking }:
|
||||
<div className="text-2xs text-green-500 mb-1.5">
|
||||
{author} · {new Date(createdAt).toLocaleString()}
|
||||
</div>
|
||||
<div className="text-sm leading-relaxed prose prose-sm prose-slate max-w-none">
|
||||
<div className="text-sm leading-relaxed prose prose-sm prose-slate dark:prose-invert max-w-none">
|
||||
<MarkdownPreview content={body} imageBaseUrl={imageBaseUrl ?? `/api/local/tasks/${taskId}/files/raw?section=output&path=`} taskId={taskId} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -328,7 +328,7 @@ export function MarkdownPreview({ content, imageBaseUrl, taskId, showOutline = f
|
||||
};
|
||||
|
||||
const content_el = (
|
||||
<div ref={containerRef} className={`${showOutline ? 'prose prose-slate max-w-none mdxg-reader' : 'prose prose-sm max-w-none'} min-w-0 break-words [&_a]:[overflow-wrap:anywhere] [&_a]:break-all [&_code]:[overflow-wrap:anywhere] [&_pre]:max-w-full [&_pre]:overflow-x-auto`}>
|
||||
<div ref={containerRef} className={`${showOutline ? 'prose prose-slate dark:prose-invert max-w-none mdxg-reader' : 'prose prose-sm dark:prose-invert max-w-none'} min-w-0 break-words [&_a]:[overflow-wrap:anywhere] [&_a]:break-all [&_code]:[overflow-wrap:anywhere] [&_pre]:max-w-full [&_pre]:overflow-x-auto`}>
|
||||
{segments.map((seg, i) => {
|
||||
if (seg.type === 'embed') {
|
||||
return <EmbedBlock key={`embed-${seg.refId}-${i}`} refId={seg.refId} taskId={taskId!} />;
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useState } from 'react';
|
||||
import { type ThemePref, readStoredTheme, setThemePref } from '../../lib/theme';
|
||||
|
||||
const ICON_PROPS = {
|
||||
width: 14,
|
||||
height: 14,
|
||||
viewBox: '0 0 24 24',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: 1.8,
|
||||
strokeLinecap: 'round' as const,
|
||||
strokeLinejoin: 'round' as const,
|
||||
'aria-hidden': true,
|
||||
};
|
||||
|
||||
const OPTIONS: Array<{ value: ThemePref; label: string; icon: JSX.Element }> = [
|
||||
{
|
||||
value: 'system',
|
||||
label: 'システム設定に合わせる',
|
||||
icon: (
|
||||
<svg {...ICON_PROPS}>
|
||||
<rect x="2" y="4" width="20" height="13" rx="2" />
|
||||
<path d="M8 21h8M12 17v4" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'light',
|
||||
label: 'ライト',
|
||||
icon: (
|
||||
<svg {...ICON_PROPS}>
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'dark',
|
||||
label: 'ダーク',
|
||||
icon: (
|
||||
<svg {...ICON_PROPS}>
|
||||
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Compact 3-way theme switch (system / light / dark) for the TopBar.
|
||||
*
|
||||
* All theme logic lives in lib/theme.ts: this only tracks the displayed
|
||||
* preference and calls setThemePref (persist + apply) on selection. The
|
||||
* pre-paint inline script in index.html + initTheme() keep the actual
|
||||
* [data-theme] attribute correct on load and on OS changes.
|
||||
*/
|
||||
export function ThemeToggle() {
|
||||
const [pref, setPref] = useState<ThemePref>(() => readStoredTheme());
|
||||
|
||||
const choose = (value: ThemePref) => {
|
||||
setPref(value);
|
||||
setThemePref(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
role="group"
|
||||
aria-label="テーマ"
|
||||
className="inline-flex items-center gap-0.5 rounded-md border border-hairline bg-surface p-0.5"
|
||||
>
|
||||
{OPTIONS.map((opt) => {
|
||||
const active = pref === opt.value;
|
||||
return (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
onClick={() => choose(opt.value)}
|
||||
aria-label={opt.label}
|
||||
aria-pressed={active}
|
||||
title={opt.label}
|
||||
className={`flex items-center justify-center w-6 h-6 rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-ring ${
|
||||
active
|
||||
? 'bg-canvas text-slate-900 shadow-sm'
|
||||
: 'text-slate-500 hover:text-slate-800'
|
||||
}`}
|
||||
>
|
||||
{opt.icon}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { PageId } from '../../lib/urlState';
|
||||
import type { AuthUser } from '../../App';
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
|
||||
interface TopBarProps {
|
||||
currentPage: PageId;
|
||||
@@ -137,6 +138,7 @@ export function TopBar({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<ThemeToggle />
|
||||
{user && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
|
||||
Reference in New Issue
Block a user