diff --git a/apps/desktop/changelog/0.6.0.md b/apps/desktop/changelog/0.6.0.md index b2d04149c..6d1a26486 100644 --- a/apps/desktop/changelog/0.6.0.md +++ b/apps/desktop/changelog/0.6.0.md @@ -1,33 +1,3 @@ # What's New in v0.6.0 -## Shiny New Things - -- Import and export your Actions (394d00f) -- Add a bio, website, and social links to your profile (507a525) -- Upload a profile picture -- Use video duration as an Action condition - -## Improvements - -- A snazzy new look for your personal profile -- Redesigned the Actions page (1ace5ea) -- Redesigned the RSSHub page (f9aca60) -- Added length limits to certain profile fields -- Simplified default commands in the entry tool (85122fb) -- Enhanced UI labels and descriptions for clarity (2ed9f70) -- Gradually rolling out an experimental unified local database for mobile and desktop (#3897 #3902) -- Polished image-preview styling (cf72753) -- Refined toast notifications (73f8011) - -## No Longer Broken - -- More reliable automatic recovery after database-migration failures (c2e0c3d) -- Fixed unread counts not clearing in the macOS Docker build (70255af) -- Fixed old entries showing during initial load (24ae065) -- Fixed handling of links starting with `.` (de8eac8) -- Fixed text-to-speech not working (82952b0) -- Fixed star/unstar status not syncing across devices (fbd0b3) - -## Thanks - -Special thanks to volunteer contributors @kovsu @huanfe1 @cscnk52 @Olexandr88 @0-o0 @kingsword09 @ericyzhu for their valuable contributions +This version has been withdrawn. diff --git a/apps/desktop/changelog/0.6.1.md b/apps/desktop/changelog/0.6.1.md new file mode 100644 index 000000000..34340c1a0 --- /dev/null +++ b/apps/desktop/changelog/0.6.1.md @@ -0,0 +1,33 @@ +# What's New in v0.6.1 + +## Shiny New Things + +- Import and export your Actions (394d00f) +- Add a bio, website, and social links to your profile (507a525) +- Upload a profile picture +- Use video duration as an Action condition + +## Improvements + +- A snazzy new look for your personal profile +- Redesigned the Actions page (1ace5ea) +- Redesigned the RSSHub page (f9aca60) +- Added length limits to certain profile fields +- Simplified default commands in the entry tool (85122fb) +- Enhanced UI labels and descriptions for clarity (2ed9f70) +- Gradually rolling out an experimental unified local database for mobile and desktop (#3897 #3902) +- Polished image-preview styling (cf72753) +- Refined toast notifications (73f8011) + +## No Longer Broken + +- More reliable automatic recovery after database-migration failures (c2e0c3d) +- Fixed unread counts not clearing in the macOS Docker build (70255af) +- Fixed old entries showing during initial load (24ae065) +- Fixed handling of links starting with `.` (de8eac8) +- Fixed text-to-speech not working (82952b0) +- Fixed star/unstar status not syncing across devices (fbd0b3) + +## Thanks + +Special thanks to volunteer contributors @kovsu @huanfe1 @cscnk52 @Olexandr88 @0-o0 @kingsword09 @ericyzhu for their valuable contributions diff --git a/apps/desktop/configs/vite.electron-render.config.ts b/apps/desktop/configs/vite.electron-render.config.ts index a502f18b1..22e38987d 100644 --- a/apps/desktop/configs/vite.electron-render.config.ts +++ b/apps/desktop/configs/vite.electron-render.config.ts @@ -1,4 +1,5 @@ -import { dirname, resolve } from "node:path" +import { dirname } from "node:path" +import { resolve } from "node:path/posix" import { fileURLToPath } from "node:url" import { tsImport } from "tsx/esm/api" diff --git a/apps/desktop/forge.config.cts b/apps/desktop/forge.config.cts index 8d8a56db6..d2fe92d60 100644 --- a/apps/desktop/forge.config.cts +++ b/apps/desktop/forge.config.cts @@ -35,7 +35,7 @@ const ymlMapsMap = { win32: "latest.yml", } -const keepModules = new Set(["font-list", "vscode-languagedetection", "fast-folder-size"]) +const keepModules = new Set(["font-list", "vscode-languagedetection"]) const keepLanguages = new Set(["en", "en_GB", "en-US", "en_US"]) // remove folders & files not to be included in the app diff --git a/apps/desktop/layer/main/package.json b/apps/desktop/layer/main/package.json index a847c30fa..ef4f2823b 100644 --- a/apps/desktop/layer/main/package.json +++ b/apps/desktop/layer/main/package.json @@ -36,7 +36,6 @@ "electron-store": "10.1.0", "electron-updater": "6.6.2", "es-toolkit": "1.39.3", - "fast-folder-size": "2.4.0", "font-list": "1.5.1", "i18next": "25.2.1", "js-yaml": "4.1.0", diff --git a/apps/desktop/layer/main/src/ipc/services/app.ts b/apps/desktop/layer/main/src/ipc/services/app.ts index 5650ec723..88eaf68b9 100644 --- a/apps/desktop/layer/main/src/ipc/services/app.ts +++ b/apps/desktop/layer/main/src/ipc/services/app.ts @@ -1,13 +1,16 @@ +import fsp from "node:fs/promises" import path from "node:path" import { fileURLToPath } from "node:url" import { callWindowExpose } from "@follow/shared/bridge" import { DEV } from "@follow/shared/constants" -import { app, BrowserWindow, clipboard, dialog } from "electron" +import { app, BrowserWindow, clipboard, dialog, shell } from "electron" +import { getCacheSize } from "~/lib/cleaner" import { i18n } from "~/lib/i18n" +import { store, StoreKey } from "~/lib/store" import { registerAppTray } from "~/lib/tray" -import { logger } from "~/logger" +import { logger, revealLogFile } from "~/logger" import { AppManager } from "~/manager/app" import { WindowManager } from "~/manager/window" import { cleanupOldRender, loadDynamicRenderEntry } from "~/updater/hot-updater" @@ -179,4 +182,73 @@ export class AppService extends IpcService { return path.join(app.getAppPath(), input) } + + @IpcMethod() + openCacheFolder(_context: IpcContext): void { + const dir = path.join(app.getPath("userData"), "cache") + shell.openPath(dir) + } + + @IpcMethod() + getCacheLimit(_context: IpcContext): number { + return store.get(StoreKey.CacheSizeLimit) || 0 + } + + @IpcMethod() + async clearCache(_context: IpcContext): Promise { + const cachePath = path.join(app.getPath("userData"), "cache", "Cache_Data") + if (process.platform === "win32") { + // Request elevation on Windows + + try { + // Create a bat file to delete cache with elevated privileges + const batPath = path.join(app.getPath("temp"), "clear_cache.bat") + await fsp.writeFile(batPath, `@echo off\nrd /s /q "${cachePath}"\ndel "%~f0"`, "utf-8") + + // Execute the bat file with admin privileges + await shell.openPath(batPath) + return + } catch (err) { + logger.error("Failed to clear cache with elevation", { error: err }) + } + } + await fsp.rm(cachePath, { recursive: true, force: true }).catch(() => { + logger.error("Failed to clear cache") + }) + } + + // getCacheLimit: t.procedure.action(async () => { + // return store.get(StoreKey.CacheSizeLimit) + // }), + + // clearCache: t.procedure.action(async () => { + + // }), + + // limitCacheSize: t.procedure.input().action(async ({ input }) => { + // logger.info("set limitCacheSize", input) + // if (input === 0) { + // store.delete(StoreKey.CacheSizeLimit) + // } else { + // store.set(StoreKey.CacheSizeLimit, input) + // } + // }), + @IpcMethod() + limitCacheSize(_context: IpcContext, input: number): void { + if (input === 0) { + store.delete(StoreKey.CacheSizeLimit) + } else { + store.set(StoreKey.CacheSizeLimit, input) + } + } + + @IpcMethod() + revealLogFile(_context: IpcContext) { + return revealLogFile() + } + + @IpcMethod() + getCacheSize(_context: IpcContext) { + return getCacheSize() + } } diff --git a/apps/desktop/layer/main/src/lib/cleaner.ts b/apps/desktop/layer/main/src/lib/cleaner.ts index 5b6a02f6f..6f95feda2 100644 --- a/apps/desktop/layer/main/src/lib/cleaner.ts +++ b/apps/desktop/layer/main/src/lib/cleaner.ts @@ -1,8 +1,6 @@ import { statSync } from "node:fs" import fsp from "node:fs/promises" -import { createRequire } from "node:module" import path from "node:path" -import { promisify } from "node:util" import { callWindowExpose } from "@follow/shared/bridge" import { app, dialog } from "electron" @@ -14,13 +12,37 @@ import { WindowManager } from "~/manager/window" import { t } from "./i18n" import { store, StoreKey } from "./store" -const esModuleInterop = (module: any) => { - return module.default || module +const getFolderSize = async (dir: string): Promise => { + try { + const files = await fsp.readdir(dir, { withFileTypes: true }) + const sizes = await Promise.all( + files.map(async (file) => { + const filePath = path.join(dir, file.name) + + if (file.isSymbolicLink()) { + return 0 + } + + if (file.isDirectory()) { + return await getFolderSize(filePath) + } + + if (file.isFile()) { + try { + const { size } = await fsp.stat(filePath) + return size + } catch { + return 0 + } + } + return 0 + }), + ) + return sizes.reduce((acc, size) => acc + size, 0) + } catch { + return 0 + } } -const require = createRequire(import.meta.url) -const fastFolderSize = esModuleInterop( - require("fast-folder-size"), -) as typeof import("fast-folder-size").default export const clearAllDataAndConfirm = async () => { const win = WindowManager.getMainWindow() @@ -71,12 +93,12 @@ export const clearAllData = async () => { caller.toast.error(`Error resetting app data: ${error.message}`) } } -const fastFolderSizeAsync = promisify(fastFolderSize) + export const getCacheSize = async () => { const cachePath = path.join(app.getPath("userData"), "cache") // Size is in bytes - const sizeInBytes = await fastFolderSizeAsync(cachePath).catch((error) => { + const sizeInBytes = await getFolderSize(cachePath).catch((error) => { logger.error(error) }) return sizeInBytes || 0 @@ -155,7 +177,7 @@ export const clearCacheCronJob = () => { export const checkAndCleanCodeCache = async () => { const cachePath = path.join(app.getPath("userData"), "Code Cache") - const size = await fastFolderSizeAsync(cachePath).catch((error) => { + const size = await getFolderSize(cachePath).catch((error) => { logger.error(error) }) diff --git a/apps/desktop/layer/main/src/manager/window.ts b/apps/desktop/layer/main/src/manager/window.ts index 79c501095..c4aa24972 100644 --- a/apps/desktop/layer/main/src/manager/window.ts +++ b/apps/desktop/layer/main/src/manager/window.ts @@ -298,7 +298,7 @@ class WindowManagerStatic { webviewTag: true, webSecurity: !DEV, nodeIntegration: true, - contextIsolation: true, + contextIsolation: false, }, ...this.getPlatformSpecificWindowConfig(), } diff --git a/apps/desktop/layer/renderer/src/initialize/analytics.ts b/apps/desktop/layer/renderer/src/initialize/analytics.ts index 3a9fe61a0..b243e77a5 100644 --- a/apps/desktop/layer/renderer/src/initialize/analytics.ts +++ b/apps/desktop/layer/renderer/src/initialize/analytics.ts @@ -1,10 +1,9 @@ import type { AuthSession } from "@follow/shared/hono" -import { setFirebaseTracker, setOpenPanelTracker, tracker } from "@follow/tracker" +import { setFirebaseTracker, tracker } from "@follow/tracker" import { QUERY_PERSIST_KEY } from "~/constants/app" import { ga4 } from "./ga4" -import { op } from "./op" export const initAnalytics = async () => { tracker.manager.appendUserProperties({ @@ -16,8 +15,6 @@ export const initAnalytics = async () => { setFirebaseTracker(ga4) - setOpenPanelTracker(op) - let session: AuthSession | undefined try { const queryData = JSON.parse(window.localStorage.getItem(QUERY_PERSIST_KEY) ?? "{}") diff --git a/apps/desktop/layer/renderer/src/lib/load-language.ts b/apps/desktop/layer/renderer/src/lib/load-language.ts index 7745398ec..b572d0507 100644 --- a/apps/desktop/layer/renderer/src/lib/load-language.ts +++ b/apps/desktop/layer/renderer/src/lib/load-language.ts @@ -74,6 +74,7 @@ export const loadLanguageAndApply = async (lang: string) => { } EventBus.dispatch("I18N_UPDATE", "") } else { + if (ELECTRON) return let importFilePath = "" if (IN_ELECTRON) { diff --git a/apps/desktop/layer/renderer/src/modules/settings/tabs/data-control.tsx b/apps/desktop/layer/renderer/src/modules/settings/tabs/data-control.tsx index d4ea2e4e3..42698adea 100644 --- a/apps/desktop/layer/renderer/src/modules/settings/tabs/data-control.tsx +++ b/apps/desktop/layer/renderer/src/modules/settings/tabs/data-control.tsx @@ -112,7 +112,7 @@ export const SettingDataControl = () => { description: t("general.log_file.description"), buttonText: t("general.log_file.button"), action: () => { - ;(ipcServices as any)?.revealLogFile?.() + ipcServices?.app.revealLogFile?.() }, }, ]} @@ -244,7 +244,7 @@ const CleanElectronCache = () => { {t("data_control.clean_cache.button")} { - ;(ipcServices as any)?.openCacheFolder?.() + ipcServices?.app.openCacheFolder?.() }} className="center flex" > @@ -253,7 +253,7 @@ const CleanElectronCache = () => { } action={async () => { - await (ipcServices as any)?.clearCache?.() + await ipcServices?.app.clearCache?.() queryClient.setQueryData(["app", "cache", "size"], 0) }} buttonText={t("data_control.clean_cache.button")} @@ -267,7 +267,7 @@ const AppCacheLimit = () => { const { data: cacheSize, isLoading: isLoadingCacheSize } = useQuery({ queryKey: ["app", "cache", "size"], queryFn: async () => { - const byteSize = (await (ipcServices as any)?.getCacheSize?.()) ?? 0 + const byteSize = (await ipcServices?.app.getCacheSize?.()) ?? 0 return Math.round(byteSize / 1024 / 1024) }, refetchOnMount: "always", @@ -279,13 +279,13 @@ const AppCacheLimit = () => { } = useQuery({ queryKey: ["app", "cache", "limit"], queryFn: async () => { - const size = (await (ipcServices as any)?.getCacheLimit?.()) ?? 0 + const size = (await ipcServices?.app.getCacheLimit?.()) ?? 0 return size }, }) const onChange = (value: number[]) => { - ;(ipcServices as any)?.limitCacheSize?.(value[0]!) + ipcServices?.app.limitCacheSize?.(value[0]!) refetchCacheLimit() } @@ -294,7 +294,7 @@ const AppCacheLimit = () => { const InfinitySymbol = return ( -
+