fix(sql-file): open and save custom-filtered text files

This commit is contained in:
zipg authored and GitHub committed 2026-09-02 11:55:00 +08:00
1 parent d3a6a69796
commit 9b6f3c48e4
8 files changed
+113 -58

No files matched your search

@@ -15,7 +15,7 @@ import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
import { translateBackendError } from "@/i18n/backend-errors";
import { copyToClipboard } from "@/lib/common/clipboard";
import { forgetExternalSqlFileTarget, moveExternalSqlFileTarget, resolveExternalSqlFileTarget, unassociatedExternalSqlFileTarget } from "@/lib/sql/externalSqlFileTarget";
import { externalSqlFileOpenErrorMessage, formatSqlFileSize, isExternalSqlFileTooLargeError } from "@/lib/sql/sqlFileOpen";
import { externalSqlFileOpenErrorMessage, formatSqlFileSize, isExternalSqlFileTooLargeError, isSqlFilePath } from "@/lib/sql/sqlFileOpen";
import * as api from "@/lib/backend/api";
import type { SqlFileEntry } from "@/lib/backend/api";
import { getSqlFileFilter, getSqlFileFolderPaths, saveSqlFileFilter, saveSqlFileFolderPaths, notifySqlFileFoldersChanged } from "@/lib/sqlFile/sqlFileFolders";
@@ -257,7 +257,7 @@ async function openFile(path: string) {
const target = resolveExternalSqlFileTarget(path, (savedConnectionId) => !!connectionStore.getConfig(savedConnectionId), unassociatedExternalSqlFileTarget());
queryStore.openExternalSqlFile(target.connectionId, target.database, path, snapshot.content, snapshot.version, target.catalog, target.schema);
} catch (e: any) {
if (isExternalSqlFileTooLargeError(e)) {
if (isExternalSqlFileTooLargeError(e) && isSqlFilePath(path)) {
executeFile(path);
toast(t("sqlFile.largeFileExecutionOpened", { size: formatSqlFileSize(e.sizeBytes) }), 6000);
return;
@@ -376,7 +376,12 @@ function handlePanelClick(event: MouseEvent) {
function normalizedSqlFileName(name: string) {
const trimmed = name.trim();
return /\.sql$/i.test(trimmed) ? trimmed : `${trimmed}.sql`;
return isSqlFilePath(trimmed) ? trimmed : `${trimmed}.sql`;
}
function normalizedRenamedFileName(name: string, currentName: string) {
const trimmed = name.trim();
return isSqlFilePath(currentName) ? normalizedSqlFileName(trimmed) : trimmed;
}
function openCreateDialog(rootPath: string, directoryPath: string) {
@@ -409,7 +414,7 @@ async function renameSqlFile() {
const target = renameTarget.value;
if (!target || !fileNameInput.value.trim()) return;
try {
const nextPath = await api.renameSqlFileInFolder(target.folderPath, target.entry.path, normalizedSqlFileName(fileNameInput.value));
const nextPath = await api.renameSqlFileInFolder(target.folderPath, target.entry.path, normalizedRenamedFileName(fileNameInput.value, target.entry.name));
// Renaming does not change content, and re-reading here would make a
// successful rename appear to fail for files too large for the editor.
queryStore.relocateExternalSqlFilePath(target.entry.path, nextPath);
@@ -487,9 +492,11 @@ const contextMenuItems = computed<ContextMenuItem[]>(() => {
}
// file
return [
{ label: t("sqlFileTree.openFile"), action: () => openFile(target.entry.path), icon: FileCode },
{ label: t("sqlFileTree.executeSqlFile"), action: () => executeFile(target.entry.path), icon: Play },
const items: ContextMenuItem[] = [{ label: t("sqlFileTree.openFile"), action: () => openFile(target.entry.path), icon: FileCode }];
if (isSqlFilePath(target.entry.name)) {
items.push({ label: t("sqlFileTree.executeSqlFile"), action: () => executeFile(target.entry.path), icon: Play });
}
items.push(
{ label: "", separator: true },
{ label: t("sqlFileTree.revealInFileManager"), action: () => revealInFileManager(target.entry.path), icon: FolderSearch },
{ label: t("sqlFileTree.copyPath"), action: () => copyPath(target.entry.path), icon: Copy },
@@ -497,7 +504,8 @@ const contextMenuItems = computed<ContextMenuItem[]>(() => {
{ label: t("sqlFileTree.renameFile"), action: () => openRenameDialog(target), icon: Pencil },
{ label: "", separator: true },
{ label: t("sqlFileTree.deleteFile"), action: () => openDeleteDialog(target), icon: Trash2, variant: "destructive" },
];
);
return items;
});
function expandSubtree(target: Extract<ContextTarget, { kind: "dir" }>) {
@@ -39,6 +39,11 @@ describe("SqlFilePanel file renaming", () => {
expect(renameFunction).toContain("queryStore.relocateExternalSqlFilePath(target.entry.path, nextPath)");
expect(renameFunction).not.toContain("readExternalSqlFileSnapshot(nextPath)");
});
it("preserves non-SQL extensions while keeping SQL rename convenience", () => {
expect(panelSource).toContain("normalizedRenamedFileName(fileNameInput.value, target.entry.name)");
expect(panelSource).toContain("isSqlFilePath(currentName) ? normalizedSqlFileName(trimmed) : trimmed");
});
});
describe("SqlFilePanel directory actions", () => {
@@ -58,4 +63,9 @@ describe("SqlFilePanel file filter", () => {
expect(panelSource).toContain("t('sqlFileTree.fileFilterPlaceholder')");
expect(panelSource).not.toContain("const fileFilterPlaceholder =");
});
it("offers SQL execution only for SQL files", () => {
expect(panelSource).toContain("if (isSqlFilePath(target.entry.name))");
expect(panelSource).toContain("isExternalSqlFileTooLargeError(e) && isSqlFilePath(path)");
});
});