mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-07 07:03:34 +08:00
🐛 fix(query-editor): 修复大字号表候选下划线裁剪 (#829)
## 背景 SQL 查询编辑器的表候选项使用 Monaco 原生双行布局,行高固定为 18px + 18px。 当编辑器字号调整为 18px 或 20px 时,表名中的下划线和字母下行部分可能被候选项行高裁剪。 ## 变更内容 - 新增 SQL 候选项布局计算逻辑,根据最终编辑器字号动态计算表名行高和候选项总高度。 - 将 Monaco 的 `suggestLineHeight` 与动态布局保持一致。 - 使用 CSS 变量替代候选项中的固定行高,确保大字号下内容完整显示。 - 保持默认字号下原有 36px 候选项高度不变。 - 增加默认字号、大字号和显式字号配置的单元测试。 ## 影响范围 - 仅影响 v2 SQL 查询编辑器的表候选项。 - 旧版编辑器和其他类型的 Monaco 编辑器不受影响。 - 不涉及数据结构、接口、数据库或构建配置变更。 - SQL 字号为 18px 时,候选项高度调整为 43px;20px 时调整为 46px。 ## 验证方式 - `sqlEditorTypography.test.ts`:3 个测试通过。 - `MonacoEditor.typography.test.tsx`:6 个测试通过。 - TypeScript 类型检查通过:`npx tsc --noEmit`。 - `git diff --check 630ba7fc^ 630ba7fc` 通过。
This commit is contained in:
@@ -304,27 +304,27 @@ body[data-theme='light'] ::-webkit-scrollbar-thumb:hover {
|
||||
|
||||
/* 表候选使用 Monaco 原生 CompletionItemLabel:第一行表名,第二行注释。 */
|
||||
.gn-query-monaco-stage .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) {
|
||||
height: 36px !important;
|
||||
min-height: 36px !important;
|
||||
max-height: 36px !important;
|
||||
height: var(--gn-query-suggest-row-height, 36px) !important;
|
||||
min-height: var(--gn-query-suggest-row-height, 36px) !important;
|
||||
max-height: var(--gn-query-suggest-row-height, 36px) !important;
|
||||
}
|
||||
|
||||
.gn-query-monaco-stage .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents {
|
||||
height: 36px !important;
|
||||
height: var(--gn-query-suggest-row-height, 36px) !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.gn-query-monaco-stage .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents > .main {
|
||||
display: grid !important;
|
||||
grid-template-columns: 18px minmax(0, 1fr);
|
||||
grid-template-rows: 18px 18px;
|
||||
grid-template-rows: var(--gn-query-suggest-name-row-height, 18px) var(--gn-query-suggest-comment-row-height, 18px);
|
||||
grid-template-areas:
|
||||
"icon name"
|
||||
"comment comment";
|
||||
align-content: center;
|
||||
gap: 0 !important;
|
||||
height: 36px;
|
||||
line-height: 18px !important;
|
||||
height: var(--gn-query-suggest-row-height, 36px);
|
||||
line-height: var(--gn-query-suggest-name-row-height, 18px) !important;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
@@ -341,19 +341,21 @@ body[data-theme='light'] ::-webkit-scrollbar-thumb:hover {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 18px;
|
||||
height: var(--gn-query-suggest-name-row-height, 18px);
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
overflow: visible;
|
||||
line-height: 18px !important;
|
||||
line-height: var(--gn-query-suggest-name-row-height, 18px) !important;
|
||||
}
|
||||
|
||||
.gn-query-monaco-stage .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents > .main > .left > .monaco-icon-label {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
height: var(--gn-query-suggest-name-row-height, 18px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: var(--gn-query-suggest-name-row-height, 18px);
|
||||
}
|
||||
|
||||
.gn-query-monaco-stage .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents > .main > .right {
|
||||
@@ -366,7 +368,7 @@ body[data-theme='light'] ::-webkit-scrollbar-thumb:hover {
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
overflow: visible;
|
||||
line-height: 18px !important;
|
||||
line-height: var(--gn-query-suggest-comment-row-height, 18px) !important;
|
||||
}
|
||||
|
||||
.gn-query-monaco-stage .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents > .main > .right > .details-label {
|
||||
@@ -379,7 +381,7 @@ body[data-theme='light'] ::-webkit-scrollbar-thumb:hover {
|
||||
overflow: visible;
|
||||
color: var(--vscode-editorSuggestWidget-foreground);
|
||||
font-size: 11px !important;
|
||||
line-height: 18px !important;
|
||||
line-height: var(--gn-query-suggest-comment-row-height, 18px) !important;
|
||||
opacity: 0.68;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -3,7 +3,10 @@ import Editor, { loader, type BeforeMount, type EditorProps, type OnMount } from
|
||||
import { useStore } from '../store';
|
||||
import { sanitizeDataTableFontSize } from '../utils/dataGridDisplay';
|
||||
import { DEFAULT_MONO_FONT_FAMILY } from '../utils/fontFamilies';
|
||||
import { resolveSqlEditorFontSize } from '../utils/sqlEditorTypography';
|
||||
import {
|
||||
resolveSqlEditorFontSize,
|
||||
resolveSqlEditorSuggestionLayout,
|
||||
} from '../utils/sqlEditorTypography';
|
||||
|
||||
export type { BeforeMount, OnMount } from '@monaco-editor/react';
|
||||
export type GonaviMonacoTypography = 'code' | 'data' | 'sql';
|
||||
@@ -842,26 +845,6 @@ const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
||||
onMount?.(editor, monaco);
|
||||
}, [onMount]);
|
||||
|
||||
// Unified surface: all call sites inherit panel via --gn-monaco-bg (no per-page bg).
|
||||
const surfaceStyle: React.CSSProperties = {
|
||||
height: props.height || '100%',
|
||||
width: props.width || '100%',
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
background: `var(${GONAVI_MONACO_BG_CSS_VAR}, var(--gn-bg-panel, transparent))`,
|
||||
};
|
||||
|
||||
const loadingFallback = (
|
||||
<div
|
||||
className={GONAVI_MONACO_SURFACE_CLASS}
|
||||
data-monaco-editor-loading="true"
|
||||
aria-busy="true"
|
||||
style={surfaceStyle}
|
||||
>
|
||||
{loading || null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const resolvedOptions = useMemo(() => {
|
||||
if (uiVersion !== 'v2') {
|
||||
return {
|
||||
@@ -891,6 +874,9 @@ const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
||||
10,
|
||||
Math.round(Number(options?.fontSize) || resolvedFontSize),
|
||||
);
|
||||
const suggestionLayout = gonaviTypography === 'sql'
|
||||
? resolveSqlEditorSuggestionLayout(effectiveEditorFontSize)
|
||||
: null;
|
||||
|
||||
return {
|
||||
...options,
|
||||
@@ -898,6 +884,7 @@ const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
||||
fontFamily: options?.fontFamily ?? monoFontFamily ?? DEFAULT_MONO_FONT_FAMILY,
|
||||
fontSize: options?.fontSize ?? resolvedFontSize,
|
||||
lineHeight: options?.lineHeight ?? Math.max(18, Math.round(effectiveEditorFontSize * 1.62)),
|
||||
...(suggestionLayout ? { suggestLineHeight: suggestionLayout.rowHeight } : {}),
|
||||
};
|
||||
}, [
|
||||
dataTableFontSize,
|
||||
@@ -911,6 +898,37 @@ const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
||||
uiVersion,
|
||||
]);
|
||||
|
||||
const suggestionLayout = uiVersion === 'v2' && gonaviTypography === 'sql'
|
||||
? resolveSqlEditorSuggestionLayout(resolvedOptions.fontSize)
|
||||
: null;
|
||||
|
||||
// Unified surface: all call sites inherit panel via --gn-monaco-bg (no per-page bg).
|
||||
const surfaceStyle = {
|
||||
height: props.height || '100%',
|
||||
width: props.width || '100%',
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
background: `var(${GONAVI_MONACO_BG_CSS_VAR}, var(--gn-bg-panel, transparent))`,
|
||||
...(suggestionLayout
|
||||
? {
|
||||
'--gn-query-suggest-name-row-height': `${suggestionLayout.nameLineHeight}px`,
|
||||
'--gn-query-suggest-comment-row-height': `${suggestionLayout.commentLineHeight}px`,
|
||||
'--gn-query-suggest-row-height': `${suggestionLayout.rowHeight}px`,
|
||||
}
|
||||
: {}),
|
||||
} as React.CSSProperties;
|
||||
|
||||
const loadingFallback = (
|
||||
<div
|
||||
className={GONAVI_MONACO_SURFACE_CLASS}
|
||||
data-monaco-editor-loading="true"
|
||||
aria-busy="true"
|
||||
style={surfaceStyle}
|
||||
>
|
||||
{loading || null}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!ready) {
|
||||
return loadingFallback;
|
||||
}
|
||||
|
||||
@@ -110,6 +110,29 @@ describe('MonacoEditor typography', () => {
|
||||
expect(dataMarkup).toContain('"lineHeight":18');
|
||||
});
|
||||
|
||||
it('sizes structured SQL completion rows from the final editor font size', () => {
|
||||
storeState.appearance.sqlEditorFontSizeFollowGlobal = false;
|
||||
storeState.appearance.sqlEditorFontSize = 18;
|
||||
|
||||
const largeMarkup = renderToStaticMarkup(
|
||||
<MonacoEditor gonaviTypography="sql" options={{ minimap: { enabled: false } }} />,
|
||||
);
|
||||
|
||||
expect(largeMarkup).toContain('"suggestLineHeight":43');
|
||||
expect(largeMarkup).toContain('--gn-query-suggest-name-row-height:25px');
|
||||
expect(largeMarkup).toContain('--gn-query-suggest-row-height:43px');
|
||||
});
|
||||
|
||||
it('sizes structured SQL completion rows from an explicit editor font size', () => {
|
||||
const explicitMarkup = renderToStaticMarkup(
|
||||
<MonacoEditor gonaviTypography="sql" options={{ fontSize: 20 }} />,
|
||||
);
|
||||
|
||||
expect(explicitMarkup).toContain('"suggestLineHeight":46');
|
||||
expect(explicitMarkup).toContain('--gn-query-suggest-name-row-height:28px');
|
||||
expect(explicitMarkup).toContain('--gn-query-suggest-row-height:46px');
|
||||
});
|
||||
|
||||
it('keeps legacy editors on their explicit font settings', () => {
|
||||
storeState.appearance.uiVersion = 'legacy';
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
migrateLegacySqlEditorTypographySettings,
|
||||
resolveSqlEditorFontSize,
|
||||
resolveSqlEditorSuggestionLayout,
|
||||
sanitizeSqlEditorTypographySettings,
|
||||
} from './sqlEditorTypography';
|
||||
|
||||
@@ -39,4 +40,22 @@ describe('SQL editor typography', () => {
|
||||
sqlEditorFontSizeFollowGlobal: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps default completion rows unchanged and grows the table-name row for large fonts', () => {
|
||||
expect(resolveSqlEditorSuggestionLayout(13)).toEqual({
|
||||
nameLineHeight: 18,
|
||||
commentLineHeight: 18,
|
||||
rowHeight: 36,
|
||||
});
|
||||
expect(resolveSqlEditorSuggestionLayout(18)).toEqual({
|
||||
nameLineHeight: 25,
|
||||
commentLineHeight: 18,
|
||||
rowHeight: 43,
|
||||
});
|
||||
expect(resolveSqlEditorSuggestionLayout(20)).toEqual({
|
||||
nameLineHeight: 28,
|
||||
commentLineHeight: 18,
|
||||
rowHeight: 46,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,14 @@ export interface SqlEditorTypographySettings {
|
||||
export const MIN_SQL_EDITOR_FONT_SIZE = 10;
|
||||
export const MAX_SQL_EDITOR_FONT_SIZE = 20;
|
||||
export const DEFAULT_SQL_EDITOR_FONT_SCALE = 0.92;
|
||||
export const SQL_EDITOR_SUGGESTION_COMMENT_LINE_HEIGHT = 18;
|
||||
export const SQL_EDITOR_SUGGESTION_NAME_LINE_HEIGHT_SCALE = 1.4;
|
||||
|
||||
export interface SqlEditorSuggestionLayout {
|
||||
nameLineHeight: number;
|
||||
commentLineHeight: number;
|
||||
rowHeight: number;
|
||||
}
|
||||
|
||||
export const DEFAULT_SQL_EDITOR_TYPOGRAPHY_SETTINGS: SqlEditorTypographySettings = {
|
||||
sqlEditorFontSize: null,
|
||||
@@ -45,6 +53,26 @@ export const resolveSqlEditorFontSize = ({
|
||||
return sanitizeSqlEditorFontSize(sqlEditorFontSize) ?? globalDerivedFontSize;
|
||||
};
|
||||
|
||||
/**
|
||||
* Keep the structured table completion label tall enough for the editor font's
|
||||
* descenders while preserving the existing 18px + 18px layout at default sizes.
|
||||
*/
|
||||
export const resolveSqlEditorSuggestionLayout = (fontSize: unknown): SqlEditorSuggestionLayout => {
|
||||
const normalizedFontSize = Number.isFinite(Number(fontSize))
|
||||
? Math.max(MIN_SQL_EDITOR_FONT_SIZE, Math.round(Number(fontSize)))
|
||||
: 14;
|
||||
const nameLineHeight = Math.max(
|
||||
SQL_EDITOR_SUGGESTION_COMMENT_LINE_HEIGHT,
|
||||
Math.round(normalizedFontSize * SQL_EDITOR_SUGGESTION_NAME_LINE_HEIGHT_SCALE),
|
||||
);
|
||||
const commentLineHeight = SQL_EDITOR_SUGGESTION_COMMENT_LINE_HEIGHT;
|
||||
return {
|
||||
nameLineHeight,
|
||||
commentLineHeight,
|
||||
rowHeight: nameLineHeight + commentLineHeight,
|
||||
};
|
||||
};
|
||||
|
||||
export const sanitizeSqlEditorTypographySettings = (
|
||||
value: Partial<SqlEditorTypographySettings> | undefined,
|
||||
): SqlEditorTypographySettings => {
|
||||
|
||||
Reference in New Issue
Block a user