mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
fix(desktop): support legacy macOS WebViews
This commit is contained in:
@@ -921,7 +921,7 @@ async function saveConfigTask() {
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogContent class="sm:max-w-[1120px] max-h-[80vh] flex flex-col overflow-hidden" @interact-outside.prevent>
|
||||
<DialogContent class="dbx-transfer-dialog sm:max-w-[1120px] max-h-[80vh] flex flex-col overflow-hidden" @interact-outside.prevent>
|
||||
<DialogHeader class="shrink-0">
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<ArrowRightLeft class="w-4 h-4" />
|
||||
@@ -1250,3 +1250,12 @@ async function saveConfigTask() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@media (min-width: 640px) {
|
||||
html.dbx-legacy-webview [data-slot="dialog-content"].dbx-transfer-dialog[class~="max-w-sm"] {
|
||||
width: calc(100vw - 2rem) !important;
|
||||
max-width: 1120px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { canFormatSqlForDatabaseType, formatSqlForDisplay, formatSqlForEditing, formatSqlText, MAX_SQL_FORMAT_CHARS, sqlFormatDialectForDbType, UnsupportedStructuredInputError } from "@/lib/sql/sqlFormatter";
|
||||
|
||||
const sqlFormatterSource = readFileSync(new URL("../../sql/sqlFormatter.ts", import.meta.url), "utf8");
|
||||
|
||||
describe("sqlFormatter", () => {
|
||||
it("does not use lookbehind regular expressions in the startup path", () => {
|
||||
expect(sqlFormatterSource).not.toContain("(?<!");
|
||||
expect(sqlFormatterSource).not.toContain("(?<=");
|
||||
});
|
||||
|
||||
it("disables SQL formatting for VictoriaMetrics queries", () => {
|
||||
expect(canFormatSqlForDatabaseType("victoriametrics")).toBe(false);
|
||||
expect(canFormatSqlForDatabaseType("mysql")).toBe(true);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { identifierInSql } from "@/lib/diagram/fieldLineage";
|
||||
|
||||
const fieldLineageSource = readFileSync(new URL("../fieldLineage.ts", import.meta.url), "utf8");
|
||||
|
||||
describe("identifierInSql", () => {
|
||||
it("matches unquoted identifiers at the start or after punctuation", () => {
|
||||
expect(identifierInSql("customer_id = 1", "customer_id")).toBe(true);
|
||||
expect(identifierInSql("SELECT account.customer_id", "customer_id")).toBe(true);
|
||||
expect(identifierInSql("SELECT (CUSTOMER_ID)", "customer_id")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not match identifiers embedded in longer identifiers", () => {
|
||||
expect(identifierInSql("SELECT customer_identifier", "customer_id")).toBe(false);
|
||||
expect(identifierInSql("SELECT archived_customer_id", "customer_id")).toBe(false);
|
||||
expect(identifierInSql("SELECT customer_id_v2", "customer_id")).toBe(false);
|
||||
expect(identifierInSql("SELECT $customer_id", "customer_id")).toBe(false);
|
||||
});
|
||||
|
||||
it("matches identifiers quoted with supported SQL delimiters", () => {
|
||||
expect(identifierInSql('SELECT "customer_id"', "customer_id")).toBe(true);
|
||||
expect(identifierInSql("SELECT `customer_id`", "customer_id")).toBe(true);
|
||||
expect(identifierInSql("SELECT [customer_id]", "customer_id")).toBe(true);
|
||||
});
|
||||
|
||||
it("does not use runtime lookbehind in the legacy WebView path", () => {
|
||||
expect(fieldLineageSource).not.toContain("(?<");
|
||||
});
|
||||
});
|
||||
@@ -65,7 +65,7 @@ export function summarizeLineageCounts(items: FieldLineageItem[]) {
|
||||
export function identifierInSql(sql: string, identifier: string): boolean {
|
||||
const escaped = escapeRegExp(identifier);
|
||||
const quoted = [`"${escapeRegExp(identifier)}"`, `\`${escapeRegExp(identifier)}\``, `\\[${escapeRegExp(identifier)}\\]`];
|
||||
const pattern = `(?:${quoted.join("|")}|(?<![\\w$])${escaped}(?![\\w$]))`;
|
||||
const pattern = `(?:${quoted.join("|")}|(?:^|[^\\w$])${escaped}(?![\\w$]))`;
|
||||
return new RegExp(pattern, "i").test(sql);
|
||||
}
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ function normalizeLikeOperatorCase(sql: string, settings: SqlFormatterSettings,
|
||||
// identifiers under their existing function/identifier case settings.
|
||||
const { masked, spans } = maskStringAndCommentSpans(sql, dialect);
|
||||
const keyword = settings.keywordCase === "upper" ? "LIKE" : "like";
|
||||
const normalized = masked.replace(/(?<![.\w$:@{#])LIKE\b(?!\s*\()/gi, keyword);
|
||||
const normalized = masked.replace(/(^|[^.\w$:@{#])LIKE\b(?!\s*\()/gi, (_match, prefix: string) => `${prefix}${keyword}`);
|
||||
return restoreSpans(normalized, spans);
|
||||
}
|
||||
|
||||
|
||||
+20
-3
@@ -3,7 +3,7 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { applyLegacyWebViewClass, isLegacyWebView, LEGACY_WEBVIEW_CLASS, missingLegacyWebViewCapabilities } from "@/lib/ui/legacyWebView";
|
||||
|
||||
const supportedFeatures = new Set(["oklch", "color-mix-oklab", "color-mix-oklch", "has-selector", "dynamic-viewport", "min-function"]);
|
||||
const supportedFeatures = new Set(["oklch", "color-mix-oklab", "color-mix-oklch", "has-selector", "dynamic-viewport", "min-function", "media-query-range"]);
|
||||
|
||||
function mockCssSupports(features: Set<string>) {
|
||||
vi.stubGlobal("CSS", {
|
||||
@@ -27,6 +27,16 @@ function mockCssSupports(features: Set<string>) {
|
||||
return features.has(key);
|
||||
},
|
||||
});
|
||||
vi.stubGlobal("matchMedia", (query: string) => ({
|
||||
matches: query === "(width >= 0px)" ? features.has("media-query-range") : false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
}));
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
@@ -45,14 +55,21 @@ describe("legacy WebView detection", () => {
|
||||
mockCssSupports(new Set(["oklch"]));
|
||||
|
||||
expect(isLegacyWebView()).toBe(true);
|
||||
expect(missingLegacyWebViewCapabilities()).toEqual(["color-mix-oklab", "color-mix-oklch", "has-selector", "dynamic-viewport", "min-function"]);
|
||||
expect(missingLegacyWebViewCapabilities()).toEqual(["color-mix-oklab", "color-mix-oklch", "has-selector", "dynamic-viewport", "min-function", "media-query-range"]);
|
||||
});
|
||||
|
||||
it("does not treat one color-mix interpolation space as the other", () => {
|
||||
mockCssSupports(new Set(["oklch", "color-mix-oklab", "has-selector", "dynamic-viewport", "min-function"]));
|
||||
|
||||
expect(isLegacyWebView()).toBe(true);
|
||||
expect(missingLegacyWebViewCapabilities()).toEqual(["color-mix-oklch"]);
|
||||
expect(missingLegacyWebViewCapabilities()).toEqual(["color-mix-oklch", "media-query-range"]);
|
||||
});
|
||||
|
||||
it("marks a WebView without media query range syntax as legacy", () => {
|
||||
mockCssSupports(new Set(["oklch", "color-mix-oklab", "color-mix-oklch", "has-selector", "dynamic-viewport", "min-function"]));
|
||||
|
||||
expect(isLegacyWebView()).toBe(true);
|
||||
expect(missingLegacyWebViewCapabilities()).toEqual(["media-query-range"]);
|
||||
});
|
||||
|
||||
it("adds and removes the root class idempotently", () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ type CssSupportCheck = {
|
||||
property?: string;
|
||||
value?: string;
|
||||
condition?: string;
|
||||
test?: () => boolean;
|
||||
};
|
||||
|
||||
const MODERN_CSS_FEATURES: CssSupportCheck[] = [
|
||||
@@ -14,9 +15,21 @@ const MODERN_CSS_FEATURES: CssSupportCheck[] = [
|
||||
{ name: "has-selector", condition: "selector(:has(*))" },
|
||||
{ name: "dynamic-viewport", property: "height", value: "100dvh" },
|
||||
{ name: "min-function", property: "width", value: "min(100%, 1px)" },
|
||||
{
|
||||
name: "media-query-range",
|
||||
test: () => {
|
||||
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
||||
try {
|
||||
return window.matchMedia("(width >= 0px)").matches;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function supports(check: CssSupportCheck): boolean {
|
||||
if (check.test) return check.test();
|
||||
if (typeof CSS === "undefined" || typeof CSS.supports !== "function") return false;
|
||||
|
||||
try {
|
||||
|
||||
@@ -6,6 +6,7 @@ const globalsCss = readCascadeCss();
|
||||
const dialogContentSource = readFileSync(new URL("../../components/ui/dialog/DialogContent.vue", import.meta.url), "utf8");
|
||||
const dialogScrollContentSource = readFileSync(new URL("../../components/ui/dialog/DialogScrollContent.vue", import.meta.url), "utf8");
|
||||
const dialogOverlaySource = readFileSync(new URL("../../components/ui/dialog/DialogOverlay.vue", import.meta.url), "utf8");
|
||||
const dataTransferDialogSource = readFileSync(new URL("../../components/transfer/DataTransferDialog.vue", import.meta.url), "utf8");
|
||||
const connectionDialogSource = readFileSync(new URL("../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8");
|
||||
const connectionTreeSource = readFileSync(new URL("../../components/sidebar/ConnectionTree.vue", import.meta.url), "utf8");
|
||||
const scheduledDatabaseBackupSource = readFileSync(new URL("../../components/backup/ScheduledDatabaseBackupSettings.vue", import.meta.url), "utf8");
|
||||
@@ -79,6 +80,14 @@ describe("legacy WebView CSS fallbacks", () => {
|
||||
expect(globalsCss).not.toContain("filter: blur(4px);");
|
||||
});
|
||||
|
||||
it("keeps the data transfer dialog width fallback scoped to legacy WebViews", () => {
|
||||
expect(dataTransferDialogSource).toContain('class="dbx-transfer-dialog sm:max-w-[1120px] max-h-[80vh] flex flex-col overflow-hidden"');
|
||||
expect(dataTransferDialogSource).toContain('html.dbx-legacy-webview [data-slot="dialog-content"].dbx-transfer-dialog[class~="max-w-sm"]');
|
||||
expect(dataTransferDialogSource).toContain("width: calc(100vw - 2rem) !important;");
|
||||
expect(dataTransferDialogSource).toContain("max-width: 1120px !important;");
|
||||
expect(globalsCss).not.toContain(".dbx-transfer-dialog");
|
||||
});
|
||||
|
||||
it("keeps primary alpha utilities readable in legacy WebViews", () => {
|
||||
expect(globalsCss).toContain("--dbx-primary-rgb: 23, 23, 23;");
|
||||
expect(globalsCss).toContain("--dbx-primary-rgb: 46, 95, 166;");
|
||||
|
||||
Reference in New Issue
Block a user