mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
Replace rewrite modal with anchored popover
This commit is contained in:
parent
2c2730926a
commit
17beaeb276
4 changed files with 341 additions and 184 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { MarkdownView, Notice, type Editor, type Plugin } from "obsidian";
|
||||
|
||||
import { RewriteSelectionModal } from "./modal";
|
||||
import type { RewriteRuntimeSettings, RewriteSession } from "./model";
|
||||
import { RewriteSelectionPopover } from "./popover";
|
||||
|
||||
export interface RewriteSelectionCommandHost extends Plugin {
|
||||
settings: {
|
||||
|
|
@ -41,7 +41,7 @@ export function registerRewriteSelectionCommand(plugin: RewriteSelectionCommandH
|
|||
debugText: null,
|
||||
};
|
||||
|
||||
new RewriteSelectionModal(plugin.app, {
|
||||
new RewriteSelectionPopover({
|
||||
codexPath: plugin.settings.codexPath,
|
||||
cwd: plugin.vaultPath,
|
||||
editor,
|
||||
|
|
|
|||
|
|
@ -1,158 +0,0 @@
|
|||
import { Modal, Notice, type App, type Editor } from "obsidian";
|
||||
|
||||
import { renderUnifiedDiff } from "../ui/turn-diff";
|
||||
import { buildSelectionUnifiedDiff } from "./diff";
|
||||
import { canApplyRewrite, type RewriteRuntimeSettings, type RewriteSession } from "./model";
|
||||
import { RewriteOutputError } from "./output";
|
||||
import { buildRewritePrompt } from "./prompt";
|
||||
import { runRewriteSelection } from "./runner";
|
||||
|
||||
export interface RewriteSelectionModalOptions {
|
||||
codexPath: string;
|
||||
cwd: string;
|
||||
editor: Editor;
|
||||
runtimeSettings: RewriteRuntimeSettings;
|
||||
session: RewriteSession;
|
||||
}
|
||||
|
||||
export class RewriteSelectionModal extends Modal {
|
||||
private instructionEl: HTMLTextAreaElement | null = null;
|
||||
private generateButton: HTMLButtonElement | null = null;
|
||||
private applyButton: HTMLButtonElement | null = null;
|
||||
private statusEl: HTMLElement | null = null;
|
||||
private previewEl: HTMLElement | null = null;
|
||||
private diffEl: HTMLElement | null = null;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
private readonly options: RewriteSelectionModalOptions,
|
||||
) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.setTitle("Rewrite selection");
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.addClass("codex-panel-rewrite");
|
||||
|
||||
this.instructionEl = contentEl.createEl("textarea", {
|
||||
cls: "codex-panel-rewrite__instruction",
|
||||
attr: { placeholder: "How should Codex rewrite the selected text?" },
|
||||
});
|
||||
this.instructionEl.value = this.options.session.instruction;
|
||||
this.instructionEl.oninput = () => this.syncControls();
|
||||
|
||||
const controls = contentEl.createDiv({ cls: "codex-panel-rewrite__controls" });
|
||||
const actions = controls.createDiv({ cls: "codex-panel-rewrite__actions" });
|
||||
this.generateButton = actions.createEl("button", { text: "Generate", attr: { type: "button" } });
|
||||
this.generateButton.onclick = () => void this.generate();
|
||||
const cancelButton = actions.createEl("button", { text: "Cancel", attr: { type: "button" } });
|
||||
cancelButton.onclick = () => {
|
||||
this.options.session.status = "cancelled";
|
||||
this.close();
|
||||
};
|
||||
|
||||
this.statusEl = contentEl.createDiv({ cls: "codex-panel-rewrite__status" });
|
||||
this.previewEl = contentEl.createEl("pre", { cls: "codex-panel-rewrite__preview" });
|
||||
this.diffEl = contentEl.createDiv({ cls: "codex-panel-rewrite__diff" });
|
||||
|
||||
const footer = contentEl.createDiv({ cls: "codex-panel-rewrite__footer" });
|
||||
this.applyButton = footer.createEl("button", {
|
||||
text: "Apply",
|
||||
cls: "mod-cta",
|
||||
attr: { type: "button" },
|
||||
});
|
||||
this.applyButton.onclick = () => this.apply();
|
||||
|
||||
this.setStatus("Enter an instruction, then generate a patch.");
|
||||
this.syncControls();
|
||||
this.instructionEl.focus();
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
|
||||
private async generate(): Promise<void> {
|
||||
const instruction = this.instructionEl?.value.trim() ?? "";
|
||||
if (!instruction) {
|
||||
new Notice("Enter a rewrite instruction first.");
|
||||
this.instructionEl?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
this.options.session.instruction = instruction;
|
||||
this.options.session.status = "generating";
|
||||
this.options.session.streamText = "";
|
||||
this.options.session.replacementText = null;
|
||||
this.options.session.debugText = null;
|
||||
this.previewEl?.setText("");
|
||||
this.diffEl?.empty();
|
||||
this.setStatus("Generating patch...");
|
||||
this.syncControls();
|
||||
|
||||
try {
|
||||
const output = await runRewriteSelection({
|
||||
codexPath: this.options.codexPath,
|
||||
cwd: this.options.cwd,
|
||||
prompt: buildRewritePrompt(this.options.session),
|
||||
runtimeSettings: this.options.runtimeSettings,
|
||||
onPreview: (text) => this.updatePreview(text),
|
||||
});
|
||||
this.options.session.replacementText = output.replacementText;
|
||||
this.options.session.status = "preview";
|
||||
this.renderDiff();
|
||||
this.setStatus("Review the patch before applying it.");
|
||||
} catch (error) {
|
||||
this.options.session.status = "failed";
|
||||
this.options.session.debugText = error instanceof RewriteOutputError ? error.rawText : null;
|
||||
this.setStatus(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
this.syncControls();
|
||||
}
|
||||
}
|
||||
|
||||
private updatePreview(text: string): void {
|
||||
this.options.session.streamText = text;
|
||||
this.previewEl?.setText(text);
|
||||
}
|
||||
|
||||
private renderDiff(): void {
|
||||
const replacement = this.options.session.replacementText;
|
||||
if (replacement === null || !this.diffEl) return;
|
||||
this.diffEl.empty();
|
||||
renderUnifiedDiff(
|
||||
this.diffEl,
|
||||
buildSelectionUnifiedDiff(this.options.session.filePath, this.options.session.originalText, replacement),
|
||||
);
|
||||
}
|
||||
|
||||
private apply(): void {
|
||||
const replacement = this.options.session.replacementText;
|
||||
if (replacement === null) return;
|
||||
|
||||
const { editor, session } = this.options;
|
||||
const currentText = editor.getRange(session.targetRange.from, session.targetRange.to);
|
||||
if (!canApplyRewrite(currentText, session.originalText)) {
|
||||
new Notice("Selection changed. Generate the rewrite again before applying.");
|
||||
return;
|
||||
}
|
||||
|
||||
editor.replaceRange(replacement, session.targetRange.from, session.targetRange.to, "codex-panel-rewrite");
|
||||
session.status = "applied";
|
||||
this.close();
|
||||
}
|
||||
|
||||
private setStatus(text: string): void {
|
||||
this.statusEl?.setText(text);
|
||||
}
|
||||
|
||||
private syncControls(): void {
|
||||
const generating = this.options.session.status === "generating";
|
||||
const hasInstruction = Boolean(this.instructionEl?.value.trim());
|
||||
if (this.instructionEl) this.instructionEl.disabled = generating;
|
||||
if (this.generateButton) this.generateButton.disabled = generating || !hasInstruction;
|
||||
if (this.applyButton) this.applyButton.disabled = generating || this.options.session.replacementText === null;
|
||||
}
|
||||
}
|
||||
293
src/editor-rewrite/popover.ts
Normal file
293
src/editor-rewrite/popover.ts
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
import { Notice, type Editor } from "obsidian";
|
||||
|
||||
import { renderUnifiedDiff } from "../ui/turn-diff";
|
||||
import { buildSelectionUnifiedDiff } from "./diff";
|
||||
import { canApplyRewrite, type RewriteRuntimeSettings, type RewriteSession } from "./model";
|
||||
import { RewriteOutputError } from "./output";
|
||||
import { buildRewritePrompt } from "./prompt";
|
||||
import { runRewriteSelection } from "./runner";
|
||||
|
||||
const POPOVER_MARGIN = 8;
|
||||
|
||||
export interface RewriteSelectionPopoverOptions {
|
||||
codexPath: string;
|
||||
cwd: string;
|
||||
editor: Editor;
|
||||
runtimeSettings: RewriteRuntimeSettings;
|
||||
session: RewriteSession;
|
||||
}
|
||||
|
||||
type Cleanup = () => void;
|
||||
|
||||
export class RewriteSelectionPopover {
|
||||
private rootEl: HTMLElement | null = null;
|
||||
private instructionEl: HTMLTextAreaElement | null = null;
|
||||
private generateButton: HTMLButtonElement | null = null;
|
||||
private applyButton: HTMLButtonElement | null = null;
|
||||
private statusEl: HTMLElement | null = null;
|
||||
private previewEl: HTMLElement | null = null;
|
||||
private diffEl: HTMLElement | null = null;
|
||||
private debugEl: HTMLDetailsElement | null = null;
|
||||
private readonly cleanups: Cleanup[] = [];
|
||||
|
||||
constructor(private readonly options: RewriteSelectionPopoverOptions) {}
|
||||
|
||||
open(): void {
|
||||
this.close();
|
||||
|
||||
const root = activeDocument.body.createDiv({ cls: "codex-panel-rewrite-popover" });
|
||||
root.setAttr("role", "dialog");
|
||||
root.setAttr("aria-label", "Rewrite selection");
|
||||
this.rootEl = root;
|
||||
|
||||
const instructionEl = root.createEl("textarea", {
|
||||
cls: "codex-panel-rewrite-popover__instruction",
|
||||
attr: { placeholder: "How should Codex rewrite the selected text?" },
|
||||
});
|
||||
instructionEl.value = this.options.session.instruction;
|
||||
instructionEl.oninput = () => this.syncControls();
|
||||
this.instructionEl = instructionEl;
|
||||
|
||||
const controls = root.createDiv({ cls: "codex-panel-rewrite-popover__controls" });
|
||||
const generateButton = controls.createEl("button", { text: "Generate", attr: { type: "button" } });
|
||||
generateButton.onclick = () => void this.generate();
|
||||
this.generateButton = generateButton;
|
||||
const cancelButton = controls.createEl("button", { text: "Cancel", attr: { type: "button" } });
|
||||
cancelButton.onclick = () => {
|
||||
this.options.session.status = "cancelled";
|
||||
this.close();
|
||||
};
|
||||
|
||||
this.statusEl = root.createDiv({ cls: "codex-panel-rewrite-popover__status" });
|
||||
this.previewEl = root.createEl("pre", { cls: "codex-panel-rewrite-popover__preview" });
|
||||
this.diffEl = root.createDiv({ cls: "codex-panel-rewrite-popover__diff" });
|
||||
const debugEl = root.createEl("details", { cls: "codex-panel-rewrite-popover__debug" });
|
||||
debugEl.createEl("summary", { text: "Debug output" });
|
||||
this.debugEl = debugEl;
|
||||
|
||||
const footer = root.createDiv({ cls: "codex-panel-rewrite-popover__footer" });
|
||||
const applyButton = footer.createEl("button", {
|
||||
text: "Apply",
|
||||
cls: "mod-cta",
|
||||
attr: { type: "button" },
|
||||
});
|
||||
applyButton.onclick = () => this.apply();
|
||||
this.applyButton = applyButton;
|
||||
|
||||
this.addDomListener(activeWindow, "resize", () => this.position());
|
||||
this.addDomListener(activeWindow, "scroll", () => this.position(), true);
|
||||
this.addDomListener(activeDocument, "keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
this.options.session.status = "cancelled";
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
this.setStatus("Enter an instruction, then generate a patch.");
|
||||
this.syncControls();
|
||||
this.position();
|
||||
instructionEl.focus();
|
||||
}
|
||||
|
||||
close(): void {
|
||||
for (const cleanup of this.cleanups.splice(0)) cleanup();
|
||||
this.rootEl?.remove();
|
||||
this.rootEl = null;
|
||||
this.instructionEl = null;
|
||||
this.generateButton = null;
|
||||
this.applyButton = null;
|
||||
this.statusEl = null;
|
||||
this.previewEl = null;
|
||||
this.diffEl = null;
|
||||
this.debugEl = null;
|
||||
}
|
||||
|
||||
private async generate(): Promise<void> {
|
||||
const instruction = this.instructionEl?.value.trim() ?? "";
|
||||
if (!instruction) {
|
||||
new Notice("Enter a rewrite instruction first.");
|
||||
this.instructionEl?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
this.options.session.instruction = instruction;
|
||||
this.options.session.status = "generating";
|
||||
this.options.session.streamText = "";
|
||||
this.options.session.replacementText = null;
|
||||
this.options.session.debugText = null;
|
||||
this.previewEl?.setText("");
|
||||
this.diffEl?.empty();
|
||||
this.renderDebug();
|
||||
this.setStatus("Generating patch...");
|
||||
this.syncControls();
|
||||
|
||||
try {
|
||||
const output = await runRewriteSelection({
|
||||
codexPath: this.options.codexPath,
|
||||
cwd: this.options.cwd,
|
||||
prompt: buildRewritePrompt(this.options.session),
|
||||
runtimeSettings: this.options.runtimeSettings,
|
||||
onPreview: (text) => this.updatePreview(text),
|
||||
});
|
||||
this.options.session.replacementText = output.replacementText;
|
||||
this.options.session.status = "preview";
|
||||
this.renderDiff();
|
||||
this.setStatus("Review the patch before applying it.");
|
||||
} catch (error) {
|
||||
this.options.session.status = "failed";
|
||||
this.options.session.debugText = error instanceof RewriteOutputError ? error.rawText : null;
|
||||
this.renderDebug();
|
||||
this.setStatus(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
this.syncControls();
|
||||
this.position();
|
||||
}
|
||||
}
|
||||
|
||||
private updatePreview(text: string): void {
|
||||
this.options.session.streamText = text;
|
||||
this.previewEl?.setText(text);
|
||||
this.position();
|
||||
}
|
||||
|
||||
private renderDiff(): void {
|
||||
const replacement = this.options.session.replacementText;
|
||||
if (replacement === null || !this.diffEl) return;
|
||||
this.diffEl.empty();
|
||||
renderUnifiedDiff(
|
||||
this.diffEl,
|
||||
buildSelectionUnifiedDiff(this.options.session.filePath, this.options.session.originalText, replacement),
|
||||
);
|
||||
}
|
||||
|
||||
private renderDebug(): void {
|
||||
if (!this.debugEl) return;
|
||||
const debugText = this.options.session.debugText;
|
||||
this.debugEl.empty();
|
||||
this.debugEl.createEl("summary", { text: "Debug output" });
|
||||
if (debugText) {
|
||||
this.debugEl.createEl("pre", { text: debugText });
|
||||
this.debugEl.classList.remove("is-hidden");
|
||||
} else {
|
||||
this.debugEl.classList.add("is-hidden");
|
||||
}
|
||||
}
|
||||
|
||||
private apply(): void {
|
||||
const replacement = this.options.session.replacementText;
|
||||
if (replacement === null) return;
|
||||
|
||||
const { editor, session } = this.options;
|
||||
const currentText = editor.getRange(session.targetRange.from, session.targetRange.to);
|
||||
if (!canApplyRewrite(currentText, session.originalText)) {
|
||||
new Notice("Selection changed. Generate the rewrite again before applying.");
|
||||
this.setStatus("Selection changed. Generate the rewrite again before applying.");
|
||||
return;
|
||||
}
|
||||
|
||||
editor.replaceRange(replacement, session.targetRange.from, session.targetRange.to, "codex-panel-rewrite");
|
||||
session.status = "applied";
|
||||
this.close();
|
||||
}
|
||||
|
||||
private setStatus(text: string): void {
|
||||
this.statusEl?.setText(text);
|
||||
}
|
||||
|
||||
private syncControls(): void {
|
||||
const generating = this.options.session.status === "generating";
|
||||
const hasInstruction = Boolean(this.instructionEl?.value.trim());
|
||||
if (this.instructionEl) this.instructionEl.disabled = generating;
|
||||
if (this.generateButton) {
|
||||
this.generateButton.disabled = generating || !hasInstruction;
|
||||
this.generateButton.setText(this.options.session.replacementText === null ? "Generate" : "Regenerate");
|
||||
}
|
||||
if (this.applyButton) this.applyButton.disabled = generating || this.options.session.replacementText === null;
|
||||
}
|
||||
|
||||
private position(): void {
|
||||
const root = this.rootEl;
|
||||
if (!root || !root.isConnected) return;
|
||||
|
||||
const view = editorViewFromEditor(this.options.editor);
|
||||
if (view?.dom instanceof HTMLElement && !view.dom.isConnected) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const anchor = selectionRect() ?? editorCursorRect(this.options.editor) ?? root.ownerDocument.body.getBoundingClientRect();
|
||||
const size = root.getBoundingClientRect();
|
||||
const viewportWidth = activeWindow.innerWidth;
|
||||
const viewportHeight = activeWindow.innerHeight;
|
||||
const left = clamp(anchor.left, POPOVER_MARGIN, viewportWidth - size.width - POPOVER_MARGIN);
|
||||
const belowTop = anchor.bottom + POPOVER_MARGIN;
|
||||
const aboveTop = anchor.top - size.height - POPOVER_MARGIN;
|
||||
const top =
|
||||
belowTop + size.height <= viewportHeight - POPOVER_MARGIN
|
||||
? belowTop
|
||||
: clamp(aboveTop, POPOVER_MARGIN, viewportHeight - size.height - POPOVER_MARGIN);
|
||||
|
||||
root.style.left = `${left}px`;
|
||||
root.style.top = `${top}px`;
|
||||
}
|
||||
|
||||
private addDomListener<K extends keyof WindowEventMap>(
|
||||
target: Window,
|
||||
type: K,
|
||||
callback: (event: WindowEventMap[K]) => void,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
private addDomListener<K extends keyof DocumentEventMap>(
|
||||
target: Document,
|
||||
type: K,
|
||||
callback: (event: DocumentEventMap[K]) => void,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
private addDomListener(
|
||||
target: Window | Document,
|
||||
type: string,
|
||||
callback: EventListener,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void {
|
||||
target.addEventListener(type, callback, options);
|
||||
this.cleanups.push(() => target.removeEventListener(type, callback, options));
|
||||
}
|
||||
}
|
||||
|
||||
function selectionRect(): DOMRect | null {
|
||||
const selection = activeWindow.getSelection();
|
||||
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return null;
|
||||
const rect = selection.getRangeAt(0).getBoundingClientRect();
|
||||
return rect.width > 0 || rect.height > 0 ? rect : null;
|
||||
}
|
||||
|
||||
function editorCursorRect(editor: Editor): DOMRect | null {
|
||||
const view = editorViewFromEditor(editor);
|
||||
if (!view) return null;
|
||||
|
||||
const offset = editor.posToOffset(editor.getCursor("to"));
|
||||
if (typeof view.coordsAtPos === "function") {
|
||||
return view.coordsAtPos(offset, 1) ?? view.coordsAtPos(offset, -1) ?? null;
|
||||
}
|
||||
return view.dom instanceof HTMLElement ? view.dom.getBoundingClientRect() : null;
|
||||
}
|
||||
|
||||
function editorViewFromEditor(editor: Editor): { coordsAtPos?: (pos: number, side?: -1 | 1) => DOMRect | null; dom?: unknown } | null {
|
||||
const candidate = editor as {
|
||||
cm?: unknown;
|
||||
editor?: { cm?: unknown };
|
||||
};
|
||||
if (isEditorView(candidate.cm)) return candidate.cm;
|
||||
if (isEditorView(candidate.editor?.cm)) return candidate.editor.cm;
|
||||
return null;
|
||||
}
|
||||
|
||||
function isEditorView(value: unknown): value is { coordsAtPos?: (pos: number, side?: -1 | 1) => DOMRect | null; dom?: unknown } {
|
||||
return Boolean(value && typeof value === "object" && ("coordsAtPos" in value || "dom" in value));
|
||||
}
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
if (max < min) return min;
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
70
styles.css
70
styles.css
|
|
@ -1394,44 +1394,43 @@
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.codex-panel-rewrite {
|
||||
.codex-panel-rewrite-popover {
|
||||
position: fixed;
|
||||
z-index: var(--layer-popover, 30);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-4-3, 12px);
|
||||
width: min(520px, calc(100vw - 24px));
|
||||
max-height: min(560px, calc(100vh - 24px));
|
||||
padding: var(--size-4-3, 12px);
|
||||
overflow: auto;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
background: var(--background-primary);
|
||||
box-shadow: var(--shadow-s);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.codex-panel-rewrite__instruction {
|
||||
min-height: 96px;
|
||||
.codex-panel-rewrite-popover__instruction {
|
||||
min-height: 92px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.codex-panel-rewrite__controls,
|
||||
.codex-panel-rewrite__footer {
|
||||
.codex-panel-rewrite-popover__controls,
|
||||
.codex-panel-rewrite-popover__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
gap: var(--size-4-2, 8px);
|
||||
}
|
||||
|
||||
.codex-panel-rewrite__context {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--size-2-3, 6px);
|
||||
.codex-panel-rewrite-popover__status {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-small);
|
||||
}
|
||||
|
||||
.codex-panel-rewrite__actions {
|
||||
display: inline-flex;
|
||||
gap: var(--size-2-3, 6px);
|
||||
}
|
||||
|
||||
.codex-panel-rewrite__status {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-small);
|
||||
}
|
||||
|
||||
.codex-panel-rewrite__preview {
|
||||
.codex-panel-rewrite-popover__preview {
|
||||
display: none;
|
||||
max-height: 120px;
|
||||
overflow: auto;
|
||||
|
|
@ -1445,13 +1444,36 @@
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.codex-panel-rewrite__preview:not(:empty) {
|
||||
.codex-panel-rewrite-popover__preview:not(:empty) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.codex-panel-rewrite__diff .codex-panel__diff {
|
||||
max-height: min(360px, 45vh);
|
||||
.codex-panel-rewrite-popover__diff .codex-panel__diff {
|
||||
max-height: min(300px, 38vh);
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.codex-panel-rewrite-popover__debug {
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-small);
|
||||
}
|
||||
|
||||
.codex-panel-rewrite-popover__debug.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.codex-panel-rewrite-popover__debug pre {
|
||||
max-height: 160px;
|
||||
overflow: auto;
|
||||
margin: var(--size-2-3, 6px) 0 0;
|
||||
padding: 8px;
|
||||
border-radius: var(--radius-s);
|
||||
background: var(--code-background, var(--background-secondary));
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-monospace);
|
||||
font-size: var(--font-smaller);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue