fix(editor): 合并语句执行状态与运行按钮
This commit is contained in:
4 files changed
+219
-94
No files matched your search
@@ -56,7 +56,8 @@ import {
|
||||
type QueryEditorTableReferenceDropDetail,
|
||||
type QueryEditorTableReferencePayload,
|
||||
} from "@/lib/editor/queryEditorTableDrop";
|
||||
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, createRunStatementButtonDom, loadEditorTheme, editorFontTheme, sqlCompletionTheme, sqlSemanticHighlightTheme } from "@/lib/editor/editorThemes";
|
||||
import { EDITOR_FONT_FAMILY_CSS_VAR, EDITOR_FONT_SIZE_CSS_VAR, loadEditorTheme, editorFontTheme, sqlCompletionTheme, sqlSemanticHighlightTheme } from "@/lib/editor/editorThemes";
|
||||
import { createStatementGutterMarkerDom, shouldShowStatementGutter } from "@/lib/editor/codemirrorStatementGutter";
|
||||
import { clampEditorFontSize, createEditorZoomCommitScheduler, fontSizeFromGestureScale, fontSizeFromWheelDelta } from "@/lib/editor/editorZoom";
|
||||
import { normalizeShortcutSettings, shortcutToCodeMirrorKey } from "@/lib/editor/shortcutRegistry";
|
||||
import { trimmedSelectionLayer } from "@/lib/editor/codemirrorTrimmedSelectionLayer";
|
||||
@@ -261,7 +262,6 @@ let codeMirrorCloseBrackets: typeof import("@codemirror/autocomplete").closeBrac
|
||||
let codeMirrorCloseBracketsKeymap: readonly import("@codemirror/view").KeyBinding[] | null = null;
|
||||
let readOnlyComp: import("@codemirror/state").Compartment | null = null;
|
||||
let runGutterComp: import("@codemirror/state").Compartment | null = null;
|
||||
let statementExecutionGutterComp: import("@codemirror/state").Compartment | null = null;
|
||||
let runKeymapComp: import("@codemirror/state").Compartment | null = null;
|
||||
let completionComp: import("@codemirror/state").Compartment | null = null;
|
||||
let diagnosticComp: import("@codemirror/state").Compartment | null = null;
|
||||
@@ -310,8 +310,6 @@ let setStatementExecutionMarkersEffect: import("@codemirror/state").StateEffectT
|
||||
let previewRangeComp: import("@codemirror/state").Compartment | null = null;
|
||||
let buildPreviewRangeExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let buildResultSourceRangeExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let buildStatementExecutionMarkersExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let statementExecutionGutterMounted = false;
|
||||
let buildRunStatementGutterExtension: (() => import("@codemirror/state").Extension) | null = null;
|
||||
let indentComp: import("@codemirror/state").Compartment | null = null;
|
||||
let codeMirrorIndentUnit: typeof import("@codemirror/language").indentUnit | null = null;
|
||||
@@ -324,6 +322,11 @@ let tableReferenceDropListenerRegistered = false;
|
||||
let imeCompositionActive = false;
|
||||
let pendingImeModelEmit = false;
|
||||
|
||||
function runStatementGutterExtension(markers = props.statementExecutionMarkers ?? []): import("@codemirror/state").Extension {
|
||||
const showRunButtons = !props.hideExecutionControls && settingsStore.editorSettings.showStatementRunButtons;
|
||||
return shouldShowStatementGutter(showRunButtons, markers.length) ? (buildRunStatementGutterExtension?.() ?? []) : [];
|
||||
}
|
||||
|
||||
type SelectionCaseMode = "upper" | "lower";
|
||||
let executableStatementRangeCache: ExecutableStatementRangeCache | null = null;
|
||||
let editorScrollbarPointerCleanup: (() => void) | null = null;
|
||||
@@ -3004,7 +3007,6 @@ onMounted(async () => {
|
||||
codeMirrorCloseBracketsKeymap = closeBracketsKeymap;
|
||||
readOnlyComp = new Compartment();
|
||||
runGutterComp = new Compartment();
|
||||
statementExecutionGutterComp = new Compartment();
|
||||
runKeymapComp = new Compartment();
|
||||
completionComp = new Compartment();
|
||||
diagnosticComp = new Compartment();
|
||||
@@ -3139,23 +3141,35 @@ onMounted(async () => {
|
||||
return field;
|
||||
};
|
||||
|
||||
class StatementExecutionStatusMarker extends GutterMarker {
|
||||
class StatementExecutionStateMarker extends GutterMarker {
|
||||
constructor(readonly marker: StatementExecutionMarker) {
|
||||
super();
|
||||
}
|
||||
|
||||
eq(other: import("@codemirror/view").GutterMarker): boolean {
|
||||
return other instanceof StatementExecutionStatusMarker && other.marker.status === this.marker.status && other.marker.successCount === this.marker.successCount && other.marker.errorCount === this.marker.errorCount;
|
||||
return other instanceof StatementExecutionStateMarker && other.marker.status === this.marker.status && other.marker.successCount === this.marker.successCount && other.marker.errorCount === this.marker.errorCount;
|
||||
}
|
||||
}
|
||||
|
||||
class StatementGutterMarker extends GutterMarker {
|
||||
constructor(
|
||||
readonly canExecute: boolean,
|
||||
readonly marker?: StatementExecutionMarker,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
eq(other: import("@codemirror/view").GutterMarker): boolean {
|
||||
return other instanceof StatementGutterMarker && other.canExecute === this.canExecute && other.marker?.status === this.marker?.status && other.marker?.successCount === this.marker?.successCount && other.marker?.errorCount === this.marker?.errorCount;
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
const element = document.createElement("span");
|
||||
const title = statementExecutionMarkerTitle(this.marker);
|
||||
element.className = `cm-statement-execution-marker cm-statement-execution-marker--${this.marker.status}`;
|
||||
element.title = title;
|
||||
element.setAttribute("aria-label", title);
|
||||
element.appendChild(createStatementExecutionStatusIconDom(this.marker.status));
|
||||
return element;
|
||||
return createStatementGutterMarkerDom({
|
||||
canExecute: this.canExecute,
|
||||
executeLabel: t("editor.contextMenu.executeCurrent"),
|
||||
status: this.marker?.status,
|
||||
statusLabel: this.marker ? statementExecutionMarkerTitle(this.marker) : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3166,34 +3180,14 @@ onMounted(async () => {
|
||||
return parts.join(", ");
|
||||
}
|
||||
|
||||
function createStatementExecutionStatusIconDom(status: StatementExecutionMarker["status"]) {
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("viewBox", "0 0 24 24");
|
||||
svg.setAttribute("fill", "none");
|
||||
svg.setAttribute("stroke", "currentColor");
|
||||
svg.setAttribute("stroke-width", "2");
|
||||
svg.setAttribute("stroke-linecap", "round");
|
||||
svg.setAttribute("stroke-linejoin", "round");
|
||||
svg.setAttribute("aria-hidden", "true");
|
||||
|
||||
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
||||
circle.setAttribute("cx", "12");
|
||||
circle.setAttribute("cy", "12");
|
||||
circle.setAttribute("r", "10");
|
||||
const mark = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
mark.setAttribute("d", status === "error" ? "m15 9-6 6m0-6 6 6" : "m9 12 2 2 4-4");
|
||||
svg.append(circle, mark);
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
||||
setStatementExecutionMarkersEffect = StateEffect.define<StatementExecutionMarker[]>();
|
||||
buildStatementExecutionMarkersExtension = () => {
|
||||
buildRunStatementGutterExtension = () => {
|
||||
const effectType = setStatementExecutionMarkersEffect!;
|
||||
const showRunButtons = !props.hideExecutionControls && settingsStore.editorSettings.showStatementRunButtons;
|
||||
const markersForState = (state: import("@codemirror/state").EditorState, markers: readonly StatementExecutionMarker[]) => {
|
||||
const ranges = markers.map((marker) => {
|
||||
const from = Math.max(0, Math.min(marker.from, state.doc.length));
|
||||
return new StatementExecutionStatusMarker(marker).range(state.doc.lineAt(from).from);
|
||||
return new StatementExecutionStateMarker(marker).range(state.doc.lineAt(from).from);
|
||||
});
|
||||
return RangeSet.of(ranges, true);
|
||||
};
|
||||
@@ -3211,14 +3205,22 @@ onMounted(async () => {
|
||||
},
|
||||
provide: (field) =>
|
||||
gutter({
|
||||
class: "cm-statement-execution-gutter",
|
||||
class: "cm-run-statement-gutter",
|
||||
markers: (currentView) => currentView.state.field(field),
|
||||
lineMarker(currentView, line, markers) {
|
||||
const executionMarker = markers.find((marker) => marker instanceof StatementExecutionStateMarker)?.marker;
|
||||
const canExecute = showRunButtons && !!executableStatementRangeStartingAt(currentView, line.from);
|
||||
return canExecute || executionMarker ? new StatementGutterMarker(canExecute, executionMarker) : null;
|
||||
},
|
||||
domEventHandlers: showRunButtons
|
||||
? {
|
||||
mousedown: executeSqlStatementFromGutter,
|
||||
}
|
||||
: {},
|
||||
}),
|
||||
});
|
||||
return field;
|
||||
};
|
||||
statementExecutionGutterMounted = (props.statementExecutionMarkers?.length ?? 0) > 0;
|
||||
|
||||
buildSqlSignatureExtension = () =>
|
||||
showTooltip.compute(["doc", "selection"], (currentState) => {
|
||||
const signature = getSqlFunctionSignatureHelp(currentState.doc.toString(), currentState.selection.main.head, props.databaseType);
|
||||
@@ -3292,26 +3294,6 @@ onMounted(async () => {
|
||||
await ensureCodeMirrorVim();
|
||||
}
|
||||
|
||||
class RunStatementGutterMarker extends GutterMarker {
|
||||
toDOM() {
|
||||
return createRunStatementButtonDom("Execute statement");
|
||||
}
|
||||
}
|
||||
|
||||
const executableStatementMarker = new RunStatementGutterMarker();
|
||||
buildRunStatementGutterExtension = () =>
|
||||
settingsStore.editorSettings.showStatementRunButtons
|
||||
? gutter({
|
||||
class: "cm-run-statement-gutter",
|
||||
lineMarker(currentView, line) {
|
||||
return executableStatementRangeStartingAt(currentView, line.from) ? executableStatementMarker : null;
|
||||
},
|
||||
domEventHandlers: {
|
||||
mousedown: executeSqlStatementFromGutter,
|
||||
},
|
||||
})
|
||||
: [];
|
||||
|
||||
const currentStatementFrameHighlighter = ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations: import("@codemirror/view").DecorationSet;
|
||||
@@ -3414,7 +3396,7 @@ onMounted(async () => {
|
||||
return { dom };
|
||||
},
|
||||
}),
|
||||
runGutterComp.of(props.hideExecutionControls ? [] : buildRunStatementGutterExtension()),
|
||||
runGutterComp.of(runStatementGutterExtension()),
|
||||
lineNumbers({
|
||||
domEventHandlers: {
|
||||
mousedown: selectSqlLineFromGutter,
|
||||
@@ -3485,7 +3467,6 @@ onMounted(async () => {
|
||||
}),
|
||||
previewRangeComp.of(buildPreviewRangeExtension()),
|
||||
buildResultSourceRangeExtension(),
|
||||
statementExecutionGutterComp.of(statementExecutionGutterMounted ? buildStatementExecutionMarkersExtension() : []),
|
||||
Prec.highest(
|
||||
keymap.of([
|
||||
{ key: "'", run: handleSqlSingleQuote },
|
||||
@@ -3792,22 +3773,7 @@ watch(
|
||||
watch(
|
||||
() => props.statementExecutionMarkers ?? [],
|
||||
(markers) => {
|
||||
if (!view.value || !statementExecutionGutterComp || !buildStatementExecutionMarkersExtension || !setStatementExecutionMarkersEffect) return;
|
||||
if (markers.length === 0) {
|
||||
if (!statementExecutionGutterMounted) return;
|
||||
view.value.dispatch({
|
||||
effects: statementExecutionGutterComp.reconfigure([]),
|
||||
});
|
||||
statementExecutionGutterMounted = false;
|
||||
return;
|
||||
}
|
||||
if (!statementExecutionGutterMounted) {
|
||||
view.value.dispatch({
|
||||
effects: statementExecutionGutterComp.reconfigure(buildStatementExecutionMarkersExtension()),
|
||||
});
|
||||
statementExecutionGutterMounted = true;
|
||||
return;
|
||||
}
|
||||
if (!view.value || !setStatementExecutionMarkersEffect) return;
|
||||
view.value.dispatch({
|
||||
effects: setStatementExecutionMarkersEffect.of(markers),
|
||||
});
|
||||
@@ -3815,6 +3781,15 @@ watch(
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.statementExecutionMarkers?.length ?? 0,
|
||||
() => {
|
||||
if (!view.value || !runGutterComp) return;
|
||||
// Remove the compartment entirely when controls and markers are absent so the editor keeps no empty gutter width.
|
||||
view.value.dispatch({ effects: runGutterComp.reconfigure(runStatementGutterExtension()) });
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.connectionId,
|
||||
() => {
|
||||
@@ -3891,7 +3866,7 @@ watch(
|
||||
wordWrapComp.reconfigure(props.forceWordWrap || ss.wordWrap ? editorViewModule.EditorView.lineWrapping : []),
|
||||
vimModeComp.reconfigure(vimModeExtension(settingsStore.editorSettings.vimModeEnabled)),
|
||||
closeBracketsComp.reconfigure(closeBracketsExtension(settingsStore.editorSettings.autoCloseBrackets)),
|
||||
runGutterComp.reconfigure(props.hideExecutionControls ? [] : (buildRunStatementGutterExtension?.() ?? [])),
|
||||
runGutterComp.reconfigure(runStatementGutterExtension()),
|
||||
runKeymapComp.reconfigure(runKeymapExtension(editorViewModule.keymap)),
|
||||
],
|
||||
});
|
||||
@@ -4198,19 +4173,6 @@ defineExpose({
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
:deep(.cm-statement-execution-gutter) {
|
||||
min-width: 34px;
|
||||
}
|
||||
|
||||
:deep(.cm-statement-execution-gutter .cm-gutterElement) {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-width: 34px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
:deep(.cm-statement-execution-marker) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -4258,6 +4220,7 @@ defineExpose({
|
||||
}
|
||||
|
||||
:deep(.cm-run-statement-marker) {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -4299,7 +4262,7 @@ defineExpose({
|
||||
color: rgb(167 243 208);
|
||||
}
|
||||
|
||||
:deep(.cm-run-statement-marker svg) {
|
||||
:deep(.cm-run-statement-marker > svg) {
|
||||
display: block;
|
||||
width: min(14px, 70%);
|
||||
height: min(14px, 70%);
|
||||
@@ -4307,6 +4270,35 @@ defineExpose({
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:deep(.cm-statement-execution-badge) {
|
||||
position: absolute;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: min(9px, 45%);
|
||||
height: min(9px, 45%);
|
||||
border-radius: 9999px;
|
||||
color: white;
|
||||
box-shadow: 0 0 0 1px rgb(255 255 255 / 0.9);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:deep(.cm-statement-execution-badge--success) {
|
||||
background: rgb(5 150 105);
|
||||
}
|
||||
|
||||
:deep(.cm-statement-execution-badge--error) {
|
||||
background: rgb(220 38 38);
|
||||
}
|
||||
|
||||
:deep(.cm-statement-execution-badge svg) {
|
||||
display: block;
|
||||
width: 75%;
|
||||
height: 75%;
|
||||
}
|
||||
|
||||
:deep(.cm-foldMarker-svg) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createStatementGutterMarkerDom, shouldShowStatementGutter } from "@/lib/editor/codemirrorStatementGutter";
|
||||
|
||||
describe("CodeMirror statement gutter marker", () => {
|
||||
it("omits the gutter when run buttons and status markers are absent", () => {
|
||||
expect(shouldShowStatementGutter(false, 0)).toBe(false);
|
||||
expect(shouldShowStatementGutter(true, 0)).toBe(true);
|
||||
expect(shouldShowStatementGutter(false, 1)).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps execution status inside the run button column", () => {
|
||||
const marker = createStatementGutterMarkerDom({
|
||||
canExecute: true,
|
||||
executeLabel: "Execute SQL",
|
||||
status: "success",
|
||||
statusLabel: "1 statement succeeded",
|
||||
});
|
||||
|
||||
expect(marker.tagName).toBe("BUTTON");
|
||||
expect(marker.classList.contains("cm-run-statement-marker")).toBe(true);
|
||||
expect(marker.querySelectorAll(".cm-statement-execution-badge--success")).toHaveLength(1);
|
||||
expect(marker.getAttribute("aria-label")).toBe("Execute SQL. 1 statement succeeded");
|
||||
});
|
||||
|
||||
it("renders a standalone status marker when run buttons are disabled", () => {
|
||||
const marker = createStatementGutterMarkerDom({
|
||||
canExecute: false,
|
||||
executeLabel: "Execute SQL",
|
||||
status: "error",
|
||||
statusLabel: "1 statement failed",
|
||||
});
|
||||
|
||||
expect(marker.tagName).toBe("SPAN");
|
||||
expect(marker.classList.contains("cm-statement-execution-marker--error")).toBe(true);
|
||||
expect(marker.querySelectorAll(".cm-statement-execution-badge")).toHaveLength(0);
|
||||
expect(marker.getAttribute("aria-label")).toBe("1 statement failed");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { createRunStatementButtonDom } from "@/lib/editor/editorThemes";
|
||||
import type { StatementExecutionMarkerStatus } from "@/lib/tabs/tabPresentation";
|
||||
|
||||
export interface StatementGutterMarkerDomOptions {
|
||||
canExecute: boolean;
|
||||
executeLabel: string;
|
||||
status?: StatementExecutionMarkerStatus;
|
||||
statusLabel?: string;
|
||||
}
|
||||
|
||||
export function shouldShowStatementGutter(showRunButtons: boolean, markerCount: number): boolean {
|
||||
return showRunButtons || markerCount > 0;
|
||||
}
|
||||
|
||||
function createStatusIconDom(status: StatementExecutionMarkerStatus, includeCircle: boolean) {
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("viewBox", "0 0 24 24");
|
||||
svg.setAttribute("fill", "none");
|
||||
svg.setAttribute("stroke", "currentColor");
|
||||
svg.setAttribute("stroke-width", includeCircle ? "2" : "3.5");
|
||||
svg.setAttribute("stroke-linecap", "round");
|
||||
svg.setAttribute("stroke-linejoin", "round");
|
||||
svg.setAttribute("aria-hidden", "true");
|
||||
|
||||
if (includeCircle) {
|
||||
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
||||
circle.setAttribute("cx", "12");
|
||||
circle.setAttribute("cy", "12");
|
||||
circle.setAttribute("r", "10");
|
||||
svg.appendChild(circle);
|
||||
}
|
||||
|
||||
const mark = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
mark.setAttribute("d", status === "error" ? "m16 8-8 8m0-8 8 8" : "m7 12 3 3 7-7");
|
||||
svg.appendChild(mark);
|
||||
return svg;
|
||||
}
|
||||
|
||||
export function createStatementGutterMarkerDom(options: StatementGutterMarkerDomOptions): HTMLElement {
|
||||
const statusLabel = options.statusLabel?.trim();
|
||||
if (options.canExecute) {
|
||||
const button = createRunStatementButtonDom(options.executeLabel);
|
||||
if (!options.status) return button;
|
||||
|
||||
const badge = document.createElement("span");
|
||||
badge.className = `cm-statement-execution-badge cm-statement-execution-badge--${options.status}`;
|
||||
badge.setAttribute("aria-hidden", "true");
|
||||
badge.appendChild(createStatusIconDom(options.status, false));
|
||||
button.appendChild(badge);
|
||||
|
||||
if (statusLabel) {
|
||||
const label = `${options.executeLabel}. ${statusLabel}`;
|
||||
button.title = label;
|
||||
button.setAttribute("aria-label", label);
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
const marker = document.createElement("span");
|
||||
marker.className = `cm-statement-execution-marker cm-statement-execution-marker--${options.status}`;
|
||||
if (statusLabel) {
|
||||
marker.title = statusLabel;
|
||||
marker.setAttribute("aria-label", statusLabel);
|
||||
}
|
||||
if (options.status) marker.appendChild(createStatusIconDom(options.status, true));
|
||||
return marker;
|
||||
}
|
||||
@@ -790,6 +790,7 @@ export function buildEditorFontThemeRules(opts?: { fixedHeight?: boolean; scroll
|
||||
margin: "0",
|
||||
outline: "none",
|
||||
padding: "0",
|
||||
position: "relative",
|
||||
transition: "color 0.15s, background-color 0.15s",
|
||||
userSelect: "none",
|
||||
verticalAlign: "middle",
|
||||
@@ -805,13 +806,38 @@ export function buildEditorFontThemeRules(opts?: { fixedHeight?: boolean; scroll
|
||||
background: "rgb(16 185 129 / 0.2)",
|
||||
color: "rgb(6 95 70)",
|
||||
},
|
||||
"&.cm-editor .cm-run-statement-marker svg": {
|
||||
"&.cm-editor .cm-run-statement-marker > svg": {
|
||||
display: "block",
|
||||
flexShrink: "0",
|
||||
height: "min(14px, 70%)",
|
||||
pointerEvents: "none",
|
||||
width: "min(14px, 70%)",
|
||||
},
|
||||
".cm-statement-execution-badge": {
|
||||
alignItems: "center",
|
||||
borderRadius: "9999px",
|
||||
bottom: "-1px",
|
||||
boxShadow: "0 0 0 1px rgb(255 255 255 / 0.9)",
|
||||
color: "white",
|
||||
display: "inline-flex",
|
||||
height: "min(9px, 45%)",
|
||||
justifyContent: "center",
|
||||
pointerEvents: "none",
|
||||
position: "absolute",
|
||||
right: "-1px",
|
||||
width: "min(9px, 45%)",
|
||||
},
|
||||
".cm-statement-execution-badge--success": {
|
||||
background: "rgb(5 150 105)",
|
||||
},
|
||||
".cm-statement-execution-badge--error": {
|
||||
background: "rgb(220 38 38)",
|
||||
},
|
||||
".cm-statement-execution-badge svg": {
|
||||
display: "block",
|
||||
height: "75%",
|
||||
width: "75%",
|
||||
},
|
||||
"&.cm-editor.cm-focused .cm-run-statement-marker:focus-visible": {
|
||||
outline: "1px solid var(--ring)",
|
||||
outlineOffset: "1px",
|
||||
|
||||
Reference in new issue
Block a user