feat(editor): allow disabling SQL snippets
This commit is contained in:
parent
705bacba04
commit
65af804099
|
|
@ -326,7 +326,11 @@ const visibleTableColumnTemplateRows = computed(() =>
|
|||
);
|
||||
|
||||
// --- Snippet state ---
|
||||
const editSnippets = ref<SqlSnippet[]>(settingsStore.editorSettings.snippets.map((s) => ({ ...s })));
|
||||
function editableSnippet(snippet: SqlSnippet): SqlSnippet {
|
||||
return { ...snippet, enabled: snippet.enabled !== false };
|
||||
}
|
||||
|
||||
const editSnippets = ref<SqlSnippet[]>(settingsStore.editorSettings.snippets.map(editableSnippet));
|
||||
|
||||
const snippetDialogOpen = ref(false);
|
||||
const snippetEditingId = ref<string | null>(null);
|
||||
|
|
@ -445,6 +449,7 @@ function saveSnippet() {
|
|||
label: snippetForm.value.label.trim() || prefix,
|
||||
prefix,
|
||||
body: snippetForm.value.body,
|
||||
enabled: editSnippets.value[idx].enabled !== false,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
|
|
@ -453,11 +458,18 @@ function saveSnippet() {
|
|||
label: snippetForm.value.label.trim() || prefix,
|
||||
prefix,
|
||||
body: snippetForm.value.body,
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
snippetDialogOpen.value = false;
|
||||
}
|
||||
|
||||
function setSnippetEnabled(id: string, enabled: boolean) {
|
||||
const idx = editSnippets.value.findIndex((s) => s.id === id);
|
||||
if (idx === -1) return;
|
||||
editSnippets.value[idx] = { ...editSnippets.value[idx], enabled };
|
||||
}
|
||||
|
||||
function deleteSnippet(id: string) {
|
||||
editSnippets.value = editSnippets.value.filter((s) => s.id !== id);
|
||||
}
|
||||
|
|
@ -593,7 +605,7 @@ watch(
|
|||
editQueryExportKeysetOptimizationEnabled.value = settingsStore.editorSettings.queryExportKeysetOptimizationEnabled;
|
||||
editUpdateDownloadSource.value = settingsStore.editorSettings.updateDownloadSource;
|
||||
editToolbarItems.value = { ...settingsStore.editorSettings.toolbarItems };
|
||||
editSnippets.value = settingsStore.editorSettings.snippets.map((s) => ({ ...s }));
|
||||
editSnippets.value = settingsStore.editorSettings.snippets.map(editableSnippet);
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
|
|
@ -3470,12 +3482,13 @@ onUnmounted(cleanupPreviewEditor);
|
|||
<div class="flex items-center justify-between">
|
||||
<p class="text-sm text-muted-foreground">{{ t("settings.snippetsDescription") }}</p>
|
||||
<Button variant="outline" size="sm" @click="openAddSnippetDialog">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
{{ t("settings.snippetsAdd") }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<table class="w-full text-sm">
|
||||
<div class="overflow-x-auto rounded-md border">
|
||||
<table class="w-full min-w-[720px] text-sm">
|
||||
<thead>
|
||||
<tr class="border-b bg-muted/50">
|
||||
<th class="px-3 py-2 text-left font-medium whitespace-nowrap">
|
||||
|
|
@ -3484,6 +3497,9 @@ onUnmounted(cleanupPreviewEditor);
|
|||
<th class="px-3 py-2 text-left font-medium whitespace-nowrap">
|
||||
{{ t("settings.snippetsPrefix") }}
|
||||
</th>
|
||||
<th class="px-3 py-2 text-left font-medium whitespace-nowrap">
|
||||
{{ t("settings.snippetsStatus") }}
|
||||
</th>
|
||||
<th class="px-3 py-2 text-left font-medium whitespace-nowrap">
|
||||
{{ t("settings.snippetsBody") }}
|
||||
</th>
|
||||
|
|
@ -3491,13 +3507,21 @@ onUnmounted(cleanupPreviewEditor);
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="snippet in editSnippets" :key="snippet.id" class="border-b last:border-b-0 hover:bg-muted/30">
|
||||
<tr v-for="snippet in editSnippets" :key="snippet.id" class="border-b last:border-b-0 hover:bg-muted/30" :class="snippet.enabled === false ? 'text-muted-foreground' : ''">
|
||||
<td class="px-3 py-2">{{ snippet.label }}</td>
|
||||
<td class="px-3 py-2">
|
||||
<Badge variant="outline" class="h-5 rounded-md px-1.5 text-[11px] font-mono text-muted-foreground">
|
||||
{{ snippet.prefix }}
|
||||
</Badge>
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Switch :id="`snippet-enabled-${snippet.id}`" :model-value="snippet.enabled !== false" size="sm" :aria-label="t('settings.snippetsToggle')" @update:model-value="(value: boolean) => setSnippetEnabled(snippet.id, value)" />
|
||||
<Label :for="`snippet-enabled-${snippet.id}`" class="text-xs font-normal text-muted-foreground">
|
||||
{{ snippet.enabled === false ? t("settings.snippetsDisabled") : t("settings.snippetsEnabled") }}
|
||||
</Label>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-3 py-2 font-mono text-xs text-muted-foreground max-w-[300px] truncate">
|
||||
{{ snippet.body }}
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -2942,6 +2942,10 @@ export default {
|
|||
snippetsAdd: "Add Snippet",
|
||||
snippetsLabel: "Label",
|
||||
snippetsPrefix: "Prefix",
|
||||
snippetsStatus: "Status",
|
||||
snippetsEnabled: "Enabled",
|
||||
snippetsDisabled: "Disabled",
|
||||
snippetsToggle: "Enable or disable snippet",
|
||||
snippetsBody: "SQL Body",
|
||||
snippetsLabelPlaceholder: "e.g. select *",
|
||||
snippetsPrefixPlaceholder: "e.g. sel",
|
||||
|
|
|
|||
|
|
@ -2855,6 +2855,10 @@ export default withEnglishFallback({
|
|||
snippetsAdd: "Agregar fragmento",
|
||||
snippetsLabel: "Etiqueta",
|
||||
snippetsPrefix: "Prefijo",
|
||||
snippetsStatus: "Estado",
|
||||
snippetsEnabled: "Activado",
|
||||
snippetsDisabled: "Desactivado",
|
||||
snippetsToggle: "Activar o desactivar fragmento",
|
||||
snippetsBody: "SQL",
|
||||
snippetsLabelPlaceholder: "p. ej. select *",
|
||||
snippetsPrefixPlaceholder: "p. ej. sel",
|
||||
|
|
|
|||
|
|
@ -2853,6 +2853,10 @@ export default withEnglishFallback({
|
|||
snippetsAdd: "Aggiungi Snippet",
|
||||
snippetsLabel: "Etichetta",
|
||||
snippetsPrefix: "Prefisso",
|
||||
snippetsStatus: "Stato",
|
||||
snippetsEnabled: "Attivo",
|
||||
snippetsDisabled: "Disattivo",
|
||||
snippetsToggle: "Attiva o disattiva snippet",
|
||||
snippetsBody: "Corpo SQL",
|
||||
snippetsLabelPlaceholder: "es. select *",
|
||||
snippetsPrefixPlaceholder: "es. sel",
|
||||
|
|
|
|||
|
|
@ -2844,6 +2844,10 @@ export default withEnglishFallback({
|
|||
snippetsAdd: "スニペットを追加",
|
||||
snippetsLabel: "ラベル",
|
||||
snippetsPrefix: "プレフィックス",
|
||||
snippetsStatus: "ステータス",
|
||||
snippetsEnabled: "有効",
|
||||
snippetsDisabled: "無効",
|
||||
snippetsToggle: "スニペットを有効または無効にする",
|
||||
snippetsBody: "SQL本文",
|
||||
snippetsLabelPlaceholder: "例: select *",
|
||||
snippetsPrefixPlaceholder: "例: sel",
|
||||
|
|
|
|||
|
|
@ -2854,6 +2854,10 @@ export default withEnglishFallback({
|
|||
snippetsAdd: "Adicionar snippet",
|
||||
snippetsLabel: "Rótulo",
|
||||
snippetsPrefix: "Prefixo",
|
||||
snippetsStatus: "Status",
|
||||
snippetsEnabled: "Ativo",
|
||||
snippetsDisabled: "Desativado",
|
||||
snippetsToggle: "Ativar ou desativar snippet",
|
||||
snippetsBody: "Corpo SQL",
|
||||
snippetsLabelPlaceholder: "ex.: select *",
|
||||
snippetsPrefixPlaceholder: "ex.: sel",
|
||||
|
|
|
|||
|
|
@ -2942,6 +2942,10 @@ export default withEnglishFallback({
|
|||
snippetsAdd: "添加片段",
|
||||
snippetsLabel: "显示名",
|
||||
snippetsPrefix: "触发键",
|
||||
snippetsStatus: "状态",
|
||||
snippetsEnabled: "已启用",
|
||||
snippetsDisabled: "已关闭",
|
||||
snippetsToggle: "启用或关闭片段",
|
||||
snippetsBody: "SQL 内容",
|
||||
snippetsLabelPlaceholder: "如:select *",
|
||||
snippetsPrefixPlaceholder: "如:sel",
|
||||
|
|
|
|||
|
|
@ -2751,6 +2751,10 @@ export default withEnglishFallback({
|
|||
snippetsAdd: "新增片段",
|
||||
snippetsLabel: "顯示名",
|
||||
snippetsPrefix: "觸發鍵",
|
||||
snippetsStatus: "狀態",
|
||||
snippetsEnabled: "已啟用",
|
||||
snippetsDisabled: "已停用",
|
||||
snippetsToggle: "啟用或停用片段",
|
||||
snippetsBody: "SQL 內容",
|
||||
snippetsLabelPlaceholder: "如:select *",
|
||||
snippetsPrefixPlaceholder: "如:sel",
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@ describe("buildSnippetItems", () => {
|
|||
expect(items[0].label).toBe("select all");
|
||||
});
|
||||
|
||||
it("does not return disabled snippets", () => {
|
||||
const items = buildSnippetItemsForTest("sel", [{ ...TEST_SNIPPETS[0], enabled: false }, TEST_SNIPPETS[1]]);
|
||||
expect(items).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not keep matching a renamed snippet by its old short label prefix", () => {
|
||||
const items = buildSnippetItemsForTest("sel", [{ id: "1", label: "select all", prefix: "fff", body: "SELECT *\nFROM my_table;" }]);
|
||||
expect(items).toEqual([]);
|
||||
|
|
|
|||
|
|
@ -662,66 +662,77 @@ export const DEFAULT_SQL_SNIPPETS: SqlSnippet[] = [
|
|||
label: "select *",
|
||||
prefix: "sel",
|
||||
body: "SELECT *\nFROM table\nLIMIT 100;",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "builtin-ins",
|
||||
label: "insert into",
|
||||
prefix: "ins",
|
||||
body: "INSERT INTO table (columns)\nVALUES (values);",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "builtin-upd",
|
||||
label: "update set",
|
||||
prefix: "upd",
|
||||
body: "UPDATE table\nSET column = value\nWHERE condition;",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "builtin-cte",
|
||||
label: "common table expression",
|
||||
prefix: "cte",
|
||||
body: "WITH name AS (\n SELECT columns\n FROM table\n)\nSELECT *\nFROM name;",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "builtin-join",
|
||||
label: "join",
|
||||
prefix: "join",
|
||||
body: "JOIN table ON left_column = right_column",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "builtin-case",
|
||||
label: "case when",
|
||||
prefix: "case",
|
||||
body: "CASE\n WHEN condition THEN value\n ELSE default\nEND",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "builtin-ct",
|
||||
label: "create table",
|
||||
prefix: "ct",
|
||||
body: "CREATE TABLE table (\n column type\n);",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "builtin-ex",
|
||||
label: "exists",
|
||||
prefix: "ex",
|
||||
body: "EXISTS (\n SELECT 1\n FROM table\n WHERE condition\n)",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "builtin-nex",
|
||||
label: "not exists",
|
||||
prefix: "nex",
|
||||
body: "NOT EXISTS (\n SELECT 1\n FROM table\n WHERE condition\n)",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "builtin-at",
|
||||
label: "alter table add column",
|
||||
prefix: "at",
|
||||
body: "ALTER TABLE table\nADD COLUMN column type;",
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: "builtin-ci",
|
||||
label: "create index",
|
||||
prefix: "ci",
|
||||
body: "CREATE INDEX idx_name\nON table (column);",
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -3587,6 +3598,7 @@ function buildSnippetItems(prefix: string, snippets: SqlSnippet[], keywordCase?:
|
|||
if (!prefix) return [];
|
||||
return snippets
|
||||
.filter((snippet) => {
|
||||
if (snippet.enabled === false) return false;
|
||||
const matchesSnippetPrefix = matchesPrefix(snippet.prefix, prefix);
|
||||
const matchesSnippetLabel = prefix.length > snippet.prefix.length && matchesPrefix(snippet.label, prefix);
|
||||
return matchesSnippetPrefix || matchesSnippetLabel;
|
||||
|
|
|
|||
|
|
@ -658,7 +658,8 @@ function normalizeSqlSnippets(value: unknown, existing?: SqlSnippet[]): SqlSnipp
|
|||
}
|
||||
if (seenPrefixes.has(item.prefix)) continue;
|
||||
seenPrefixes.add(item.prefix);
|
||||
valid.push({ id: item.id, label: item.label, prefix: item.prefix, body: item.body });
|
||||
// Older settings do not have this field; only an explicit false disables a snippet.
|
||||
valid.push({ id: item.id, label: item.label, prefix: item.prefix, body: item.body, enabled: item.enabled !== false });
|
||||
}
|
||||
if (valid.length === 0) return existing ?? DEFAULT_SQL_SNIPPETS;
|
||||
return valid;
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ export interface SqlSnippet {
|
|||
label: string;
|
||||
prefix: string;
|
||||
body: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export type CompletionAssistantObjectKind = "database" | "schema" | "table" | "view" | "routine" | "procedure" | "function" | "column";
|
||||
|
|
|
|||
|
|
@ -136,6 +136,22 @@ test("defaults statement run buttons to enabled and preserves saved booleans", (
|
|||
assert.equal(normalizeEditorSettings({ showStatementRunButtons: "nope" as any }).showStatementRunButtons, true);
|
||||
});
|
||||
|
||||
test("normalizes SQL snippet enabled state", () => {
|
||||
const settings = normalizeEditorSettings({
|
||||
snippets: [
|
||||
{ id: "legacy", label: "legacy", prefix: "leg", body: "SELECT 1;" },
|
||||
{ id: "disabled", label: "disabled", prefix: "dis", body: "SELECT 2;", enabled: false },
|
||||
{ id: "invalid", label: "invalid", prefix: "inv", body: "SELECT 3;", enabled: "nope" },
|
||||
],
|
||||
} as any);
|
||||
|
||||
assert.deepEqual(settings.snippets, [
|
||||
{ id: "legacy", label: "legacy", prefix: "leg", body: "SELECT 1;", enabled: true },
|
||||
{ id: "disabled", label: "disabled", prefix: "dis", body: "SELECT 2;", enabled: false },
|
||||
{ id: "invalid", label: "invalid", prefix: "inv", body: "SELECT 3;", enabled: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test("defaults unsaved SQL close confirmation to enabled", () => {
|
||||
assert.equal(DEFAULT_EDITOR_SETTINGS.confirmUnsavedSqlClose, true);
|
||||
assert.equal(normalizeEditorSettings({}).confirmUnsavedSqlClose, true);
|
||||
|
|
|
|||
Loading…
Reference in New Issue