feat(update): auto-replace stale downloads and relocate update toggle

Closes #9140
This commit is contained in:
t8y2 committed 2026-09-15 14:58:20 +08:00
1 parent d79dc05cf5
commit 39042f7917
16 files changed
+123 -61

No files matched your search

@@ -1735,7 +1735,6 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editGenerateSqlQuoteIdentifiers.value = DEFAULT_EDITOR_SETTINGS.generateSqlQuoteIdentifiers;
editFormatSqlOnSqlFileSave.value = DEFAULT_EDITOR_SETTINGS.formatSqlOnSqlFileSave;
editClickTableNavigationTarget.value = DEFAULT_EDITOR_SETTINGS.clickTableNavigationTarget;
editUpdateNotificationsEnabled.value = DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled;
editSidebarObjectInfoMode.value = DEFAULT_EDITOR_SETTINGS.sidebarObjectInfoMode;
editSidebarAllowHorizontalScroll.value = DEFAULT_EDITOR_SETTINGS.sidebarAllowHorizontalScroll;
editSidebarShowTooltips.value = DEFAULT_EDITOR_SETTINGS.sidebarShowTooltips;
@@ -1790,6 +1789,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editSnippets.value = DEFAULT_SQL_SNIPPETS.map((s) => ({ ...s }));
} else if (tab === "about") {
editUpdateDownloadSource.value = DEFAULT_EDITOR_SETTINGS.updateDownloadSource;
editUpdateNotificationsEnabled.value = DEFAULT_EDITOR_SETTINGS.updateNotificationsEnabled;
}
}
@@ -6217,16 +6217,6 @@ onUnmounted(() => {
<Switch id="quit-on-close" v-model="editQuitOnClose" />
</div>
<div class="settings-item flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="update-notifications-enabled">{{ t("settings.updateNotificationsEnabled") }}</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.updateNotificationsEnabledDescription") }}
</p>
</div>
<Switch id="update-notifications-enabled" v-model="editUpdateNotificationsEnabled" />
</div>
<div class="settings-appearance-group" data-icon-theme-settings>
<Label>{{ t("settings.iconTheme") }}</Label>
<div class="settings-appearance-choice-grid settings-icon-theme-grid">
@@ -9398,8 +9388,17 @@ LIMIT 100;</pre
</div>
</div>
<div class="settings-item rounded-lg border p-4">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<div class="settings-item flex flex-col gap-4 rounded-lg border p-4">
<div class="flex items-center justify-between gap-4">
<div class="min-w-0 space-y-1">
<Label for="update-notifications-enabled">{{ t("settings.updateNotificationsEnabled") }}</Label>
<p class="text-sm text-muted-foreground">
{{ t("settings.updateNotificationsEnabledDescription") }}
</p>
</div>
<Switch id="update-notifications-enabled" v-model="editUpdateNotificationsEnabled" />
</div>
<div class="flex flex-col gap-3 border-t border-border/60 pt-4 sm:flex-row sm:items-center sm:justify-between">
<div class="min-w-0 space-y-1">
<Label>{{ t("settings.updateDownloadSource") }}</Label>
<p class="text-sm text-muted-foreground">
@@ -9420,12 +9419,9 @@ LIMIT 100;</pre
<ChangelogPanel :checking-updates="props.checkingUpdates" @check-updates="emit('check-updates')" />
<div class="grid gap-3 sm:grid-cols-2">
<div class="grid gap-3 sm:grid-cols-3">
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://qm.qq.com/cgi-bin/qm/qr?k=&group_code=1087880322')">
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
{{ t("settings.community") }}
</div>
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
<div class="flex items-center gap-2 text-sm font-medium">
<img
src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iODYiIHdpZHRoPSI4NiIgdmlld0JveD0iMCAwIDEyMCAxNDUiPjxwYXRoIGZpbGw9IiNmYWFiMDciIGQ9Ik02MC41MDMgMTQyLjIzN2MtMTIuNTMzIDAtMjQuMDM4LTQuMTk1LTMxLjQ0NS0xMC40Ni0zLjc2MiAxLjEyNC04LjU3NCAyLjkzMi0xMS42MSA1LjE3NS0yLjYgMS45MTgtMi4yNzUgMy44NzQtMS44MDcgNC42NjMgMi4wNTYgMy40NyAzNS4yNzMgMi4yMTYgNDQuODYyIDEuMTM2em0wIDBjMTIuNTM1IDAgMjQuMDM5LTQuMTk1IDMxLjQ0Ny0xMC40NiAzLjc2IDEuMTI0IDguNTczIDIuOTMyIDExLjYxIDUuMTc1IDIuNTk4IDEuOTE4IDIuMjc0IDMuODc0IDEuODA1IDQuNjYzLTIuMDU2IDMuNDctMzUuMjcyIDIuMjE2LTQ0Ljg2MiAxLjEzNnptMCAwIi8+PHBhdGggZD0iTTYwLjU3NiA2Ny4xMTljMjAuNjk4LS4xNCAzNy4yODYtNC4xNDcgNDIuOTA3LTUuNjgzIDEuMzQtLjM2NyAyLjA1Ni0xLjAyNCAyLjA1Ni0xLjAyNC4wMDUtLjE4OS4wODUtMy4zNy4wODUtNS4wMUMxMDUuNjI0IDI3Ljc2OCA5Mi41OC4wMDEgNjAuNSAwIDI4LjQyLjAwMSAxNS4zNzUgMjcuNzY5IDE1LjM3NSA1NS40MDFjMCAxLjY0Mi4wOCA0LjgyMi4wODYgNS4wMSAwIDAgLjU4My42MTUgMS42NS45MTMgNS4xOSAxLjQ0NCAyMi4wOSA1LjY1IDQzLjMxMiA1Ljc5NXptNTYuMjQ1IDIzLjAyYy0xLjI4My00LjEyOS0zLjAzNC04Ljk0NC00LjgwOC0xMy41NjggMCAwLTEuMDItLjEyNi0xLjUzNy4wMjMtMTUuOTEzIDQuNjIzLTM1LjIwMiA3LjU3LTQ5LjkgNy4zOTJoLS4xNTNjLTE0LjYxNi4xNzUtMzMuNzc0LTIuNzM3LTQ5LjYzNC03LjMxNS0uNjA2LS4xNzUtMS44MDItLjEtMS44MDItLjEtMS43NzQgNC42MjQtMy41MjUgOS40NC00LjgwOCAxMy41NjgtNi4xMTkgMTkuNjktNC4xMzYgMjcuODM4LTIuNjI3IDI4LjAyIDMuMjM5LjM5MiAxMi42MDYtMTQuODIxIDEyLjYwNi0xNC44MjEgMCAxNS40NTkgMTMuOTU3IDM5LjE5NSA0NS45MTggMzkuNDEzaC44NDhjMzEuOTYtLjIxOCA0NS45MTctMjMuOTU0IDQ1LjkxNy0zOS40MTMgMCAwIDkuMzY4IDE1LjIxMyAxMi42MDcgMTQuODIyIDEuNTA4LS4xODMgMy40OTEtOC4zMzItMi42MjctMjguMDIxIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTQ5LjA4NSA0MC44MjRjLTQuMzUyLjE5Ny04LjA3LTQuNzYtOC4zMDQtMTEuMDYzLS4yMzYtNi4zMDUgMy4wOTgtMTEuNTc2IDcuNDUtMTEuNzczIDQuMzQ3LS4xOTUgOC4wNjQgNC43NiA4LjMgMTEuMDY1LjIzOCA2LjMwNi0zLjA5NyAxMS41NzctNy40NDYgMTEuNzcxbTMxLjEzMy0xMS4wNjNjLS4yMzMgNi4zMDItMy45NTEgMTEuMjYtOC4zMDMgMTEuMDYzLTQuMzUtLjE5NS03LjY4NC01LjQ2NS03LjQ0Ni0xMS43Ny4yMzYtNi4zMDUgMy45NTItMTEuMjYgOC4zLTExLjA2NiA0LjM1Mi4xOTcgNy42ODYgNS40NjggNy40NDkgMTEuNzczIi8+PHBhdGggZmlsbD0iI2ZhYWIwNyIgZD0iTTg3Ljk1MiA0OS43MjVDODYuNzkgNDcuMTUgNzUuMDc3IDQ0LjI4IDYwLjU3OCA0NC4yOGgtLjE1NmMtMTQuNSAwLTI2LjIxMiAyLjg3LTI3LjM3NSA1LjQ0NmEuODYzLjg2MyAwIDAwLS4wODUuMzY3Ljg4Ljg4IDAgMDAuMTYuNDk2Yy45OCAxLjQyNyAxMy45ODUgOC40ODcgMjcuMyA4LjQ4N2guMTU2YzEzLjMxNCAwIDI2LjMxOS03LjA1OCAyNy4yOTktOC40ODdhLjg3My44NzMgMCAwMC4xNi0uNDk4Ljg1Ni44NTYgMCAwMC0uMDg1LS4zNjUiLz48cGF0aCBkPSJNNTQuNDM0IDI5Ljg1NGMuMTk5IDIuNDktMS4xNjcgNC43MDItMy4wNDYgNC45NDMtMS44ODMuMjQyLTMuNTY4LTEuNTgtMy43NjgtNC4wNy0uMTk3LTIuNDkyIDEuMTY3LTQuNzA0IDMuMDQzLTQuOTQ0IDEuODg2LS4yNDQgMy41NzQgMS41OCAzLjc3MSA0LjA3bTExLjk1Ni44MzNjLjM4NS0uNjg5IDMuMDA0LTQuMzEyIDguNDI3LTIuOTkzIDEuNDI1LjM0NyAyLjA4NC44NTcgMi4yMjMgMS4wNTcuMjA1LjI5Ni4yNjIuNzE4LjA1MyAxLjI4Ni0uNDEyIDEuMTI2LTEuMjYzIDEuMDk1LTEuNzM0Ljg3NS0uMzA1LS4xNDItNC4wODItMi42Ni03LjU2MiAxLjA5Ny0uMjQuMjU3LS42NjguMzQ2LTEuMDczLjA0LS40MDctLjMwOC0uNTc0LS45My0uMzM0LTEuMzYyIi8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTYwLjU3NiA4My4wOGgtLjE1M2MtOS45OTYuMTItMjIuMTE2LTEuMjA0LTMzLjg1NC0zLjUxOC0xLjAwNCA1LjgxOC0xLjYxIDEzLjEzMi0xLjA5IDIxLjg1MyAxLjMxNiAyMi4wNDMgMTQuNDA3IDM1LjkgMzQuNjE0IDM2LjFoLjgyYzIwLjIwOC0uMiAzMy4yOTgtMTQuMDU3IDM0LjYxNi0zNi4xLjUyLTguNzIzLS4wODctMTYuMDM1LTEuMDkyLTIxLjg1NC0xMS43MzkgMi4zMTUtMjMuODYyIDMuNjQtMzMuODYgMy41MTgiLz48cGF0aCBmaWxsPSIjZWIxOTIzIiBkPSJNMzIuMTAyIDgxLjIzNXYyMS42OTNzOS45MzcgMi4wMDQgMTkuODkzLjYxNlY4My41MzVjLTYuMzA3LS4zNTctMTMuMTA5LTEuMTUyLTE5Ljg5My0yLjMiLz48cGF0aCBmaWxsPSIjZWIxOTIzIiBkPSJNMTA1LjUzOSA2MC40MTJzLTE5LjMzIDYuMTAyLTQ0Ljk2MyA2LjI3NWgtLjE1M2MtMjUuNTkxLS4xNzItNDQuODk2LTYuMjU1LTQ0Ljk2Mi02LjI3NUw4Ljk4NyA3Ni41N2MxNi4xOTMgNC44ODIgMzYuMjYxIDguMDI4IDUxLjQzNiA3Ljg0NWguMTUzYzE1LjE3NS4xODMgMzUuMjQyLTIuOTYzIDUxLjQzNy03Ljg0NXptMCAwIi8+PC9zdmc+"
alt="QQ"
@@ -9437,10 +9433,7 @@ LIMIT 100;</pre
<div class="mt-1 font-mono text-base">1087880322</div>
</button>
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://discord.gg/W7NyVDRt6a')">
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
{{ t("settings.community") }}
</div>
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
<div class="flex items-center gap-2 text-sm font-medium">
<img src="https://cdn.simpleicons.org/discord/5865F2" alt="Discord" class="h-7 w-7 rounded-md bg-white p-1" />
Discord
<ExternalLink class="ml-auto h-3.5 w-3.5 text-muted-foreground" />
@@ -9448,10 +9441,7 @@ LIMIT 100;</pre
<div class="mt-1 text-sm text-primary">discord.gg/W7NyVDRt6a</div>
</button>
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://docs.qq.com/doc/DVVhMY0h1ekJqc0tz')">
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
{{ t("settings.community") }}
</div>
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
<div class="flex items-center gap-2 text-sm font-medium">
<span class="flex h-7 w-7 items-center justify-center rounded-md bg-[#07C160] text-white">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path
@@ -9467,10 +9457,7 @@ LIMIT 100;</pre
</div>
</button>
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://github.com/t8y2/dbx')">
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
{{ t("settings.project") }}
</div>
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
<div class="flex items-center gap-2 text-sm font-medium">
<img src="https://cdn.simpleicons.org/github/181717" alt="GitHub" class="h-7 w-7 rounded-md bg-white p-1" />
{{ t("settings.openSource") }}
<ExternalLink class="ml-auto h-3.5 w-3.5 text-muted-foreground" />
@@ -9478,10 +9465,7 @@ LIMIT 100;</pre
<div class="mt-1 text-sm text-primary">github.com/t8y2/dbx</div>
</button>
<button type="button" class="rounded-lg border p-4 text-left transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" @click="openExternalUrl('https://dbxio.com')">
<div class="text-xs font-medium uppercase tracking-wider text-muted-foreground">
{{ t("settings.project") }}
</div>
<div class="mt-3 flex items-center gap-2 text-sm font-medium">
<div class="flex items-center gap-2 text-sm font-medium">
<AppLogo class="h-7 w-7" />
{{ t("settings.officialDocs") }}
<ExternalLink class="ml-auto h-3.5 w-3.5 text-muted-foreground" />
@@ -101,7 +101,8 @@ describe("silent update lifecycle", () => {
const updater = mount({ prepareForUpdate: prepare });
await updater.initialize();
await updater.installDownloadedUpdate();
expect(mocks.checkForUpdates).not.toHaveBeenCalled();
// Only the startup recheck runs (same latest version keeps the cache); installing never needs fresh metadata or a re-download.
expect(mocks.checkForUpdates).toHaveBeenCalledTimes(1);
expect(mocks.downloadUpdate).not.toHaveBeenCalled();
expect(mocks.installDownloadedUpdate).toHaveBeenCalledWith("cached", "1.1.0");
expect(prepare.mock.invocationCallOrder[0]).toBeLessThan(mocks.installDownloadedUpdate.mock.invocationCallOrder[0]);
@@ -291,4 +292,71 @@ describe("silent update lifecycle", () => {
await vi.waitFor(() => expect(mocks.downloadUpdate).toHaveBeenCalledTimes(2));
expect(updater.updateDownloaded.value).toBe(true);
});
it("replaces a downloaded update when a newer version is released", async () => {
const newer = { ...info, latest_version: "1.2.0", release_name: "v1.2.0" };
const replacement = { ...cache, version: "1.2.0" };
mocks.getDownloadedUpdate.mockResolvedValue(cache);
mocks.checkForUpdates.mockResolvedValue(newer);
mocks.downloadUpdate.mockResolvedValue(replacement);
const updater = mount();
await updater.initialize();
await vi.waitFor(() => expect(mocks.downloadUpdate).toHaveBeenCalled());
expect(mocks.discardDownloadedUpdate).toHaveBeenCalledWith("cached");
expect(mocks.downloadUpdate).toHaveBeenCalledWith("official", "1.2.0", expect.any(String), "Changes");
expect(updater.updateDownloaded.value).toBe(true);
expect(updater.updateInfo.value?.latest_version).toBe("1.2.0");
expect(updater.phase.value).toBe("ready");
});
it("keeps a downloaded update when the released version is not newer", async () => {
mocks.getDownloadedUpdate.mockResolvedValue(cache);
mocks.checkForUpdates.mockResolvedValue({ ...info, latest_version: "1.1.0" });
const updater = mount();
await updater.initialize();
await flush();
expect(mocks.checkForUpdates).toHaveBeenCalled();
expect(mocks.discardDownloadedUpdate).not.toHaveBeenCalled();
expect(mocks.downloadUpdate).not.toHaveBeenCalled();
expect(updater.phase.value).toBe("ready");
expect(updater.updateDownloaded.value).toBe(true);
});
it("keeps a downloaded update when the remote reports an older version", async () => {
const updater = mount();
await updater.checkUpdates({ silent: true });
expect(updater.updateDownloaded.value).toBe(true);
mocks.checkForUpdates.mockResolvedValue({ ...info, latest_version: "1.0.5" });
await updater.checkUpdates({ silent: true });
expect(mocks.discardDownloadedUpdate).not.toHaveBeenCalled();
expect(mocks.downloadUpdate).toHaveBeenCalledOnce();
expect(updater.phase.value).toBe("ready");
});
it("does not disturb an installation that starts while a recheck is in flight", async () => {
mocks.getDownloadedUpdate.mockResolvedValue(cache);
const gate = deferred<typeof info>();
mocks.checkForUpdates.mockReturnValue(gate.promise);
const release = vi.fn();
const updater = mount({ prepareForUpdate: async () => release });
await updater.initialize();
await updater.installDownloadedUpdate();
gate.resolve(info);
await flush();
expect(updater.phase.value).toBe("restart");
expect(mocks.discardDownloadedUpdate).not.toHaveBeenCalled();
expect(mocks.downloadUpdate).not.toHaveBeenCalled();
});
it("keeps the ready package when discarding a superseded update fails", async () => {
mocks.getDownloadedUpdate.mockResolvedValue(cache);
mocks.checkForUpdates.mockResolvedValue({ ...info, latest_version: "1.2.0" });
mocks.discardDownloadedUpdate.mockRejectedValue(new Error("file busy"));
const updater = mount();
await updater.initialize();
await flush();
expect(mocks.downloadUpdate).not.toHaveBeenCalled();
expect(updater.updateDownloaded.value).toBe(true);
expect(updater.updateCheckMessage.value).toContain("file busy");
});
});
+32 -3
View File
@@ -77,6 +77,13 @@ export function isUpdateIgnored(info: api.UpdateInfo | null, ignoredVersion: str
return compareParsedUpdateVersions(parsedLatest, parsedIgnored) <= 0;
}
export function isNewerRemoteVersion(latest: string, cached: string): boolean {
const parsedLatest = parseUpdateVersion(latest);
const parsedCached = parseUpdateVersion(cached);
if (!parsedLatest || !parsedCached) return normalizeUpdateVersion(latest) !== normalizeUpdateVersion(cached);
return compareParsedUpdateVersions(parsedLatest, parsedCached) > 0;
}
export function normalizeUpdateDownloadSource(value: unknown): SettingsUpdateDownloadSource {
// Old persisted AtomGit preferences should retain their mainland mirror behavior.
if (value === "atomgit") return "cnb";
@@ -199,7 +206,9 @@ export function useAppUpdater(options: UseAppUpdaterOptions = {}) {
async function checkUpdates(checkOptions: { silent?: boolean } = {}) {
if (disposed || isIgnoringUpdate.value) return;
if (!checkOptions.silent) showUpdateDialog.value = true;
if (phase.value !== "idle" || downloaded.value || (checkOptions.silent && !notificationsEnabled.value)) return;
// A downloaded-but-uninstalled update keeps the app in the ready phase; checks
// continue so a newer release can replace the cached package.
if ((phase.value !== "idle" && phase.value !== "ready") || (checkOptions.silent && !notificationsEnabled.value)) return;
clearRetry();
const token = ++generation;
phase.value = "checking";
@@ -208,18 +217,38 @@ export function useAppUpdater(options: UseAppUpdaterOptions = {}) {
const info = await api.checkForUpdates(currentLocale(), normalizeUpdateDownloadSource(settingsStore.editorSettings.updateDownloadSource));
if (token !== generation || disposed) return;
updateInfo.value = info;
phase.value = "idle";
// Installation may have started while the check was in flight; only restore
// the phase this check itself owns.
if (phase.value === "checking") phase.value = downloaded.value ? "ready" : "idle";
if (!info.update_available) updateCheckMessage.value = t("updates.upToDate", { version: info.current_version });
if (canDownloadAndInstallUpdate(info, isTauriRuntime()) && !isUpdateIgnored(info, settingsStore.editorSettings.ignoredUpdateVersion)) {
const cached = downloaded.value;
if (cached && !isNewerRemoteVersion(info.latest_version, cached.version)) return;
if (cached && !(await discardSupersededUpdate(cached))) return;
await downloadUpdateInBackground();
}
} catch (error) {
if (token !== generation || disposed) return;
phase.value = "idle";
if (phase.value === "checking") phase.value = downloaded.value ? "ready" : "idle";
fail(error);
scheduleRetry();
}
}
async function discardSupersededUpdate(cache: api.DownloadedUpdate): Promise<boolean> {
if (isInstallingUpdate.value || isIgnoringUpdate.value) return false;
try {
await api.discardDownloadedUpdate(cache.cache_id);
if (downloaded.value?.cache_id === cache.cache_id) {
downloaded.value = null;
downloadProgress.value = null;
if (phase.value === "ready" || phase.value === "checking") phase.value = "idle";
}
return true;
} catch (error) {
fail(error);
return false;
}
}
async function downloadUpdateInBackground() {
if (disposed || isIgnoringUpdate.value || phase.value !== "idle" || downloaded.value || !canDownloadAndInstallUpdate(updateInfo.value, isTauriRuntime())) return;
const version = updateInfo.value!.latest_version;
-2
View File
@@ -7779,11 +7779,9 @@ export default withEnglishFallback({
settingsTransferImportConfirmAction: "Parametrləri yüklə",
settingsTransferImportLoaded: "Parametrlər yükləndi. Onları yadda saxlamaq üçün Tətbiq et düyməsinə klikləyin.",
settingsTransferCategoryOther: "Digər",
community: "İcma",
qqGroup: "QQ qrupu",
wechatGroup: "WeChat qrupu",
wechatGroupInvite: "Tencent Docs dəvəti",
project: "Layihə",
openSource: "Açıq mənbəli kod deposu",
officialDocs: "Rəsmi sənədlər",
changelogTitle: "Dəyişikliklər jurnalı",
-2
View File
@@ -7968,11 +7968,9 @@ export default {
settingsTransferImportConfirmAction: "Load settings",
settingsTransferImportLoaded: "Settings loaded. Click Apply to save them.",
settingsTransferCategoryOther: "Other",
community: "Community",
qqGroup: "QQ Group",
wechatGroup: "WeChat Group",
wechatGroupInvite: "Tencent Docs invite",
project: "Project",
openSource: "Open-source repository",
officialDocs: "Official docs",
changelogTitle: "Changelog",
-2
View File
@@ -7469,11 +7469,9 @@ export default withEnglishFallback({
settingsTransferImportConfirmAction: "Cargar configuración",
settingsTransferImportLoaded: "Configuración cargada. Haz clic en «Aplicar» para guardarla.",
settingsTransferCategoryOther: "Otro",
community: "Comunidad",
qqGroup: "Grupo QQ",
wechatGroup: "Grupo WeChat",
wechatGroupInvite: "Invitación de Tencent Docs",
project: "Proyecto",
openSource: "Repositorio de código abierto",
officialDocs: "Documentación oficial",
changelogTitle: "Registro de cambios",
-2
View File
@@ -7469,11 +7469,9 @@ export default withEnglishFallback({
settingsTransferImportConfirmAction: "Carica impostazioni",
settingsTransferImportLoaded: "Impostazioni caricate. Fai clic su «Applica» per salvarle.",
settingsTransferCategoryOther: "Altro",
community: "Community",
qqGroup: "Gruppo QQ",
wechatGroup: "Gruppo WeChat",
wechatGroupInvite: "Invito Tencent Docs",
project: "Progetto",
openSource: "Repository open-source",
officialDocs: "Documenti ufficiali",
changelogTitle: "Registro modifiche",
-2
View File
@@ -7495,11 +7495,9 @@ export default withEnglishFallback({
settingsTransferImportConfirmAction: "設定を読み込む",
settingsTransferImportLoaded: "設定を読み込みました。「適用」をクリックして保存してください。",
settingsTransferCategoryOther: "その他",
community: "コミュニティ",
qqGroup: "QQグループ",
wechatGroup: "WeChatグループ",
wechatGroupInvite: "Tencent Docs招待",
project: "プロジェクト",
openSource: "オープンソースリポジトリ",
officialDocs: "公式ドキュメント",
changelogTitle: "更新履歴",
-2
View File
@@ -7239,11 +7239,9 @@ export default withEnglishFallback({
settingsTransferImportConfirmAction: "설정 불러오기",
settingsTransferImportLoaded: "설정을 불러왔습니다. 저장하려면 '적용'을 클릭하세요.",
settingsTransferCategoryOther: "기타",
community: "커뮤니티",
qqGroup: "QQ 그룹",
wechatGroup: "위챗 그룹",
wechatGroupInvite: "텐센트 문서 초대",
project: "프로젝트",
openSource: "오픈 소스 저장소",
officialDocs: "공식 문서",
changelogTitle: "변경 이력",
-2
View File
@@ -7471,11 +7471,9 @@ export default withEnglishFallback({
settingsTransferImportConfirmAction: "Carregar configurações",
settingsTransferImportLoaded: "Configurações carregadas. Clique em «Aplicar» para salvá-las.",
settingsTransferCategoryOther: "Outro",
community: "Comunidade",
qqGroup: "Grupo do QQ",
wechatGroup: "Grupo do WeChat",
wechatGroupInvite: "Convite do Tencent Docs",
project: "Projeto",
openSource: "Repositório de código aberto",
officialDocs: "Documentação oficial",
changelogTitle: "Registro de alterações",
-2
View File
@@ -7633,11 +7633,9 @@ export default withEnglishFallback({
supportInfoOperatingSystem: "İşletim Sistemi",
supportInfoArchitecture: "Mimari",
supportInfoUnknown: "Bilinmiyor",
community: "Community",
qqGroup: "QQ Grubu",
wechatGroup: "WeChat Grubu",
wechatGroupInvite: "Tencent Docs daveti",
project: "Proje",
openSource: "Açık kaynak deposu",
officialDocs: "Resmî belgeler",
changelogTitle: "Değişiklik günlüğü",
-2
View File
@@ -7944,11 +7944,9 @@ export default withEnglishFallback({
settingsTransferImportConfirmAction: "载入设置",
settingsTransferImportLoaded: "已载入设置,请点击“应用”保存",
settingsTransferCategoryOther: "其他",
community: "社区",
qqGroup: "QQ 群",
wechatGroup: "微信交流群",
wechatGroupInvite: "腾讯文档邀请",
project: "项目",
openSource: "开源仓库",
officialDocs: "官方文档",
changelogTitle: "更新日志",
-2
View File
@@ -6491,11 +6491,9 @@ export default withEnglishFallback({
settingsTransferImportConfirmAction: "載入設定",
settingsTransferImportLoaded: "已載入設定,請點擊「套用」儲存",
settingsTransferCategoryOther: "其他",
community: "社群",
qqGroup: "QQ 群",
wechatGroup: "微信交流群",
wechatGroupInvite: "騰訊文件邀請",
project: "專案",
openSource: "開源倉庫",
officialDocs: "官方文件",
changelogTitle: "更新日誌",
@@ -173,7 +173,6 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ id: "appearance-icons", category: "appearance", titleKey: "settings.iconTheme", targetId: "appearance", visible: desktopOnly },
{ id: "appearance-tray", category: "appearance", titleKey: "settings.showTrayIcon", descriptionKey: "settings.showTrayIconDescription", targetId: "appearance", visible: desktopOnly },
{ id: "appearance-quit", category: "appearance", titleKey: "settings.quitOnClose", descriptionKey: "settings.quitOnCloseDescription", targetId: "appearance", visible: desktopOnly },
{ id: "appearance-updates", category: "appearance", titleKey: "settings.updateNotificationsEnabled", descriptionKey: "settings.updateNotificationsEnabledDescription", targetId: "appearance" },
{ id: "appearance-debug-logs", category: "appearance", titleKey: "settings.debugLoggingEnabled", descriptionKey: "settings.debugLoggingEnabledDescription", targetId: "appearance", visible: desktopOnly },
{ id: "navigation", category: "navigation", titleKey: "settings.navigationTab", targetId: "navigation" },
{ id: "navigation-sidebar", category: "navigation", titleKey: "settings.sidebarActivation", targetId: "navigation" },
@@ -263,6 +262,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ id: "security-password", category: "security", titleKey: "auth.changePassword", targetId: "security", visible: webOnly },
{ id: "about-support", category: "about", titleKey: "settings.supportInfoTitle", descriptionKey: "settings.supportInfoDescription", targetId: "about" },
{ id: "about-transfer", category: "about", titleKey: "settings.settingsTransferTitle", descriptionKey: "settings.settingsTransferDescription", targetId: "about" },
{ id: "about-update-notifications", category: "about", titleKey: "settings.updateNotificationsEnabled", descriptionKey: "settings.updateNotificationsEnabledDescription", targetId: "about" },
{ id: "about-update", category: "about", titleKey: "settings.updateDownloadSource", descriptionKey: "settings.updateDownloadSourceDescription", targetId: "about" },
];
@@ -71,7 +71,7 @@ export function serializeSettingsTransfer(settings: EditorSettings, meta: Settin
const SETTINGS_TRANSFER_CATEGORY_ORDER: readonly SettingsTransferCategoryId[] = ["appearance", "editor", "formatter", "navigation", "data", "shortcuts", "snippets", "other"];
const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readonly EditorSettingsDraftKey[]> = {
appearance: ["fontFamily", "fontSize", "tableFontFamily", "uiFontFamily", "uiScale", "theme", "customThemes", "activeCustomThemeId", "backgroundImage", "toolbarItems", "updateNotificationsEnabled"],
appearance: ["fontFamily", "fontSize", "tableFontFamily", "uiFontFamily", "uiScale", "theme", "customThemes", "activeCustomThemeId", "backgroundImage", "toolbarItems"],
editor: [
"executeMode",
"defaultTransactionMode",
@@ -171,7 +171,7 @@ const SETTINGS_TRANSFER_CATEGORY_KEYS: Record<SettingsTransferCategoryId, readon
],
shortcuts: ["shortcuts", "sqlShortcuts"],
snippets: ["snippets"],
other: ["updateDownloadSource"],
other: ["updateDownloadSource", "updateNotificationsEnabled"],
};
const KEY_TO_CATEGORY = new Map<string, SettingsTransferCategoryId>();