mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-25 02:00:09 +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:第一行表名,第二行注释。 */
|
/* 表候选使用 Monaco 原生 CompletionItemLabel:第一行表名,第二行注释。 */
|
||||||
.gn-query-monaco-stage .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) {
|
.gn-query-monaco-stage .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) {
|
||||||
height: 36px !important;
|
height: var(--gn-query-suggest-row-height, 36px) !important;
|
||||||
min-height: 36px !important;
|
min-height: var(--gn-query-suggest-row-height, 36px) !important;
|
||||||
max-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 {
|
.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;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gn-query-monaco-stage .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents > .main {
|
.gn-query-monaco-stage .monaco-editor .suggest-widget .monaco-list .monaco-list-row:not(.string-label) > .contents > .main {
|
||||||
display: grid !important;
|
display: grid !important;
|
||||||
grid-template-columns: 18px minmax(0, 1fr);
|
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:
|
grid-template-areas:
|
||||||
"icon name"
|
"icon name"
|
||||||
"comment comment";
|
"comment comment";
|
||||||
align-content: center;
|
align-content: center;
|
||||||
gap: 0 !important;
|
gap: 0 !important;
|
||||||
height: 36px;
|
height: var(--gn-query-suggest-row-height, 36px);
|
||||||
line-height: 18px !important;
|
line-height: var(--gn-query-suggest-name-row-height, 18px) !important;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
@@ -341,19 +341,21 @@ body[data-theme='light'] ::-webkit-scrollbar-thumb:hover {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 18px;
|
height: var(--gn-query-suggest-name-row-height, 18px);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
overflow: visible;
|
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 {
|
.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;
|
min-width: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
height: var(--gn-query-suggest-name-row-height, 18px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
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 {
|
.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;
|
min-width: 0;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
overflow: visible;
|
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 {
|
.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;
|
overflow: visible;
|
||||||
color: var(--vscode-editorSuggestWidget-foreground);
|
color: var(--vscode-editorSuggestWidget-foreground);
|
||||||
font-size: 11px !important;
|
font-size: 11px !important;
|
||||||
line-height: 18px !important;
|
line-height: var(--gn-query-suggest-comment-row-height, 18px) !important;
|
||||||
opacity: 0.68;
|
opacity: 0.68;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ import Editor, { loader, type BeforeMount, type EditorProps, type OnMount } from
|
|||||||
import { useStore } from '../store';
|
import { useStore } from '../store';
|
||||||
import { sanitizeDataTableFontSize } from '../utils/dataGridDisplay';
|
import { sanitizeDataTableFontSize } from '../utils/dataGridDisplay';
|
||||||
import { DEFAULT_MONO_FONT_FAMILY } from '../utils/fontFamilies';
|
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 { BeforeMount, OnMount } from '@monaco-editor/react';
|
||||||
export type GonaviMonacoTypography = 'code' | 'data' | 'sql';
|
export type GonaviMonacoTypography = 'code' | 'data' | 'sql';
|
||||||
@@ -842,26 +845,6 @@ const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
|||||||
onMount?.(editor, monaco);
|
onMount?.(editor, monaco);
|
||||||
}, [onMount]);
|
}, [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(() => {
|
const resolvedOptions = useMemo(() => {
|
||||||
if (uiVersion !== 'v2') {
|
if (uiVersion !== 'v2') {
|
||||||
return {
|
return {
|
||||||
@@ -891,6 +874,9 @@ const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
|||||||
10,
|
10,
|
||||||
Math.round(Number(options?.fontSize) || resolvedFontSize),
|
Math.round(Number(options?.fontSize) || resolvedFontSize),
|
||||||
);
|
);
|
||||||
|
const suggestionLayout = gonaviTypography === 'sql'
|
||||||
|
? resolveSqlEditorSuggestionLayout(effectiveEditorFontSize)
|
||||||
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...options,
|
...options,
|
||||||
@@ -898,6 +884,7 @@ const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
|||||||
fontFamily: options?.fontFamily ?? monoFontFamily ?? DEFAULT_MONO_FONT_FAMILY,
|
fontFamily: options?.fontFamily ?? monoFontFamily ?? DEFAULT_MONO_FONT_FAMILY,
|
||||||
fontSize: options?.fontSize ?? resolvedFontSize,
|
fontSize: options?.fontSize ?? resolvedFontSize,
|
||||||
lineHeight: options?.lineHeight ?? Math.max(18, Math.round(effectiveEditorFontSize * 1.62)),
|
lineHeight: options?.lineHeight ?? Math.max(18, Math.round(effectiveEditorFontSize * 1.62)),
|
||||||
|
...(suggestionLayout ? { suggestLineHeight: suggestionLayout.rowHeight } : {}),
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
dataTableFontSize,
|
dataTableFontSize,
|
||||||
@@ -911,6 +898,37 @@ const MonacoEditor: React.FC<MonacoEditorProps> = ({
|
|||||||
uiVersion,
|
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) {
|
if (!ready) {
|
||||||
return loadingFallback;
|
return loadingFallback;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,29 @@ describe('MonacoEditor typography', () => {
|
|||||||
expect(dataMarkup).toContain('"lineHeight":18');
|
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', () => {
|
it('keeps legacy editors on their explicit font settings', () => {
|
||||||
storeState.appearance.uiVersion = 'legacy';
|
storeState.appearance.uiVersion = 'legacy';
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest';
|
|||||||
import {
|
import {
|
||||||
migrateLegacySqlEditorTypographySettings,
|
migrateLegacySqlEditorTypographySettings,
|
||||||
resolveSqlEditorFontSize,
|
resolveSqlEditorFontSize,
|
||||||
|
resolveSqlEditorSuggestionLayout,
|
||||||
sanitizeSqlEditorTypographySettings,
|
sanitizeSqlEditorTypographySettings,
|
||||||
} from './sqlEditorTypography';
|
} from './sqlEditorTypography';
|
||||||
|
|
||||||
@@ -39,4 +40,22 @@ describe('SQL editor typography', () => {
|
|||||||
sqlEditorFontSizeFollowGlobal: false,
|
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 MIN_SQL_EDITOR_FONT_SIZE = 10;
|
||||||
export const MAX_SQL_EDITOR_FONT_SIZE = 20;
|
export const MAX_SQL_EDITOR_FONT_SIZE = 20;
|
||||||
export const DEFAULT_SQL_EDITOR_FONT_SCALE = 0.92;
|
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 = {
|
export const DEFAULT_SQL_EDITOR_TYPOGRAPHY_SETTINGS: SqlEditorTypographySettings = {
|
||||||
sqlEditorFontSize: null,
|
sqlEditorFontSize: null,
|
||||||
@@ -45,6 +53,26 @@ export const resolveSqlEditorFontSize = ({
|
|||||||
return sanitizeSqlEditorFontSize(sqlEditorFontSize) ?? globalDerivedFontSize;
|
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 = (
|
export const sanitizeSqlEditorTypographySettings = (
|
||||||
value: Partial<SqlEditorTypographySettings> | undefined,
|
value: Partial<SqlEditorTypographySettings> | undefined,
|
||||||
): SqlEditorTypographySettings => {
|
): SqlEditorTypographySettings => {
|
||||||
|
|||||||
Reference in New Issue
Block a user