🐛 fix(snippet): 修复SQL片段入口与弹窗布局

- 将 SQL 编辑器代码片段管理入口改为打开工具中心 SQL 片段面板
- 关闭旧独立片段弹窗入口,避免同一功能出现两个入口形态
- 限制片段管理弹窗内容区高度并固定底部操作行,避免按钮被语法参考内容挤出
This commit is contained in:
Syngnat
2026-06-24 17:49:48 +08:00
parent d08ab62f92
commit 9da9a36cf3
6 changed files with 92 additions and 11 deletions

View File

@@ -42,7 +42,10 @@ describe('tool center menu entries', () => {
expect(appSource).toContain("key: 'snippet-settings'");
expect(appSource).toContain("title: t('app.tools.entry.snippets.title')");
expect(appSource).toContain("description: t('app.tools.entry.snippets.description')");
expect(appSource).toContain('setIsSnippetModalOpen(true)');
expect(appSource).toContain("handleOpenToolCenterPane('workspace', 'snippet-settings')");
expect(appSource).toContain('gonavi:open-snippet-settings');
expect(appSource).toContain("setIsSnippetModalOpen(false);");
expect(appSource).not.toContain('setIsSnippetModalOpen(true)');
const snippetIndex = appSource.indexOf("key: 'snippet-settings'");
const shortcutIndex = appSource.indexOf("key: 'shortcut-settings'", snippetIndex);

View File

@@ -1967,6 +1967,7 @@ function App() {
setToolCenterBackGroupKey(group);
setActiveToolCenterGroupKey(group);
setActiveToolCenterPane({ key, group });
setIsToolsModalOpen(true);
}, []);
const handleReturnToToolCenter = useCallback((closeChild?: () => void) => {
const returnGroup = toolCenterBackGroupKey ?? 'config';
@@ -2472,13 +2473,14 @@ function App() {
useEffect(() => {
const handleOpenSnippetSettingsEvent = () => {
setIsSnippetModalOpen(true);
setIsSnippetModalOpen(false);
handleOpenToolCenterPane('workspace', 'snippet-settings');
};
window.addEventListener('gonavi:open-snippet-settings', handleOpenSnippetSettingsEvent as EventListener);
return () => {
window.removeEventListener('gonavi:open-snippet-settings', handleOpenSnippetSettingsEvent as EventListener);
};
}, []);
}, [handleOpenToolCenterPane]);
useEffect(() => {
const handleOpenTabDisplaySettingsEvent = () => {

View File

@@ -7293,7 +7293,10 @@ describe('QueryEditor external SQL save', () => {
expect(modalSource).toContain("defaultActiveKey={['snippet-help']}");
expect(modalSource).toContain('footer={null}');
expect(modalSource).toContain('data-sql-snippet-action-row="true"');
expect(modalSource).toContain('body: { paddingTop: 8, paddingBottom: 24 }');
expect(modalSource).toContain('data-sql-snippet-content-region="true"');
expect(modalSource).toContain('data-sql-snippet-editor-scroll-region="true"');
expect(modalSource).toContain("maxHeight: snippetModalBodyMaxHeight");
expect(modalSource).toContain("flex: '0 0 auto'");
expect(modalSource).toContain("size=\"large\"");
expect(modalSource).toContain('minWidth: 96');
expect(modalSource).toContain('syntaxHelp');

View File

@@ -2618,7 +2618,10 @@ describe('QueryEditor external SQL save', () => {
expect(modalSource).toContain("defaultActiveKey={['snippet-help']}");
expect(modalSource).toContain('footer={null}');
expect(modalSource).toContain('data-sql-snippet-action-row="true"');
expect(modalSource).toContain('body: { paddingTop: 8, paddingBottom: 24 }');
expect(modalSource).toContain('data-sql-snippet-content-region="true"');
expect(modalSource).toContain('data-sql-snippet-editor-scroll-region="true"');
expect(modalSource).toContain("maxHeight: snippetModalBodyMaxHeight");
expect(modalSource).toContain("flex: '0 0 auto'");
expect(modalSource).toContain("size=\"large\"");
expect(modalSource).toContain('minWidth: 96');
expect(modalSource).toContain('syntaxHelp');

View File

@@ -320,6 +320,15 @@ describe('SnippetSettingsModal i18n', () => {
expect(source).not.toContain('示例SELECT');
});
it('keeps snippet editor content scrollable without clipping the action row', () => {
expect(source).toContain("const snippetModalBodyMaxHeight = 'calc(100vh - 128px)';");
expect(source).toContain("maxHeight: snippetModalBodyMaxHeight");
expect(source).toContain('data-sql-snippet-content-region="true"');
expect(source).toContain('data-sql-snippet-editor-scroll-region="true"');
expect(source).toContain("overflowY: 'auto'");
expect(source).toContain("flex: '0 0 auto'");
});
it('keeps the shell and feedback keys available in every locale', () => {
locales.forEach((locale) => {
const catalog = JSON.parse(readFileSync(new URL(`../../../shared/i18n/${locale}.json`, import.meta.url), 'utf8')) as Record<string, string>;
@@ -364,4 +373,33 @@ describe('SnippetSettingsModal i18n', () => {
expect(messageApi.warning).toHaveBeenCalledWith('Prefix is required');
});
it('renders a bounded content region and fixed action row for long syntax help', async () => {
const renderer = await renderModal();
const newButton = renderer.root.findAll((node: any) => node.type === 'button' && getText(node).includes('New Snippet'))[0];
await act(async () => {
newButton.props.onClick();
});
const contentRegion = renderer.root.findByProps({ 'data-sql-snippet-content-region': 'true' });
const editorScrollRegion = renderer.root.findByProps({ 'data-sql-snippet-editor-scroll-region': 'true' });
const actionRow = renderer.root.findByProps({ 'data-sql-snippet-action-row': 'true' });
expect(contentRegion.props.style).toMatchObject({
flex: '1 1 420px',
minHeight: 0,
overflow: 'hidden',
});
expect(editorScrollRegion.props.style).toMatchObject({
flex: 1,
minHeight: 0,
overflowY: 'auto',
});
expect(actionRow.props.style).toMatchObject({
flex: '0 0 auto',
justifyContent: 'flex-end',
});
});
});

View File

@@ -77,6 +77,7 @@ export default function SnippetSettingsModal({
const mutedColor = darkMode ? 'rgba(255,255,255,0.5)' : 'rgba(16,24,40,0.55)';
const selectedBg = darkMode ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.04)';
const newSnippetAction = t('snippet_settings.action.new');
const snippetModalBodyMaxHeight = 'calc(100vh - 128px)';
const sortedSnippets = useMemo(
() => [...sqlSnippets].sort((a, b) => a.prefix.localeCompare(b.prefix)),
@@ -248,16 +249,34 @@ export default function SnippetSettingsModal({
styles={{
content: shellStyle,
header: { background: 'transparent', borderBottom: 'none', paddingBottom: 8 },
body: { paddingTop: 8, paddingBottom: 24 },
body: {
paddingTop: 8,
paddingBottom: 0,
display: 'flex',
flexDirection: 'column',
maxHeight: snippetModalBodyMaxHeight,
minHeight: 0,
overflow: 'hidden',
},
}}
footer={null}
>
<div style={{ display: 'flex', gap: 16, minHeight: 420 }}>
<div
data-sql-snippet-content-region="true"
style={{
display: 'flex',
gap: 16,
flex: '1 1 420px',
minHeight: 0,
overflow: 'hidden',
}}
>
{/* Left: snippet list */}
<div
style={{
width: 220,
flexShrink: 0,
minHeight: 0,
borderRadius: 14,
border: overlayTheme.sectionBorder,
background: overlayTheme.sectionBg,
@@ -332,7 +351,7 @@ export default function SnippetSettingsModal({
</div>
{/* Right: editor */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ flex: 1, minWidth: 0, minHeight: 0, display: 'flex' }}>
{showEditor ? (
<div
style={{
@@ -341,6 +360,8 @@ export default function SnippetSettingsModal({
flexDirection: 'column',
gap: 12,
height: '100%',
minHeight: 0,
overflow: 'hidden',
}}
>
<div style={{ display: 'flex', gap: 12 }}>
@@ -379,7 +400,17 @@ export default function SnippetSettingsModal({
/>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
<div
data-sql-snippet-editor-scroll-region="true"
style={{
flex: 1,
display: 'flex',
flexDirection: 'column',
minHeight: 0,
overflowY: 'auto',
paddingRight: 4,
}}
>
<div style={{ fontSize: 12, color: mutedColor, marginBottom: 4 }}>{t('snippet_settings.field.body.label')}</div>
<Input.TextArea
value={draft.body}
@@ -422,11 +453,12 @@ export default function SnippetSettingsModal({
data-sql-snippet-action-row="true"
style={{
display: 'flex',
flex: '0 0 auto',
gap: 12,
justifyContent: 'flex-end',
alignItems: 'center',
paddingTop: 18,
marginTop: 18,
paddingTop: 12,
marginTop: 12,
borderTop: overlayTheme.sectionBorder,
}}
>