mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 14:16:43 +00:00
fix(ui): render codex config as exact text (#1215)
This commit is contained in:
@@ -23,6 +23,7 @@ interface RawConfigEditorPanelProps {
|
||||
onRefresh: () => Promise<void> | void;
|
||||
onDiscard?: () => void;
|
||||
language?: 'json' | 'yaml' | 'toml';
|
||||
plainText?: boolean;
|
||||
loadingLabel?: string;
|
||||
parseWarningLabel?: string;
|
||||
ownershipNotice?: ReactNode;
|
||||
@@ -44,6 +45,7 @@ export function RawConfigEditorPanel({
|
||||
onRefresh,
|
||||
onDiscard,
|
||||
language = 'json',
|
||||
plainText = false,
|
||||
loadingLabel = 'Loading settings.json...',
|
||||
parseWarningLabel = 'Parse warning',
|
||||
ownershipNotice,
|
||||
@@ -128,6 +130,7 @@ export function RawConfigEditorPanel({
|
||||
onChange={onChange}
|
||||
language={language}
|
||||
readonly={readOnly}
|
||||
plainText={plainText}
|
||||
minHeight="100%"
|
||||
heightMode="fill-parent"
|
||||
/>
|
||||
|
||||
@@ -24,6 +24,7 @@ interface CodeEditorProps {
|
||||
onChange: (value: string) => void;
|
||||
language?: 'json' | 'yaml' | 'toml';
|
||||
readonly?: boolean;
|
||||
plainText?: boolean;
|
||||
className?: string;
|
||||
minHeight?: string;
|
||||
heightMode?: 'content' | 'fill-parent';
|
||||
@@ -95,6 +96,7 @@ export function CodeEditor({
|
||||
onChange,
|
||||
language = 'json',
|
||||
readonly = false,
|
||||
plainText = false,
|
||||
className,
|
||||
minHeight = '300px',
|
||||
heightMode = 'content',
|
||||
@@ -211,40 +213,67 @@ export function CodeEditor({
|
||||
className={cn(isFillParent && 'scrollbar-editor min-h-0 flex-1 overflow-auto')}
|
||||
data-slot={isFillParent ? 'code-editor-viewport' : undefined}
|
||||
>
|
||||
<Editor
|
||||
value={value}
|
||||
onValueChange={readonly ? () => {} : onChange}
|
||||
highlight={highlightCode}
|
||||
key={isDark ? 'dark-editor' : 'light-editor'}
|
||||
padding={12}
|
||||
disabled={readonly}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
textareaClassName={cn(
|
||||
'focus:outline-none font-mono text-sm',
|
||||
readonly && 'cursor-not-allowed'
|
||||
)}
|
||||
preClassName="font-mono text-sm"
|
||||
style={{
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
||||
fontSize: '0.875rem',
|
||||
minHeight,
|
||||
}}
|
||||
/>
|
||||
{plainText ? (
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(event) => {
|
||||
if (!readonly) onChange(event.target.value);
|
||||
}}
|
||||
readOnly={readonly}
|
||||
wrap="off"
|
||||
spellCheck={false}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
className={cn(
|
||||
'block w-full resize-none border-0 bg-transparent p-3 font-mono text-sm leading-relaxed',
|
||||
'whitespace-pre overflow-auto focus:outline-none',
|
||||
isFillParent ? 'h-full min-h-full' : 'min-h-[300px]',
|
||||
readonly && 'cursor-not-allowed'
|
||||
)}
|
||||
style={{
|
||||
minHeight,
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
data-slot="code-editor-plain-textarea"
|
||||
/>
|
||||
) : (
|
||||
<Editor
|
||||
value={value}
|
||||
onValueChange={readonly ? () => {} : onChange}
|
||||
highlight={highlightCode}
|
||||
key={isDark ? 'dark-editor' : 'light-editor'}
|
||||
padding={12}
|
||||
disabled={readonly}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
textareaClassName={cn(
|
||||
'focus:outline-none font-mono text-sm',
|
||||
readonly && 'cursor-not-allowed'
|
||||
)}
|
||||
preClassName="font-mono text-sm"
|
||||
style={{
|
||||
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
||||
fontSize: '0.875rem',
|
||||
minHeight,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Secrets Toggle Overlay */}
|
||||
<div className="absolute top-2 right-2 z-10 opacity-50 hover:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 bg-background/50 hover:bg-background border shadow-sm rounded-full"
|
||||
onClick={() => setIsMasked(!isMasked)}
|
||||
title={isMasked ? t('codeEditor.revealSensitive') : t('codeEditor.maskSensitive')}
|
||||
>
|
||||
{isMasked ? <Eye className="h-3 w-3" /> : <EyeOff className="h-3 w-3" />}
|
||||
</Button>
|
||||
</div>
|
||||
{!plainText && (
|
||||
<div className="absolute top-2 right-2 z-10 opacity-50 hover:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 bg-background/50 hover:bg-background border shadow-sm rounded-full"
|
||||
onClick={() => setIsMasked(!isMasked)}
|
||||
title={isMasked ? t('codeEditor.revealSensitive') : t('codeEditor.maskSensitive')}
|
||||
>
|
||||
{isMasked ? <Eye className="h-3 w-3" /> : <EyeOff className="h-3 w-3" />}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Validation status */}
|
||||
|
||||
@@ -253,6 +253,7 @@ export function CodexPage() {
|
||||
onRefresh={refreshAll}
|
||||
onDiscard={() => setRawDraftText(null)}
|
||||
language="toml"
|
||||
plainText
|
||||
/* TODO i18n: missing key for "Loading config.toml..." */
|
||||
loadingLabel="Loading config.toml..."
|
||||
/* TODO i18n: missing key for "TOML warning" */
|
||||
|
||||
@@ -67,4 +67,32 @@ describe('CodeEditor', () => {
|
||||
|
||||
expect(screen.getByText('Valid TOML')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('can render raw TOML as plain text so visible lines match copied text', () => {
|
||||
const rawToml =
|
||||
'model = "gpt-5.4"\n' +
|
||||
'[mcp_servers.playwright]\n' +
|
||||
'command = "node"\n' +
|
||||
'args = ["--experimental-loader", "./very/long/path/that/should/not/soft/wrap/into/fake/toml/lines.js"]\n';
|
||||
|
||||
const { container } = render(
|
||||
<CodeEditor
|
||||
value={rawToml}
|
||||
onChange={vi.fn()}
|
||||
language="toml"
|
||||
minHeight="100%"
|
||||
heightMode="fill-parent"
|
||||
plainText
|
||||
/>
|
||||
);
|
||||
|
||||
const textarea = container.querySelector('[data-slot="code-editor-plain-textarea"]');
|
||||
|
||||
expect(textarea).toBeInstanceOf(HTMLTextAreaElement);
|
||||
expect(textarea).toHaveValue(rawToml);
|
||||
expect(textarea).toHaveAttribute('wrap', 'off');
|
||||
expect(container.querySelector('pre')).not.toBeInTheDocument();
|
||||
expect(container.querySelector('button')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Valid TOML')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user