From fb08ee964d8bdeedc243e0e5242abd274aef5f85 Mon Sep 17 00:00:00 2001 From: Innei Date: Thu, 12 Dec 2024 22:18:02 +0800 Subject: [PATCH 01/39] fix(ci): should upload render assets Signed-off-by: Innei --- .github/workflows/build-render.yml | 75 ------------------------------ .github/workflows/build.yml | 12 +++++ 2 files changed, 12 insertions(+), 75 deletions(-) delete mode 100644 .github/workflows/build-render.yml diff --git a/.github/workflows/build-render.yml b/.github/workflows/build-render.yml deleted file mode 100644 index 4540694821..0000000000 --- a/.github/workflows/build-render.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: Build Electron Render - -on: - push: - tags: - - "v*" - -env: - VITE_WEB_URL: ${{ vars.VITE_WEB_URL }} - VITE_API_URL: ${{ vars.VITE_API_URL }} - VITE_IMGPROXY_URL: ${{ vars.VITE_IMGPROXY_URL }} - VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }} - VITE_OPENPANEL_CLIENT_ID: ${{ vars.VITE_OPENPANEL_CLIENT_ID }} - VITE_OPENPANEL_API_URL: ${{ vars.VITE_OPENPANEL_API_URL }} - VITE_FIREBASE_CONFIG: ${{ vars.VITE_FIREBASE_CONFIG }} - NODE_OPTIONS: --max-old-space-size=8192 - -jobs: - build-render: - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - - permissions: - id-token: write - contents: write - attestations: write - - steps: - - name: Check out Git repository - uses: actions/checkout@v4 - with: - lfs: true - - - name: Cache pnpm modules - uses: actions/cache@v4 - with: - path: ~/.pnpm-store - key: ${{ matrix.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ matrix.os }}- - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: "pnpm" - - - name: Install dependencies - run: pnpm i - - name: Build - run: pnpm build:render - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - - - name: Setup Version - id: version - uses: ./.github/actions/setup-version - - - name: Create Release Draft - uses: softprops/action-gh-release@v2 - with: - name: v${{ steps.version.outputs.APP_VERSION }} - draft: false - prerelease: true - tag_name: v${{ steps.version.outputs.APP_VERSION }} - files: | - dist/manifest.yml - dist/*.tar.gz diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5121fe827..6dbd260fc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -120,6 +120,12 @@ jobs: KEYCHAIN_PATH: ${{ runner.temp }}/app-signing.keychain-db run: npm exec turbo run //#build:macos + - name: Build Renderer + if: matrix.os == 'ubuntu-latest' + run: pnpm build:render + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + - name: Upload file uses: actions/upload-artifact@v4 env: @@ -133,6 +139,8 @@ jobs: out/make/**/*.exe out/make/**/*.AppImage out/make/**/*.yml + dist/manifest.yml + dist/*.tar.gz retention-days: 90 - name: Upload file (arm64.dmg) @@ -157,6 +165,8 @@ jobs: out/make/**/*.exe out/make/**/*.AppImage out/make/**/*.yml + dist/manifest.yml + dist/*.tar.gz - run: npx changelogithub if: github.ref_type == 'tag' || github.event.inputs.tag_version != '' @@ -182,3 +192,5 @@ jobs: out/make/**/*.exe out/make/**/*.AppImage out/make/**/*.yml + dist/manifest.yml + dist/*.tar.gz From aeaf79522be23abb64ae940a4377a92448dfd923 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:32:01 +0800 Subject: [PATCH 02/39] fix: update user profile --- .../src/modules/profile/profile-setting-form.tsx | 11 +++++++---- packages/shared/src/auth.ts | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/renderer/src/modules/profile/profile-setting-form.tsx b/apps/renderer/src/modules/profile/profile-setting-form.tsx index 1319b59303..f7ddf63b87 100644 --- a/apps/renderer/src/modules/profile/profile-setting-form.tsx +++ b/apps/renderer/src/modules/profile/profile-setting-form.tsx @@ -10,6 +10,7 @@ import { FormMessage, } from "@follow/components/ui/form/index.jsx" import { Input } from "@follow/components/ui/input/index.js" +import { updateUser } from "@follow/shared/auth" import { cn } from "@follow/utils/utils" import { zodResolver } from "@hookform/resolvers/zod" import { useMutation } from "@tanstack/react-query" @@ -19,7 +20,6 @@ import { toast } from "sonner" import { z } from "zod" import { setWhoami, useWhoami } from "~/atoms/user" -import { apiClient } from "~/lib/api-fetch" import { toastFetchError } from "~/lib/error-parser" const formSchema = z.object({ @@ -48,9 +48,12 @@ export const ProfileSettingForm = ({ }) const updateMutation = useMutation({ - mutationFn: async (values: z.infer) => - apiClient["auth-app"]["update-account"].$patch({ - json: values, + mutationFn: (values: z.infer) => + updateUser({ + // @ts-expect-error + handle: values.handle, + image: values.image, + name: values.name, }), onError: (error) => { toastFetchError(error) diff --git a/packages/shared/src/auth.ts b/packages/shared/src/auth.ts index f3a992c5a4..32b522d009 100644 --- a/packages/shared/src/auth.ts +++ b/packages/shared/src/auth.ts @@ -21,7 +21,7 @@ const authClient = createAuthClient({ plugins: serverPlugins, }) -export const { signIn, signOut, getSession, getProviders, createSession } = authClient +export const { signIn, signOut, getSession, getProviders, createSession, updateUser } = authClient export const LOGIN_CALLBACK_URL = `${WEB_URL}/login` export type LoginRuntime = "browser" | "app" From 84698e139b7f085e54f7b5f1b36db908c0a622f1 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:43:07 +0800 Subject: [PATCH 03/39] chore: fix user handle type --- .../renderer/src/modules/profile/profile-setting-form.tsx | 1 - packages/shared/src/auth.ts | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/renderer/src/modules/profile/profile-setting-form.tsx b/apps/renderer/src/modules/profile/profile-setting-form.tsx index f7ddf63b87..a78e7b0ee2 100644 --- a/apps/renderer/src/modules/profile/profile-setting-form.tsx +++ b/apps/renderer/src/modules/profile/profile-setting-form.tsx @@ -50,7 +50,6 @@ export const ProfileSettingForm = ({ const updateMutation = useMutation({ mutationFn: (values: z.infer) => updateUser({ - // @ts-expect-error handle: values.handle, image: values.image, name: values.name, diff --git a/packages/shared/src/auth.ts b/packages/shared/src/auth.ts index 32b522d009..7e71ff759c 100644 --- a/packages/shared/src/auth.ts +++ b/packages/shared/src/auth.ts @@ -1,6 +1,7 @@ import { env } from "@follow/shared/env" import type { authPlugins } from "@follow/shared/hono" import type { BetterAuthClientPlugin } from "better-auth/client" +import { inferAdditionalFields } from "better-auth/client/plugins" import { createAuthClient } from "better-auth/react" import { IN_ELECTRON, WEB_URL } from "./constants" @@ -14,6 +15,13 @@ const serverPlugins = [ id: "createSession", $InferServerPlugin: {} as (typeof authPlugins)[1], }, + inferAdditionalFields({ + user: { + handle: { + type: "string", + }, + }, + }), ] satisfies BetterAuthClientPlugin[] const authClient = createAuthClient({ From dbcd56f46891b8cf0568e5107ce4486385969edd Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:51:33 +0800 Subject: [PATCH 04/39] chore: eslint to check package & only run type-aware rules in CI --- apps/renderer/package.json | 1 - apps/server/helper/meta-map.ts | 4 +- apps/server/package.json | 5 +- eslint.config.mjs | 27 +++-- package.json | 1 + plugins/eslint/eslint-package-json.js | 144 ++++++++++++++++++++++++++ plugins/vite/generate-main-hash.ts | 6 +- pnpm-lock.yaml | 15 +-- 8 files changed, 181 insertions(+), 22 deletions(-) create mode 100644 plugins/eslint/eslint-package-json.js diff --git a/apps/renderer/package.json b/apps/renderer/package.json index b5ee2bf5a8..79d82dfc20 100644 --- a/apps/renderer/package.json +++ b/apps/renderer/package.json @@ -120,7 +120,6 @@ "@follow/hooks": "workspace:*", "@follow/logger": "workspace:*", "@follow/models": "workspace:*", - "@follow/shared": "workspace:*", "@follow/types": "workspace:*", "@follow/utils": "workspace:*", "@hono/node-server": "1.13.7", diff --git a/apps/server/helper/meta-map.ts b/apps/server/helper/meta-map.ts index ba9af11cee..487934c2b4 100644 --- a/apps/server/helper/meta-map.ts +++ b/apps/server/helper/meta-map.ts @@ -4,12 +4,12 @@ import { dirname } from "node:path" import { fileURLToPath } from "node:url" import chokidar from "chokidar" -import { glob } from "glob" +import fg from "fast-glob" const __dirname = dirname(fileURLToPath(import.meta.url)) async function generateMetaMap() { - const files = await glob("./client/pages/(main)/**/metadata.ts", { + const files = await fg.glob("./client/pages/(main)/**/metadata.ts", { cwd: path.resolve(__dirname, ".."), }) diff --git a/apps/server/package.json b/apps/server/package.json index 70e4bd318c..57a03362e6 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -33,7 +33,6 @@ "react-router": "7.0.2", "satori": "0.12.0", "sonner": "1.7.1", - "tailwindcss": "3.4.16", "use-context-selector": "2.0.0", "xss": "1.0.15" }, @@ -51,15 +50,15 @@ "daisyui": "4.12.20", "dotenv-flow": "4.1.0", "es-toolkit": "1.29.0", + "fast-glob": "^3.3.2", "foxact": "0.2.43", - "glob": "11.0.0", "html-minifier-terser": "7.2.0", "lightningcss": "1.28.2", "masonic": "4.0.1", "nanoid": "5.0.9", "path-to-regexp": "8.2.0", "react-dom": "^18.3.1", - "tailwindcss": "3.4.15", + "tailwindcss": "3.4.16", "tsup": "8.3.5", "tsx": "4.19.2", "vite": "6.0.3" diff --git a/eslint.config.mjs b/eslint.config.mjs index f1921f91ba..2769d4390d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,8 +3,11 @@ import { defineConfig, GLOB_TS_SRC } from "eslint-config-hyoban" import checkI18nJson from "./plugins/eslint/eslint-check-i18n-json.js" import noDebug from "./plugins/eslint/eslint-no-debug.js" +import packageJson from "./plugins/eslint/eslint-package-json.js" import recursiveSort from "./plugins/eslint/eslint-recursive-sort.js" +const isCI = process.env.CI === "true" || process.env.CI === "1" + export default defineConfig( { formatting: false, @@ -16,13 +19,15 @@ export default defineConfig( "resources/**", ], preferESM: false, - projectService: { - allowDefaultProject: ["apps/main/preload/index.d.ts"], - defaultProject: "tsconfig.json", - }, - typeChecked: "essential", + projectService: isCI + ? { + allowDefaultProject: ["apps/main/preload/index.d.ts"], + defaultProject: "tsconfig.json", + } + : undefined, + typeChecked: isCI ? "essential" : false, }, - { + isCI && { files: GLOB_TS_SRC, rules: { "require-await": "off", @@ -78,4 +83,14 @@ export default defineConfig( "check-i18n-json/no-extra-keys": "error", }, }, + { + files: ["apps/**/package.json", "packages/**/package.json"], + plugins: { + "ensure-package-json": packageJson, + }, + rules: { + "ensure-package-json/ensure-package-version": "error", + "ensure-package-json/no-duplicate-package": "error", + }, + }, ) diff --git a/package.json b/package.json index e546976696..c24ecd5ece 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "eslint": "^9.16.0", "eslint-config-hyoban": "^3.1.15", "fake-indexeddb": "6.0.0", + "fast-glob": "^3.3.2", "happy-dom": "15.11.7", "hono": "4.6.13", "html-minifier-terser": "7.2.0", diff --git a/plugins/eslint/eslint-package-json.js b/plugins/eslint/eslint-package-json.js new file mode 100644 index 0000000000..0370ed5367 --- /dev/null +++ b/plugins/eslint/eslint-package-json.js @@ -0,0 +1,144 @@ +// @ts-check +import fs from "node:fs" +import path from "node:path" +import process from "node:process" + +import fg from "fast-glob" + +const dependencyKeys = ["dependencies", "devDependencies"] + +/** @type {import("eslint").ESLint.Plugin} */ +export default { + rules: { + "ensure-package-version": { + meta: { + type: "problem", + docs: { + description: "Ensure that the versions of packages in the workspace are consistent", + category: "Possible Errors", + recommended: true, + }, + fixable: "code", + hasSuggestions: true, + }, + create(context) { + if (!context.filename.endsWith("package.json")) return {} + + const cwd = process.cwd() + const packageJsonFilePaths = fg.globSync( + ["packages/*/package.json", "apps/*/package.json", "package.json"], + { + cwd, + ignore: ["**/node_modules/**"], + }, + ) + + /** @type {Map} */ + const packageVersionMap = new Map() + + packageJsonFilePaths.forEach((filePath) => { + if (filePath === path.relative(cwd, context.filename)) return + + const packageJson = JSON.parse(fs.readFileSync(filePath, "utf-8")) + + dependencyKeys.forEach((key) => { + const dependencies = packageJson[key] + if (!dependencies) return + + Object.keys(dependencies).forEach((dependency) => { + if (!packageVersionMap.has(dependency)) { + packageVersionMap.set(dependency, []) + } + packageVersionMap.get(dependency)?.push({ + version: dependencies[dependency], + filePath, + }) + }) + }) + }) + + return { + "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty > JSONObjectExpression > JSONProperty"( + node, + ) { + const parent = node?.parent?.parent + if (!parent) return + const packageCategory = parent.key.value + if (!dependencyKeys.includes(packageCategory)) return + const packageName = node.key.value + const packageVersion = node.value.value + + const versions = packageVersionMap.get(packageName) + if (!versions || versions.find((v) => v.version === packageVersion)) return + + context.report({ + node, + message: `Inconsistent versions of ${packageName}: ${versions.map((v) => v.version).join(", ")}`, + suggest: versions.map((version) => ({ + desc: `Follow the version ${version.version} in ${version.filePath}`, + fix: (fixer) => fixer.replaceText(node.value, version.version), + })), + }) + }, + } + }, + }, + "no-duplicate-package": { + meta: { + type: "problem", + docs: { + description: "Ensure packages are not duplicated in one package.json", + category: "Possible Errors", + recommended: true, + }, + }, + create(context) { + if (!context.filename.endsWith("package.json")) return {} + + let json + try { + json = JSON.parse(fs.readFileSync(context.filename, "utf-8")) + } catch { + return {} + } + + const dependencyMap = new Map() + dependencyKeys.forEach((key) => { + const dependencies = json[key] + if (!dependencies) return + + if (!dependencyMap.get(key)) { + dependencyMap.set(key, new Set()) + } + + const dependencySet = dependencyMap.get(key) + Object.keys(dependencies).forEach((dependency) => { + dependencySet.add(dependency) + }) + }) + + return { + "Program > JSONExpressionStatement > JSONObjectExpression > JSONProperty > JSONObjectExpression > JSONProperty"( + node, + ) { + const parent = node?.parent?.parent + if (!parent) return + const packageCategory = parent.key.value + if (!dependencyKeys.includes(packageCategory)) return + const packageName = node.key.value + + dependencyKeys.forEach((key) => { + if (key === packageCategory) return + if (!dependencyMap.get(key)?.has(packageName)) return + + context.report({ + node, + message: `Duplicated package ${packageName} in ${key}`, + }) + }) + }, + } + }, + }, + }, +} diff --git a/plugins/vite/generate-main-hash.ts b/plugins/vite/generate-main-hash.ts index 69a60e8976..b443e1acfc 100644 --- a/plugins/vite/generate-main-hash.ts +++ b/plugins/vite/generate-main-hash.ts @@ -1,14 +1,12 @@ import { createHash } from "node:crypto" import fs from "node:fs/promises" -import { createRequire } from "node:module" import path from "node:path" -const require = createRequire(import.meta.url) -const glob = require("glob") as typeof import("glob") +import fg from "fast-glob" export async function calculateMainHash(mainDir: string): Promise { // Get all TypeScript files in the main directory recursively - const files = glob.sync("**/*.{ts,tsx}", { + const files = fg.globSync("**/*.{ts,tsx}", { cwd: mainDir, ignore: ["node_modules/**", "dist/**"], }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e2fd4496f..d11f27ada5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,6 +184,9 @@ importers: fake-indexeddb: specifier: 6.0.0 version: 6.0.0 + fast-glob: + specifier: ^3.3.2 + version: 3.3.2 happy-dom: specifier: 15.11.7 version: 15.11.7 @@ -822,9 +825,6 @@ importers: sonner: specifier: 1.7.1 version: 1.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - tailwindcss: - specifier: 3.4.16 - version: 3.4.16(ts-node@10.9.1(@types/node@22.10.1)(typescript@5.7.2)) use-context-selector: specifier: 2.0.0 version: 2.0.0(react@18.3.1)(scheduler@0.25.0) @@ -871,12 +871,12 @@ importers: es-toolkit: specifier: 1.29.0 version: 1.29.0 + fast-glob: + specifier: ^3.3.2 + version: 3.3.2 foxact: specifier: 0.2.43 version: 0.2.43(react@18.3.1) - glob: - specifier: 11.0.0 - version: 11.0.0 html-minifier-terser: specifier: 7.2.0 version: 7.2.0 @@ -895,6 +895,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + tailwindcss: + specifier: 3.4.16 + version: 3.4.16(ts-node@10.9.1(@types/node@22.10.1)(typescript@5.7.2)) tsup: specifier: 8.3.5 version: 8.3.5(jiti@2.3.3)(postcss@8.4.49)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.6.1) From 3365d7afa0b8ed880197e67133c627ca6ccdf084 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Fri, 13 Dec 2024 10:56:14 +0800 Subject: [PATCH 05/39] chore: fix lint --- eslint.config.mjs | 10 +++++----- plugins/eslint/eslint-package-json.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 2769d4390d..b805be8cd8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,7 +3,7 @@ import { defineConfig, GLOB_TS_SRC } from "eslint-config-hyoban" import checkI18nJson from "./plugins/eslint/eslint-check-i18n-json.js" import noDebug from "./plugins/eslint/eslint-no-debug.js" -import packageJson from "./plugins/eslint/eslint-package-json.js" +import packageJsonExtend from "./plugins/eslint/eslint-package-json.js" import recursiveSort from "./plugins/eslint/eslint-recursive-sort.js" const isCI = process.env.CI === "true" || process.env.CI === "1" @@ -84,13 +84,13 @@ export default defineConfig( }, }, { - files: ["apps/**/package.json", "packages/**/package.json"], + files: ["package.json", "apps/**/package.json", "packages/**/package.json"], plugins: { - "ensure-package-json": packageJson, + "package-json-extend": packageJsonExtend, }, rules: { - "ensure-package-json/ensure-package-version": "error", - "ensure-package-json/no-duplicate-package": "error", + "package-json-extend/ensure-package-version": "error", + "package-json-extend/no-duplicate-package": "error", }, }, ) diff --git a/plugins/eslint/eslint-package-json.js b/plugins/eslint/eslint-package-json.js index 0370ed5367..c0119e3304 100644 --- a/plugins/eslint/eslint-package-json.js +++ b/plugins/eslint/eslint-package-json.js @@ -76,7 +76,7 @@ export default { message: `Inconsistent versions of ${packageName}: ${versions.map((v) => v.version).join(", ")}`, suggest: versions.map((version) => ({ desc: `Follow the version ${version.version} in ${version.filePath}`, - fix: (fixer) => fixer.replaceText(node.value, version.version), + fix: (fixer) => fixer.replaceText(node.value, `"${version.version}"`), })), }) }, From 3e2603c3ea0a0659e306d4bf22f0eaa139f3f640 Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 13 Dec 2024 13:23:25 +0800 Subject: [PATCH 06/39] refactor: enhance icon transition handling and improve Zustand store proxy functionality Signed-off-by: Innei --- .../src/components/ux/transition/icon.tsx | 24 ++++++++----------- apps/renderer/src/store/utils/helper.ts | 11 ++++++++- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/apps/renderer/src/components/ux/transition/icon.tsx b/apps/renderer/src/components/ux/transition/icon.tsx index f9045c8ec5..982c571efa 100644 --- a/apps/renderer/src/components/ux/transition/icon.tsx +++ b/apps/renderer/src/components/ux/transition/icon.tsx @@ -44,13 +44,11 @@ const createIconTransition = exit={exit} /> ) : ( - cloneElement(icon1, { - className: cn(icon1ClassName, className), - key: "1", - initial, - animate, - exit, - }) + + {cloneElement(icon1, { + className: cn(icon1ClassName, className), + })} + ) ) : typeof icon2 === "string" ? ( ) : ( - cloneElement(icon2, { - className: cn(icon2ClassName, className), - key: "2", - initial, - animate, - exit, - }) + + {cloneElement(icon2, { + className: cn(icon2ClassName, className), + })} + )} ) diff --git a/apps/renderer/src/store/utils/helper.ts b/apps/renderer/src/store/utils/helper.ts index 271564f3d2..e1762bc88d 100644 --- a/apps/renderer/src/store/utils/helper.ts +++ b/apps/renderer/src/store/utils/helper.ts @@ -60,7 +60,16 @@ export const createZustandStore = { get(_, prop) { if (prop in storeMap) { - return storeMap[prop as string].getState() + return new Proxy(() => {}, { + get() { + return storeMap[prop as string].getState() + }, + apply(target, thisArg, argumentsList) { + return storeMap[prop as string].setState( + produce(storeMap[prop as string].getState(), ...argumentsList), + ) + }, + }) } return }, From 351f34289fab89f9866405f8910e6226cb88ec87 Mon Sep 17 00:00:00 2001 From: Innei Date: Fri, 13 Dec 2024 13:26:42 +0800 Subject: [PATCH 07/39] fix: update SupportCreator component styles for better layout Signed-off-by: Innei --- .../src/modules/entry-content/components/SupportCreator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/renderer/src/modules/entry-content/components/SupportCreator.tsx b/apps/renderer/src/modules/entry-content/components/SupportCreator.tsx index 596cffa24d..4a2f21571c 100644 --- a/apps/renderer/src/modules/entry-content/components/SupportCreator.tsx +++ b/apps/renderer/src/modules/entry-content/components/SupportCreator.tsx @@ -44,7 +44,7 @@ export const SupportCreator = ({ entryId }: { entryId: string }) => {
{feed.ownerUserId ? ( Date: Fri, 13 Dec 2024 16:19:48 +0800 Subject: [PATCH 08/39] feat: integrate react-query for fetching unread feed items by view - Added useQuery to fetch unread items based on the current view with a 10-minute refetch interval. - Updated imports to include necessary hooks and actions for managing unread feed state. Signed-off-by: Innei --- .../renderer/src/modules/feed-column/list.shared.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/renderer/src/modules/feed-column/list.shared.tsx b/apps/renderer/src/modules/feed-column/list.shared.tsx index 88b1e51d4c..48fc688b39 100644 --- a/apps/renderer/src/modules/feed-column/list.shared.tsx +++ b/apps/renderer/src/modules/feed-column/list.shared.tsx @@ -5,6 +5,7 @@ import { views } from "@follow/constants" import { stopPropagation } from "@follow/utils/dom" import { cn } from "@follow/utils/utils" import * as HoverCard from "@radix-ui/react-hover-card" +import { useQuery } from "@tanstack/react-query" import { AnimatePresence, m } from "framer-motion" import { memo, useCallback, useMemo, useRef, useState } from "react" import { useTranslation } from "react-i18next" @@ -23,7 +24,7 @@ import { useCategoryOpenStateByView, useSubscriptionByView, } from "~/store/subscription" -import { useFeedUnreadStore } from "~/store/unread" +import { feedUnreadActions, useFeedUnreadStore } from "~/store/unread" import { getFeedListSort, setFeedListSortBy, setFeedListSortOrder, useFeedListSort } from "./atom" import { feedColumnStyles } from "./styles" @@ -111,6 +112,15 @@ export const ListHeader = ({ view }: { view: number }) => { const expansion = Object.values(categoryOpenStateData).every((value) => value === true) useUpdateUnreadCount() + useQuery({ + queryKey: ["fetchUnreadByView", view], + queryFn: () => feedUnreadActions.fetchUnreadByView(view), + // 10 minute + refetchInterval: 1000 * 60 * 10, + refetchOnMount: "always", + refetchOnWindowFocus: true, + }) + const totalUnread = useFeedUnreadStore( useCallback( (state) => { From 14b1aa50b8e1aa905d61347cb95c6ccbd5f1d3ed Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:02:26 +0800 Subject: [PATCH 09/39] chore: no drag for react scan --- apps/renderer/src/styles/base.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/renderer/src/styles/base.css b/apps/renderer/src/styles/base.css index 28e2b2f474..be9fb6c69d 100644 --- a/apps/renderer/src/styles/base.css +++ b/apps/renderer/src/styles/base.css @@ -53,5 +53,5 @@ button { } #react-scan-toolbar { - @apply max-lg:!hidden; + @apply max-lg:!hidden no-drag-region; } From cdd76d00d275d8b50a354a3c5bcded0ec4ad9b86 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:18:52 +0800 Subject: [PATCH 10/39] chore: update lint message --- plugins/eslint/eslint-package-json.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/eslint/eslint-package-json.js b/plugins/eslint/eslint-package-json.js index c0119e3304..bea9732ba4 100644 --- a/plugins/eslint/eslint-package-json.js +++ b/plugins/eslint/eslint-package-json.js @@ -73,7 +73,7 @@ export default { context.report({ node, - message: `Inconsistent versions of ${packageName}: ${versions.map((v) => v.version).join(", ")}`, + message: `Inconsistent versions of ${packageName}: ${Array.from(new Set(versions.map((v) => v.version))).join(", ")}`, suggest: versions.map((version) => ({ desc: `Follow the version ${version.version} in ${version.filePath}`, fix: (fixer) => fixer.replaceText(node.value, `"${version.version}"`), From 0505e3df3f7b2bce393440d101f7a33b5b5fa773 Mon Sep 17 00:00:00 2001 From: ktKongTong <44502608+ktKongTong@users.noreply.github.com> Date: Sat, 14 Dec 2024 00:36:15 +0800 Subject: [PATCH 11/39] fix(header): improve border visibility for meta display (#2138) --- apps/renderer/src/modules/entry-content/header.desktop.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/renderer/src/modules/entry-content/header.desktop.tsx b/apps/renderer/src/modules/entry-content/header.desktop.tsx index 5e39de691e..c4354f70b3 100644 --- a/apps/renderer/src/modules/entry-content/header.desktop.tsx +++ b/apps/renderer/src/modules/entry-content/header.desktop.tsx @@ -29,8 +29,8 @@ function EntryHeaderImpl({ view, entryId, className, compact }: EntryHeaderProps
From ffe535d5e7f2aea0b7a0d1d1a13cdc49559044c5 Mon Sep 17 00:00:00 2001 From: X Date: Mon, 16 Dec 2024 18:40:37 +0800 Subject: [PATCH 12/39] fix: feed column flickering (#2143) * fix: feed column flickering * fix: update --- apps/renderer/src/modules/entry-column/wrapper.shared.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/renderer/src/modules/entry-column/wrapper.shared.tsx b/apps/renderer/src/modules/entry-column/wrapper.shared.tsx index 39222a4a14..b75fcb102d 100644 --- a/apps/renderer/src/modules/entry-column/wrapper.shared.tsx +++ b/apps/renderer/src/modules/entry-column/wrapper.shared.tsx @@ -1,5 +1,5 @@ export const styles = tw`relative h-0 grow` -export const animationStyles = tw`delay-100 duration-200 ease-in-out animate-in fade-in slide-in-from-bottom-24 f-motion-reduce:fade-in-0 f-motion-reduce:slide-in-from-bottom-0` +export const animationStyles = tw`duration-300 ease-in-out animate-in fade-in slide-in-from-bottom-24 f-motion-reduce:animate-none` export interface EntryColumnWrapperProps extends ComponentType { onScroll?: (e: React.UIEvent) => void From 45e37a07e8eee65ae5b02b524b5e09185c804c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E7=A9=BA=E7=9A=84=E7=9B=A1=E9=A0=AD?= <9551552+ghostendsky@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:41:03 +0800 Subject: [PATCH 13/39] feat(i18n): translations (zh-TW) (#2166) --- locales/app/zh-TW.json | 15 +++++++++++++-- locales/external/zh-TW.json | 3 +++ locales/settings/zh-TW.json | 3 +++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/locales/app/zh-TW.json b/locales/app/zh-TW.json index debcba08c8..439e8e21c4 100644 --- a/locales/app/zh-TW.json +++ b/locales/app/zh-TW.json @@ -201,6 +201,7 @@ "feed_form.fee_description": "若需訂閱此列表,需支付訂閱費用", "feed_form.feed_not_found": "未找到摘要。", "feed_form.feedback": "回饋", + "feed_form.fill_default": "填充", "feed_form.follow": "關注", "feed_form.follow_with_fee": "使用 {{fee}} Power 訂閱", "feed_form.followed": "🎉 已關注。", @@ -218,6 +219,7 @@ "feed_item.claimed_by_unknown": "其擁有者。", "feed_item.claimed_by_you": "您已認領", "feed_item.claimed_feed": "已認領摘要", + "feed_item.claimed_list": "已認領列表", "feed_item.error_since": "錯誤發生時間", "feed_item.not_publicly_visible": "在您的個人頁面上不公開顯示", "feed_view_type.articles": "文章", @@ -334,14 +336,19 @@ "sidebar.feed_actions.unfollow_feed_many": "取消關注所有選取的訂閱", "sidebar.feed_actions.unfollow_feed_many_confirm": "確認取消關注所有選取的訂閱嗎?", "sidebar.feed_actions.unfollow_feed_many_warning": "警告:此操作將取消關注所有選取的訂閱,且無法復原。", - "sidebar.feed_column.context_menu.add_feeds_to_list": "添加摘要到列表", + "sidebar.feed_column.context_menu.add_feeds_to_category": "新增摘要到類別", + "sidebar.feed_column.context_menu.add_feeds_to_list": "新增摘要到列表", "sidebar.feed_column.context_menu.change_to_other_view": "切換到其他視圖", + "sidebar.feed_column.context_menu.create_category": "新增類別", "sidebar.feed_column.context_menu.delete_category": "刪除類別", "sidebar.feed_column.context_menu.delete_category_confirmation": "刪除類別 {{folderName}}?", "sidebar.feed_column.context_menu.mark_as_read": "標記為已讀", + "sidebar.feed_column.context_menu.new_category_modal.category_name": "類別名稱", + "sidebar.feed_column.context_menu.new_category_modal.create": "創建", "sidebar.feed_column.context_menu.rename_category": "重命名類別", "sidebar.feed_column.context_menu.rename_category_error": "重命名類別失敗", "sidebar.feed_column.context_menu.rename_category_success": "類別重命名成功", + "sidebar.feed_column.context_menu.title": "新增摘要到新類別", "sidebar.select_sort_method": "選擇排序方式", "signin.continue_with": "透過 {{provider}} 登入", "signin.sign_in_to": "登入", @@ -366,17 +373,20 @@ "trending.user": "熱門用戶", "user_button.account": "帳戶", "user_button.achievement": "成就", + "user_button.actions": "自動化指令", "user_button.download_desktop_app": "下載桌面應用程式", "user_button.log_out": "登出", "user_button.power": "Power", "user_button.preferences": "偏好設定", "user_button.profile": "個人檔案", + "user_button.zen_mode": "禪定模式", "user_profile.close": "關閉", "user_profile.edit": "編輯", "user_profile.loading": "載入中", "user_profile.share": "分享", "user_profile.toggle_item_style": "切換項目樣式", "words.achievement": "成就", + "words.actions": "自動化指令", "words.add": "新增", "words.boost": "加成", "words.browser": "瀏覽器", @@ -402,10 +412,11 @@ "words.starred": "收藏", "words.title": "標題", "words.transform": "轉換", + "words.trending": "趨勢", "words.undo": "撤銷", "words.unread": "未讀", "words.user": "使用者", "words.which.all": "全部", "words.zero_items": "沒有內容", - "zen.exit": "退出 Zen 模式" + "zen.exit": "退出禪定模式" } diff --git a/locales/external/zh-TW.json b/locales/external/zh-TW.json index bd5e91de6a..7a92ab546e 100644 --- a/locales/external/zh-TW.json +++ b/locales/external/zh-TW.json @@ -9,6 +9,8 @@ "feed.follower_other": "追隨者", "feed.followsAndFeeds": "在 {{appName}} 上有 {{subscriptionCount}} 個 {{subscriptionNoun}} 和 {{feedsCount}} 個 {{feedsNoun}}", "feed.followsAndReads": "{{subscriptionCount}} 個 {{subscriptionNoun}} 和 {{readCount}} 個 {{readNoun}} 在 {{appName}}", + "feed.madeby": "作者:", + "feed.preview": "預覽", "feed.read_one": "閱讀", "feed.read_other": "閱讀數", "feed.view_feed_url": "查看鏈接", @@ -16,6 +18,7 @@ "feed_item.claimed_by_unknown": "其擁有者。", "feed_item.claimed_by_you": "您已認領", "feed_item.claimed_feed": "已認領摘要", + "feed_item.claimed_list": "已認領列表", "feed_item.error_since": "錯誤發生時間", "feed_item.not_publicly_visible": "在您的個人頁面上不公開顯示", "header.app": "應用程式", diff --git a/locales/settings/zh-TW.json b/locales/settings/zh-TW.json index 5120f53e45..b8fb3ddbc6 100644 --- a/locales/settings/zh-TW.json +++ b/locales/settings/zh-TW.json @@ -7,6 +7,7 @@ "about.socialMedia": "社群媒體", "actions.actionName": "操作 {{number}}", "actions.action_card.add": "新增", + "actions.action_card.add_action": "新增自動化指令", "actions.action_card.all": "全部", "actions.action_card.and": "和", "actions.action_card.block": "封鎖", @@ -107,6 +108,8 @@ "feeds.tableHeaders.subscriptionCount": "訂閱數", "feeds.tableHeaders.tipAmount": "收到的贊助", "general.app": "App", + "general.auto_expand_long_social_media.description": "自動擴展包含長文字的社群媒體條目。", + "general.auto_expand_long_social_media.label": "拓展長社群媒體", "general.auto_group.description": "自動依照網址域名分類訂閱源。", "general.auto_group.label": "自動分類", "general.cache": "快取", From 7a6edb4575ce80310e2f3977a03e363b363a6387 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:13:46 +0800 Subject: [PATCH 14/39] feat: register or login with email and password (#2075) * feat: login with password * chore: auto-fix linting and formatting issues * feat: show email * refactor: login form * chore: update * fix: show login error * chore: update * credential provider * feat: confirm password * chore: update * revokeOtherSessions when update password * changelog * typecheck * chore: update * chore: update hono * feat: forget password * chore: update * feat: reset password page * feat: register form * chore: update * chore: update * chore: update * chore: update * fix: email login handler * fix: navigate to login after register * chore: remove forget password button for now * chore: update * feat: forget password page * chore: update hono * fix: forget-password link * feat: login email text * refactor: enhance login and forget password functionality - Updated the forget password page to include a back navigation button using MotionButtonBase. - Refactored the login component to utilize the new Login module, simplifying the structure. - Adjusted translations for consistency in the login text across English and German locales. - Improved the useAuthProviders hook to return a more structured AuthProvider interface. Signed-off-by: Innei * feat: add form validation and UI enhancements for login-related pages - Introduced form validation state management in forget-password, register, and reset-password components. - Updated button states to be disabled when forms are invalid, improving user experience. - Enhanced UI elements with consistent styling and layout adjustments, including the addition of MotionButtonBase for navigation. - Improved accessibility and responsiveness of card components. Signed-off-by: Innei * feat: enhance login component with dynamic provider buttons - Added MotionButtonBase for improved button animations and interactions. - Refactored the rendering logic to conditionally display login options based on the presence of credential providers. - Introduced a new icon mapping for providers to enhance visual representation. - Improved layout and styling for better user experience during login. Signed-off-by: Innei * feat: add GitHub provider icon and adjust button margin in login component - Introduced a GitHub icon to the provider icon map for enhanced visual representation. - Adjusted the margin of the submit button to improve layout consistency. Signed-off-by: Innei * chore: update --------- Signed-off-by: Innei Co-authored-by: hyoban Co-authored-by: DIYgod Co-authored-by: Innei --- .../modules/profile/profile-setting-form.tsx | 5 + .../modules/profile/update-password-form.tsx | 138 +++++ .../src/pages/settings/(settings)/profile.tsx | 2 + apps/renderer/src/queries/auth.ts | 19 +- apps/server/client/modules/login/index.tsx | 302 ++++++++++ .../client/pages/(login)/forget-password.tsx | 112 ++++ apps/server/client/pages/(login)/login.tsx | 132 +---- apps/server/client/pages/(login)/register.tsx | 139 +++++ .../client/pages/(login)/reset-password.tsx | 130 ++++ apps/server/client/query/users.ts | 19 +- apps/server/package.json | 4 +- changelog/next.md | 2 + locales/external/de.json | 2 +- locales/external/en.json | 27 +- locales/settings/en.json | 7 + packages/shared/src/auth.ts | 35 +- packages/shared/src/hono.ts | 556 ++++++++++++------ pnpm-lock.yaml | 6 + 18 files changed, 1296 insertions(+), 341 deletions(-) create mode 100644 apps/renderer/src/modules/profile/update-password-form.tsx create mode 100644 apps/server/client/modules/login/index.tsx create mode 100644 apps/server/client/pages/(login)/forget-password.tsx create mode 100644 apps/server/client/pages/(login)/register.tsx create mode 100644 apps/server/client/pages/(login)/reset-password.tsx diff --git a/apps/renderer/src/modules/profile/profile-setting-form.tsx b/apps/renderer/src/modules/profile/profile-setting-form.tsx index a78e7b0ee2..9ee4a65e97 100644 --- a/apps/renderer/src/modules/profile/profile-setting-form.tsx +++ b/apps/renderer/src/modules/profile/profile-setting-form.tsx @@ -10,6 +10,7 @@ import { FormMessage, } from "@follow/components/ui/form/index.jsx" import { Input } from "@follow/components/ui/input/index.js" +import { Label } from "@follow/components/ui/label/index.js" import { updateUser } from "@follow/shared/auth" import { cn } from "@follow/utils/utils" import { zodResolver } from "@hookform/resolvers/zod" @@ -74,6 +75,10 @@ export const ProfileSettingForm = ({ return (
+
+ +

{user?.email}

+
data.newPassword === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], + }) + +const UpdateExistingPasswordForm = () => { + const { t } = useTranslation("settings") + + const form = useForm>({ + resolver: zodResolver(updatePasswordFormSchema), + defaultValues: { + currentPassword: "", + newPassword: "", + confirmPassword: "", + }, + }) + + const updateMutation = useMutation({ + mutationFn: async (values: z.infer) => { + const res = await changePassword({ + currentPassword: values.currentPassword, + newPassword: values.newPassword, + revokeOtherSessions: true, + }) + if (res.error) { + throw new Error(res.error.message) + } + }, + onError: (error) => { + toast.error(error.message) + }, + onSuccess: (_) => { + toast(t("profile.update_password_success"), { + duration: 3000, + }) + form.reset() + }, + }) + + function onSubmit(values: z.infer) { + updateMutation.mutate(values) + } + + return ( + + + ( + + {t("profile.change_password.label")} + + + + + + )} + /> + ( + + + + + + + )} + /> + ( + + + + + + + )} + /> +
+ +
+ + + ) +} + +export const UpdatePasswordForm = () => { + const { data: hasPassword, isLoading } = useHasPassword() + + if (isLoading || !hasPassword) { + return null + } + + return +} diff --git a/apps/renderer/src/pages/settings/(settings)/profile.tsx b/apps/renderer/src/pages/settings/(settings)/profile.tsx index 510dfac7ca..0a3464f9dc 100644 --- a/apps/renderer/src/pages/settings/(settings)/profile.tsx +++ b/apps/renderer/src/pages/settings/(settings)/profile.tsx @@ -1,4 +1,5 @@ import { ProfileSettingForm } from "~/modules/profile/profile-setting-form" +import { UpdatePasswordForm } from "~/modules/profile/update-password-form" import { SettingsTitle } from "~/modules/settings/title" import { defineSettingPageData } from "~/modules/settings/utils" @@ -15,6 +16,7 @@ export function Component() { <> + ) } diff --git a/apps/renderer/src/queries/auth.ts b/apps/renderer/src/queries/auth.ts index 354d9b4199..16751ddba3 100644 --- a/apps/renderer/src/queries/auth.ts +++ b/apps/renderer/src/queries/auth.ts @@ -1,4 +1,4 @@ -import { getSession } from "@follow/shared/auth" +import { getSession, listAccounts } from "@follow/shared/auth" import type { AuthSession } from "@follow/shared/hono" import type { FetchError } from "ofetch" @@ -7,6 +7,23 @@ import { defineQuery } from "~/lib/defineQuery" export const auth = { getSession: () => defineQuery(["auth", "session"], () => getSession()), + getAccounts: () => + defineQuery(["auth", "accounts"], async () => { + const accounts = await listAccounts() + return accounts.data as Array<{ id: string; provider: string }> + }), +} + +export const useAccounts = () => { + return useAuthQuery(auth.getAccounts()) +} + +export const useHasPassword = () => { + const accounts = useAccounts() + return { + ...accounts, + data: !!accounts.data?.find((account) => account.provider === "credential"), + } } export const useSession = (options?: { enabled?: boolean }) => { diff --git a/apps/server/client/modules/login/index.tsx b/apps/server/client/modules/login/index.tsx new file mode 100644 index 0000000000..949408ae08 --- /dev/null +++ b/apps/server/client/modules/login/index.tsx @@ -0,0 +1,302 @@ +import { UserAvatar } from "@client/components/ui/user-avatar" +import { queryClient } from "@client/lib/query-client" +import { useSession } from "@client/query/auth" +import { useAuthProviders } from "@client/query/users" +import { Logo } from "@follow/components/icons/logo.jsx" +import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.jsx" +import { Button, MotionButtonBase } from "@follow/components/ui/button/index.js" +import { Divider } from "@follow/components/ui/divider/index.js" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@follow/components/ui/form/index.jsx" +import { Input } from "@follow/components/ui/input/index.js" +import { LoadingCircle } from "@follow/components/ui/loading/index.jsx" +import { authProvidersConfig } from "@follow/constants" +import { createSession, loginHandler, signOut } from "@follow/shared/auth" +import { DEEPLINK_SCHEME } from "@follow/shared/constants" +import { cn } from "@follow/utils/utils" +import { zodResolver } from "@hookform/resolvers/zod" +import { useCallback, useEffect, useMemo, useRef, useState } from "react" +import { useForm } from "react-hook-form" +import { Trans, useTranslation } from "react-i18next" +import { Link, useLocation, useNavigate } from "react-router" +import { toast } from "sonner" +import { z } from "zod" + +const overrideProviderIconMap: Record = { + apple: , + github: , +} + +export function Login() { + const { status, refetch } = useSession() + + const [redirecting, setRedirecting] = useState(false) + + const { data: authProviders, isLoading } = useAuthProviders() + + const location = useLocation() + const urlParams = new URLSearchParams(location.search) + const provider = urlParams.get("provider") + const isCredentialProvider = provider === "credential" + + const isAuthenticated = status === "authenticated" + + const { t } = useTranslation("external") + + useEffect(() => { + if (provider && !isCredentialProvider && status === "unauthenticated") { + loginHandler(provider) + setRedirecting(true) + } + }, [isCredentialProvider, provider, status]) + + const getCallbackUrl = useCallback(async () => { + const { data } = await createSession() + if (!data) return null + return { + url: `${DEEPLINK_SCHEME}auth?ck=${data.ck}&userId=${data.userId}`, + userId: data.userId, + } + }, []) + + const handleOpenApp = useCallback(async () => { + const callbackUrl = await getCallbackUrl() + if (!callbackUrl) return + window.open(callbackUrl.url, "_top") + }, [getCallbackUrl]) + + const onceRef = useRef(false) + useEffect(() => { + if (isAuthenticated && !onceRef.current) { + handleOpenApp() + } + onceRef.current = true + }, [handleOpenApp, isAuthenticated]) + + const LoginOrStatusContent = useMemo(() => { + switch (true) { + case isAuthenticated: { + return ( +
+
+ +
+ +
+
+

+ {t("redirect.successMessage", { app_name: APP_NAME })}
+
+ {t("redirect.instruction", { app_name: APP_NAME })} +

+
+ + + +
+
+ ) + } + default: { + if (!authProviders?.credential) { + return ( +
+ {Object.entries(authProviders || []) + .filter(([key]) => key !== "credential") + .map(([key, provider]) => ( + + ))} +
+ ) + } else { + return ( + <> + +
+
+ +

{t("login.or")}

+ +
+
+
+ {Object.entries(authProviders || []) + .filter(([key]) => key !== "credential") + .map(([key, provider]) => ( + { + loginHandler(key) + }} + > + {overrideProviderIconMap[provider.id] ? ( +
+ {overrideProviderIconMap[provider.id]} +
+ ) : ( +
+ )} + + ))} +
+ + ) + } + } + } + }, [authProviders, handleOpenApp, isAuthenticated, refetch, t]) + const Content = useMemo(() => { + switch (true) { + case redirecting: { + return
{t("login.redirecting")}
+ } + default: { + return
{LoginOrStatusContent}
+ } + } + }, [LoginOrStatusContent, redirecting, t]) + + return ( +
+ + {isLoading && } + + + <> + {!isAuthenticated && !isLoading && ( +

+ {t("login.logInTo")} + {` ${APP_NAME}`} +

+ )} + {Content} + +
+
+ ) +} + +const formSchema = z.object({ + email: z.string().email(), + password: z.string().max(128), +}) + +async function onSubmit(values: z.infer) { + const res = await loginHandler("credential", "browser", values) + if (res?.error) { + toast.error(res.error.message) + return + } + queryClient.invalidateQueries({ queryKey: ["auth", "session"] }) +} + +function LoginWithPassword() { + const { t } = useTranslation("external") + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: "", + password: "", + }, + }) + const { isValid } = form.formState + const navigate = useNavigate() + + return ( +
+ + ( + + {t("login.email")} + + + + + + )} + /> + ( + + {t("login.password")} + + + + + + )} + /> + + {t("login.forget_password.note")} + + + + + + ) +} diff --git a/apps/server/client/pages/(login)/forget-password.tsx b/apps/server/client/pages/(login)/forget-password.tsx new file mode 100644 index 0000000000..7b1f800c83 --- /dev/null +++ b/apps/server/client/pages/(login)/forget-password.tsx @@ -0,0 +1,112 @@ +import { Button, MotionButtonBase } from "@follow/components/ui/button/index.jsx" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@follow/components/ui/card/index.jsx" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@follow/components/ui/form/index.jsx" +import { Input } from "@follow/components/ui/input/index.js" +import { forgetPassword } from "@follow/shared/auth" +import { env } from "@follow/shared/env" +import { zodResolver } from "@hookform/resolvers/zod" +import { useMutation } from "@tanstack/react-query" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import { useNavigate } from "react-router" +import { toast } from "sonner" +import { z } from "zod" + +const forgetPasswordFormSchema = z.object({ + email: z.string().email(), +}) + +export function Component() { + const { t } = useTranslation("external") + const form = useForm>({ + resolver: zodResolver(forgetPasswordFormSchema), + defaultValues: { + email: "", + }, + }) + + const { isValid } = form.formState + const updateMutation = useMutation({ + mutationFn: async (values: z.infer) => { + const res = await forgetPassword({ + email: values.email, + redirectTo: `${env.VITE_WEB_URL}/reset-password`, + }) + if (res.error) { + throw new Error(res.error.message) + } + }, + onError: (error) => { + toast.error(error.message) + }, + onSuccess: () => { + toast.success(t("login.forget_password.success")) + }, + }) + + function onSubmit(values: z.infer) { + updateMutation.mutate(values) + } + + const navigate = useNavigate() + + return ( +
+ + + + { + history.length > 1 ? history.back() : navigate("/login") + }} + className="-ml-1 inline-flex cursor-pointer items-center" + > + + + {t("login.forget_password.label")} + + + + + {t("login.forget_password.description")} + +
+ + ( + + {t("login.email")} + + + + + + )} + /> +
+ +
+ + +
+
+
+ ) +} diff --git a/apps/server/client/pages/(login)/login.tsx b/apps/server/client/pages/(login)/login.tsx index c0d537051f..280f89782e 100644 --- a/apps/server/client/pages/(login)/login.tsx +++ b/apps/server/client/pages/(login)/login.tsx @@ -1,135 +1,5 @@ -import { UserAvatar } from "@client/components/ui/user-avatar" -import { useSession } from "@client/query/auth" -import { useAuthProviders } from "@client/query/users" -import { Logo } from "@follow/components/icons/logo.jsx" -import { Button } from "@follow/components/ui/button/index.js" -import { authProvidersConfig } from "@follow/constants" -import { createSession, loginHandler, signOut } from "@follow/shared/auth" -import { DEEPLINK_SCHEME } from "@follow/shared/constants" -import { cn } from "@follow/utils/utils" -import { useCallback, useEffect, useRef, useState } from "react" -import { useTranslation } from "react-i18next" -import { useLocation } from "react-router" +import { Login } from "@client/modules/login" export function Component() { return } - -function Login() { - const { status, refetch } = useSession() - - const [redirecting, setRedirecting] = useState(false) - - const { data: authProviders } = useAuthProviders() - const location = useLocation() - const urlParams = new URLSearchParams(location.search) - const provider = urlParams.get("provider") - - const isAuthenticated = status === "authenticated" - - const { t } = useTranslation("external") - - useEffect(() => { - if (provider && status === "unauthenticated") { - loginHandler(provider) - setRedirecting(true) - } - }, [status]) - - const getCallbackUrl = useCallback(async () => { - const { data } = await createSession() - if (!data) return null - return { - url: `${DEEPLINK_SCHEME}auth?ck=${data.ck}&userId=${data.userId}`, - userId: data.userId, - } - }, []) - - const handleOpenApp = useCallback(async () => { - const callbackUrl = await getCallbackUrl() - if (!callbackUrl) return - window.open(callbackUrl.url, "_top") - }, [getCallbackUrl]) - - const onceRef = useRef(false) - useEffect(() => { - if (isAuthenticated && !onceRef.current) { - handleOpenApp() - } - onceRef.current = true - }, [handleOpenApp, isAuthenticated]) - - return ( -
- - {!isAuthenticated && ( -

- {t("login.logInTo")} - {` ${APP_NAME}`} -

- )} - {redirecting ? ( -
{t("login.redirecting")}
- ) : ( -
- {isAuthenticated ? ( -
-
- -
- -
-
-

- {t("redirect.successMessage", { app_name: APP_NAME })}
-
- {t("redirect.instruction", { app_name: APP_NAME })} -

-
- - - -
-
- ) : ( - <> - {Object.entries(authProviders || []).map(([key, provider]) => ( - - ))} - - )} -
- )} -
- ) -} diff --git a/apps/server/client/pages/(login)/register.tsx b/apps/server/client/pages/(login)/register.tsx new file mode 100644 index 0000000000..9066b8a657 --- /dev/null +++ b/apps/server/client/pages/(login)/register.tsx @@ -0,0 +1,139 @@ +import { Logo } from "@follow/components/icons/logo.jsx" +import { Button } from "@follow/components/ui/button/index.jsx" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@follow/components/ui/form/index.jsx" +import { Input } from "@follow/components/ui/input/index.js" +import { signUp } from "@follow/shared/auth" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { Trans, useTranslation } from "react-i18next" +import { Link, useNavigate } from "react-router" +import { toast } from "sonner" +import { z } from "zod" + +export function Component() { + return ( +
+ + +
+ ) +} + +const formSchema = z + .object({ + email: z.string().email(), + password: z.string().min(8).max(128), + confirmPassword: z.string(), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], + }) + +function RegisterForm() { + const { t } = useTranslation("external") + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: "", + password: "", + confirmPassword: "", + }, + }) + + const { isValid } = form.formState + + const navigate = useNavigate() + + function onSubmit(values: z.infer) { + return signUp.email({ + email: values.email, + password: values.password, + name: values.email.split("@")[0], + callbackURL: "/", + fetchOptions: { + onSuccess() { + navigate("/login") + }, + onError(context) { + toast.error(context.error.message) + }, + }, + }) + } + + return ( +
+

+ {t("register.label", { app_name: APP_NAME })} +

+
+ + {t("register.login")} + + ), + }} + /> +
+
+ + ( + + {t("register.email")} + + + + + + )} + /> + ( + + {t("register.password")} + + + + + + )} + /> + ( + + {t("register.confirm_password")} + + + + + + )} + /> + + + +
+ ) +} diff --git a/apps/server/client/pages/(login)/reset-password.tsx b/apps/server/client/pages/(login)/reset-password.tsx new file mode 100644 index 0000000000..2dac23add9 --- /dev/null +++ b/apps/server/client/pages/(login)/reset-password.tsx @@ -0,0 +1,130 @@ +import { Button, MotionButtonBase } from "@follow/components/ui/button/index.jsx" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@follow/components/ui/card/index.jsx" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@follow/components/ui/form/index.jsx" +import { Input } from "@follow/components/ui/input/index.js" +import { resetPassword } from "@follow/shared/auth" +import { zodResolver } from "@hookform/resolvers/zod" +import { useMutation } from "@tanstack/react-query" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import { useNavigate } from "react-router" +import { toast } from "sonner" +import { z } from "zod" + +const passwordSchema = z.string().min(8).max(128) +const initPasswordFormSchema = z + .object({ + newPassword: passwordSchema, + confirmPassword: passwordSchema, + }) + .refine((data) => data.newPassword === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], + }) + +export function Component() { + const { t } = useTranslation("external") + const form = useForm>({ + resolver: zodResolver(initPasswordFormSchema), + defaultValues: { + newPassword: "", + confirmPassword: "", + }, + }) + + const { isValid } = form.formState + + const navigate = useNavigate() + const updateMutation = useMutation({ + mutationFn: async (values: z.infer) => { + const res = await resetPassword({ newPassword: values.newPassword }) + const error = res.error?.message + if (error) { + throw new Error(error) + } + }, + onError: (error) => { + toast.error(error.message) + }, + onSuccess: () => { + toast.success(t("login.reset_password.success")) + navigate("/login") + }, + }) + + function onSubmit(values: z.infer) { + updateMutation.mutate(values) + } + + return ( +
+ + + + { + history.length > 1 ? history.back() : navigate("/login") + }} + className="-ml-1 inline-flex cursor-pointer items-center" + > + + + {t("login.forget_password.label")} + + + + {t("login.reset_password.description")} +
+ + ( + + {t("login.new_password.label")} + + + + + + )} + /> + ( + + {t("login.confirm_password.label")} + + + + + + )} + /> + +
+ +
+ + +
+
+
+ ) +} diff --git a/apps/server/client/query/users.ts b/apps/server/client/query/users.ts index 1c2e155311..cdebcba5d2 100644 --- a/apps/server/client/query/users.ts +++ b/apps/server/client/query/users.ts @@ -56,20 +56,15 @@ export const useUserQuery = (handleOrId: string | undefined) => { initialData: getHydrateData(`profiles.$get,query:id=${handleOrId}`), }) } - +export interface AuthProvider { + name: string + id: string + color: string + icon: string +} export const useAuthProviders = () => { return useQuery({ queryKey: ["providers"], - queryFn: async () => (await getProviders()).data, - placeholderData: { - google: { - id: "google", - name: "Google", - }, - github: { - id: "github", - name: "GitHub", - }, - }, + queryFn: async () => (await getProviders()).data as Record, }) } diff --git a/apps/server/package.json b/apps/server/package.json index 57a03362e6..b4d5898a2f 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -27,6 +27,7 @@ "rc-modal-sheet": "0.3.2", "react": "^18.3.1", "react-blurhash": "^0.3.0", + "react-hook-form": "7.54.0", "react-hotkeys-hook": "4.6.1", "react-i18next": "^15.1.3", "react-photo-view": "1.2.6", @@ -34,7 +35,8 @@ "satori": "0.12.0", "sonner": "1.7.1", "use-context-selector": "2.0.0", - "xss": "1.0.15" + "xss": "1.0.15", + "zod": "3.23.8" }, "devDependencies": { "@follow/components": "workspace:*", diff --git a/changelog/next.md b/changelog/next.md index 17888d80b6..d7f8b9d6fb 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -2,6 +2,8 @@ ## New Features +- Register or Login with email and password + ## Improvements ## Bug Fixes diff --git a/locales/external/de.json b/locales/external/de.json index 40b9c343a9..9c662ce994 100644 --- a/locales/external/de.json +++ b/locales/external/de.json @@ -15,7 +15,7 @@ "invitation.getCodeMessage": "You can get an invitation code through the following ways:", "invitation.title": "Invitation Code", "login.backToWebApp": "Back to the web app", - "login.logInTo": "Log in to ", + "login.logInTo": "Login to ", "login.openApp": "Open app", "login.redirecting": "Redirecting", "login.welcomeTo": "Welcome to ", diff --git a/locales/external/en.json b/locales/external/en.json index ac5ff6e0d8..a0ac35eb35 100644 --- a/locales/external/en.json +++ b/locales/external/en.json @@ -34,14 +34,37 @@ "invitation.getCodeMessage": "You can get an invitation code in the following ways:", "invitation.title": "Invitation Code", "login.backToWebApp": "Back To Web App", + "login.confirm_password.label": "Confirm Password", "login.continueWith": "Continue with {{provider}}", - "login.logInTo": "Log in to ", + "login.email": "Email", + "login.forget_password.description": "Enter the email address associated with your account and we’ll send you an email about how to reset your password.", + "login.forget_password.label": "Forget Password", + "login.forget_password.note": "Forgot your password?", + "login.forget_password.success": "Email has been sent successfully", + "login.logInTo": "Login to ", + "login.logInWithEmail": "Login with email", + "login.new_password.label": "New Password", "login.openApp": "Open App", + "login.or": "Or", + "login.password": "Password", "login.redirecting": "Redirecting", + "login.register": "Create one", + "login.reset_password.description": "Enter new password and confirm it to reset your password", + "login.reset_password.label": "Reset Password", + "login.reset_password.success": "Password has been successfully reset", "login.signOut": "Sign out", + "login.signUp": "Sign up with email", + "login.submit": "Submit", "login.welcomeTo": "Welcome to ", "redirect.continueInBrowser": "Continue in Browser", "redirect.instruction": "Now is the time to open {{app_name}} and safely close this page.", "redirect.openApp": "Open {{app_name}}", - "redirect.successMessage": "You have successfully connected to {{app_name}} Account." + "redirect.successMessage": "You have successfully connected to {{app_name}} Account.", + "register.confirm_password": "Confirm Password", + "register.email": "Email", + "register.label": "Create a {{app_name}} account", + "register.login": "Login", + "register.note": "Already have an account? ", + "register.password": "Password", + "register.submit": "Create account" } diff --git a/locales/settings/en.json b/locales/settings/en.json index 7c2788fe4f..1c770ab962 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -259,14 +259,21 @@ "lists.title": "Title", "lists.view": "View", "profile.avatar.label": "Avatar", + "profile.change_password.label": "Change Password", + "profile.confirm_password.label": "Confirm Password", + "profile.current_password.label": "Current Password", + "profile.email.label": "Email", "profile.handle.description": "Your unique identifier.", "profile.handle.label": "Handle", "profile.name.description": "Your public display name.", "profile.name.label": "Display Name", + "profile.new_password.label": "New Password", + "profile.reset_password_mail_sent": "Reset password mail sent.", "profile.sidebar_title": "Profile", "profile.submit": "Submit", "profile.title": "Profile Settings", "profile.updateSuccess": "Profile updated.", + "profile.update_password_success": "Password updated.", "titles.about": "About", "titles.actions": "Actions", "titles.appearance": "Appearance", diff --git a/packages/shared/src/auth.ts b/packages/shared/src/auth.ts index 7e71ff759c..089292a818 100644 --- a/packages/shared/src/auth.ts +++ b/packages/shared/src/auth.ts @@ -19,6 +19,7 @@ const serverPlugins = [ user: { handle: { type: "string", + required: false, }, }, }), @@ -29,14 +30,44 @@ const authClient = createAuthClient({ plugins: serverPlugins, }) -export const { signIn, signOut, getSession, getProviders, createSession, updateUser } = authClient +// @keep-sorted +export const { + changePassword, + createSession, + forgetPassword, + getProviders, + getSession, + linkSocial, + listAccounts, + resetPassword, + signIn, + signOut, + signUp, + updateUser, +} = authClient export const LOGIN_CALLBACK_URL = `${WEB_URL}/login` export type LoginRuntime = "browser" | "app" -export const loginHandler = (provider: string, runtime: LoginRuntime = "app") => { +export const loginHandler = async ( + provider: string, + runtime?: LoginRuntime, + args?: { + email?: string + password?: string + }, +) => { + const { email, password } = args ?? {} if (IN_ELECTRON) { window.open(`${WEB_URL}/login?provider=${provider}`) } else { + if (provider === "credential") { + if (!email || !password) { + window.location.href = "/login" + return + } + return signIn.email({ email, password }) + } + signIn.social({ provider: provider as "google" | "github" | "apple", callbackURL: runtime === "app" ? LOGIN_CALLBACK_URL : undefined, diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts index f009cb30c9..738643bb77 100644 --- a/packages/shared/src/hono.ts +++ b/packages/shared/src/hono.ts @@ -341,7 +341,32 @@ declare const actions: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: { + name: string; + condition: ConditionItem[] | ConditionItem[][]; + result: { + disabled?: boolean; + translation?: z.infer; + summary?: boolean; + readability?: boolean; + sourceContent?: boolean; + silence?: boolean; + block?: boolean; + newEntryNotification?: boolean; + rewriteRules?: { + from: string; + to: string; + }[]; + blockRules?: { + field: z.infer; + operator: z.infer; + value: string | number; + }[]; + webhooks?: string[]; + }; + }[]; + }>; }; dialect: "pg"; }>; @@ -1004,7 +1029,38 @@ declare const airdrops: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: { + "Invitations count": number; + "Purchase lists cost": number; + "Total tip amount": number; + "Feeds subscriptions count": number; + "Lists subscriptions count": number; + "Inbox subscriptions count": number; + "Recent read count in the last month": number; + "Mint count": number; + "Claimed feeds count": number; + "Claimed feeds subscriptions count": number; + "Lists with more than 1 feed count": number; + "Created lists subscriptions count": number; + "Created lists income amount": number; + "GitHub Community Contributions": number; + "Invitations count Rank": number; + "Purchase lists cost Rank": number; + "Total tip amount Rank": number; + "Feeds subscriptions count Rank": number; + "Lists subscriptions count Rank": number; + "Inbox subscriptions count Rank": number; + "Recent read count in the last month Rank": number; + "Mint count Rank": number; + "Claimed feeds count Rank": number; + "Claimed feeds subscriptions count Rank": number; + "Lists with more than 1 feed count Rank": number; + "Created lists subscriptions count Rank": number; + "Created lists income amount Rank": number; + "GitHub Community Contributions Rank": number; + } | null; + }>; verify: drizzle_orm_pg_core.PgColumn<{ name: "verify"; tableName: "airdrops"; @@ -1485,6 +1541,15 @@ declare const CommonEntryFields: { data: string[]; driverParam: string | string[]; enumValues: [string, ...string[]]; + size: undefined; + baseBuilder: { + name: "categories"; + dataType: "string"; + columnType: "PgText"; + data: string; + enumValues: [string, ...string[]]; + driverParam: string; + }; }, { name: "categories"; dataType: "string"; @@ -1711,7 +1776,9 @@ declare const entries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: MediaModel[]; + }>; categories: drizzle_orm_pg_core.PgColumn<{ name: "categories"; tableName: "entries"; @@ -1741,10 +1808,20 @@ declare const entries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, object, object>; + }, {}, {}>; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + baseBuilder: drizzle_orm_pg_core.PgColumnBuilder<{ + name: "categories"; + dataType: "string"; + columnType: "PgText"; + data: string; + enumValues: [string, ...string[]]; + driverParam: string; + }, {}, {}, drizzle_orm.ColumnBuilderExtraConfig>; + size: undefined; + }>; attachments: drizzle_orm_pg_core.PgColumn<{ name: "attachments"; tableName: "entries"; @@ -1761,7 +1838,9 @@ declare const entries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: AttachmentsModel[]; + }>; extra: drizzle_orm_pg_core.PgColumn<{ name: "extra"; tableName: "entries"; @@ -1778,7 +1857,9 @@ declare const entries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: ExtraModel; + }>; language: drizzle_orm_pg_core.PgColumn<{ name: "language"; tableName: "entries"; @@ -2250,10 +2331,20 @@ declare const entryReadHistories: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, object, object>; + }, {}, {}>; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + baseBuilder: drizzle_orm_pg_core.PgColumnBuilder<{ + name: "user_ids"; + dataType: "string"; + columnType: "PgText"; + data: string; + enumValues: [string, ...string[]]; + driverParam: string; + }, {}, {}, drizzle_orm.ColumnBuilderExtraConfig>; + size: undefined; + }>; readCount: drizzle_orm_pg_core.PgColumn<{ name: "read_count"; tableName: "entryReadHistories"; @@ -3112,7 +3203,9 @@ declare const inboxesEntries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: MediaModel[]; + }>; categories: drizzle_orm_pg_core.PgColumn<{ name: "categories"; tableName: "inboxes_entries"; @@ -3142,10 +3235,20 @@ declare const inboxesEntries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, object, object>; + }, {}, {}>; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + baseBuilder: drizzle_orm_pg_core.PgColumnBuilder<{ + name: "categories"; + dataType: "string"; + columnType: "PgText"; + data: string; + enumValues: [string, ...string[]]; + driverParam: string; + }, {}, {}, drizzle_orm.ColumnBuilderExtraConfig>; + size: undefined; + }>; attachments: drizzle_orm_pg_core.PgColumn<{ name: "attachments"; tableName: "inboxes_entries"; @@ -3162,7 +3265,9 @@ declare const inboxesEntries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: AttachmentsModel[]; + }>; extra: drizzle_orm_pg_core.PgColumn<{ name: "extra"; tableName: "inboxes_entries"; @@ -3179,7 +3284,9 @@ declare const inboxesEntries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: ExtraModel; + }>; language: drizzle_orm_pg_core.PgColumn<{ name: "language"; tableName: "inboxes_entries"; @@ -4117,10 +4224,20 @@ declare const lists: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, object, object>; + }, {}, {}>; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + baseBuilder: drizzle_orm_pg_core.PgColumnBuilder<{ + name: "feed_ids"; + dataType: "string"; + columnType: "PgText"; + data: string; + enumValues: [string, ...string[]]; + driverParam: string; + }, {}, {}, drizzle_orm.ColumnBuilderExtraConfig>; + size: undefined; + }>; title: drizzle_orm_pg_core.PgColumn<{ name: "title"; tableName: "lists"; @@ -4710,7 +4827,9 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: Record; + }>; updateAt: drizzle_orm_pg_core.PgColumn<{ name: "update_at"; tableName: "settings"; @@ -5036,16 +5155,16 @@ declare const users: drizzle_orm_pg_core.PgTableWithColumns<{ dialect: "pg"; }>; declare function lower(handle: AnyPgColumn): SQL; -declare const usersOpenApiSchema: z.ZodObject; - email: z.ZodString; - emailVerified: z.ZodNullable; - image: z.ZodNullable; - handle: z.ZodNullable; - createdAt: z.ZodDate; - updatedAt: z.ZodDate; -}, "email">, z.UnknownKeysParam, z.ZodTypeAny, { +declare const usersOpenApiSchema: zod.ZodObject; + email: zod.ZodString; + emailVerified: zod.ZodNullable; + image: zod.ZodNullable; + handle: zod.ZodNullable; + createdAt: zod.ZodDate; + updatedAt: zod.ZodDate; +}, "email">, "strip", zod.ZodTypeAny, { name: string | null; id: string; emailVerified: boolean | null; @@ -6203,12 +6322,13 @@ declare const boosts: drizzle_orm_pg_core.PgTableWithColumns<{ declare const auth: { handler: (request: Request) => Promise; api: { - getSession: ((context: { + getSession: (context: { headers: Headers; query?: { disableCookieCache?: boolean; } | undefined; - }) => Promise<{ + asResponse?: R | undefined; + }) => false extends R ? Promise<{ user: { id: string; createdAt: Date; @@ -6239,17 +6359,17 @@ declare const auth: { toUserId: string | null; } | undefined; role: "user" | "trial"; - } | null>) & { + } | null> & { options: { method: "GET"; query: zod.ZodOptional; + disableCookieCache: zod.ZodOptional]>>; disableRefresh: zod.ZodOptional; }, "strip", zod.ZodTypeAny, { disableCookieCache?: boolean | undefined; disableRefresh?: boolean | undefined; }, { - disableCookieCache?: boolean | undefined; + disableCookieCache?: string | boolean | undefined; disableRefresh?: boolean | undefined; }>>; requireHeaders: true; @@ -6295,9 +6415,19 @@ declare const auth: { metadata: { CUSTOM_SESSION: boolean; }; + query: zod.ZodOptional]>>; + disableRefresh: zod.ZodOptional; + }, "strip", zod.ZodTypeAny, { + disableCookieCache?: boolean | undefined; + disableRefresh?: boolean | undefined; + }, { + disableCookieCache?: string | boolean | undefined; + disableRefresh?: boolean | undefined; + }>>; }; path: "/get-session"; - }; + } : Promise; } & { getProviders: { >; body: zod.ZodObject<{ callbackURL: zod.ZodOptional; + newUserCallbackURL: zod.ZodOptional; errorCallbackURL: zod.ZodOptional; - provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab")[]]>; + provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit")[]]>; disableRedirect: zod.ZodOptional; idToken: zod.ZodOptional>; }, "strip", zod.ZodTypeAny, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; idToken?: { token: string; accessToken?: string | undefined; @@ -6385,10 +6516,11 @@ declare const auth: { nonce?: string | undefined; } | undefined; callbackURL?: string | undefined; + newUserCallbackURL?: string | undefined; errorCallbackURL?: string | undefined; disableRedirect?: boolean | undefined; }, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; idToken?: { token: string; accessToken?: string | undefined; @@ -6397,6 +6529,7 @@ declare const auth: { nonce?: string | undefined; } | undefined; callbackURL?: string | undefined; + newUserCallbackURL?: string | undefined; errorCallbackURL?: string | undefined; disableRedirect?: boolean | undefined; }>; @@ -6472,8 +6605,9 @@ declare const auth: { }>>; body: zod.ZodObject<{ callbackURL: zod.ZodOptional; + newUserCallbackURL: zod.ZodOptional; errorCallbackURL: zod.ZodOptional; - provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab")[]]>; + provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit")[]]>; disableRedirect: zod.ZodOptional; idToken: zod.ZodOptional>; }, "strip", zod.ZodTypeAny, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; idToken?: { token: string; accessToken?: string | undefined; @@ -6504,10 +6638,11 @@ declare const auth: { nonce?: string | undefined; } | undefined; callbackURL?: string | undefined; + newUserCallbackURL?: string | undefined; errorCallbackURL?: string | undefined; disableRedirect?: boolean | undefined; }, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; idToken?: { token: string; accessToken?: string | undefined; @@ -6516,6 +6651,7 @@ declare const auth: { nonce?: string | undefined; } | undefined; callbackURL?: string | undefined; + newUserCallbackURL?: string | undefined; errorCallbackURL?: string | undefined; disableRedirect?: boolean | undefined; }>; @@ -6684,11 +6820,25 @@ declare const auth: { schema: { type: "object"; properties: { - user: { + id: { type: string; + description: string; }; - session: { + email: { + type: string; + description: string; + }; + name: { + type: string; + description: string; + }; + image: { + type: string; + description: string; + }; + emailVerified: { type: string; + description: string; }; }; }; @@ -6701,38 +6851,11 @@ declare const auth: { }>]>(...ctx: C): Promise; path: "/sign-up/email"; options: { @@ -6802,11 +6925,25 @@ declare const auth: { schema: { type: "object"; properties: { - user: { + id: { + type: string; + description: string; + }; + email: { type: string; + description: string; }; - session: { + name: { type: string; + description: string; + }; + image: { + type: string; + description: string; + }; + emailVerified: { + type: string; + description: string; }; }; }; @@ -6850,9 +6987,6 @@ declare const auth: { schema: { type: "object"; properties: { - session: { - type: string; - }; user: { type: string; }; @@ -6876,22 +7010,12 @@ declare const auth: { }] ? Response : { user: { id: string; - createdAt: Date; - updatedAt: Date; email: string; - emailVerified: boolean; name: string; - image?: string | null | undefined; - }; - session: { - id: string; - userId: string; + image: string | null | undefined; + emailVerified: boolean; createdAt: Date; updatedAt: Date; - expiresAt: Date; - token: string; - ipAddress?: string | null | undefined; - userAgent?: string | null | undefined; }; redirect: boolean; url: string | undefined; @@ -6926,9 +7050,6 @@ declare const auth: { schema: { type: "object"; properties: { - session: { - type: string; - }; user: { type: string; }; @@ -7806,7 +7927,13 @@ declare const auth: { }>]>(...ctx: C): Promise; path: "/update-user"; options: { @@ -8540,12 +8667,12 @@ declare const auth: { }>>; body: zod.ZodObject<{ callbackURL: zod.ZodOptional; - provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab")[]]>; + provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit")[]]>; }, "strip", zod.ZodTypeAny, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; callbackURL?: string | undefined; }, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; callbackURL?: string | undefined; }>; use: better_call.Endpoint>; body: zod.ZodObject<{ callbackURL: zod.ZodOptional; - provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab")[]]>; + provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit")[]]>; }, "strip", zod.ZodTypeAny, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; callbackURL?: string | undefined; }, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; callbackURL?: string | undefined; }>; use: better_call.Endpoint; }; plugins: ({ id: "custom-session"; @@ -8915,6 +9047,16 @@ declare const auth: { metadata: { CUSTOM_SESSION: boolean; }; + query: zod.ZodOptional]>>; + disableRefresh: zod.ZodOptional; + }, "strip", zod.ZodTypeAny, { + disableCookieCache?: boolean | undefined; + disableRefresh?: boolean | undefined; + }, { + disableCookieCache?: string | boolean | undefined; + disableRefresh?: boolean | undefined; + }>>; }> | undefined)?]>(...ctx: C): Promise]>>; + disableRefresh: zod.ZodOptional; + }, "strip", zod.ZodTypeAny, { + disableCookieCache?: boolean | undefined; + disableRefresh?: boolean | undefined; + }, { + disableCookieCache?: string | boolean | undefined; + disableRefresh?: boolean | undefined; + }>>; }; method: better_call.Method | better_call.Method[]; headers: Headers; @@ -9041,6 +9193,28 @@ declare const auth: { }; }; }; + $ErrorCodes: { + USER_NOT_FOUND: string; + FAILED_TO_CREATE_USER: string; + FAILED_TO_CREATE_SESSION: string; + FAILED_TO_UPDATE_USER: string; + FAILED_TO_GET_SESSION: string; + INVALID_PASSWORD: string; + INVALID_EMAIL: string; + INVALID_EMAIL_OR_PASSWORD: string; + SOCIAL_ACCOUNT_ALREADY_LINKED: string; + PROVIDER_NOT_FOUND: string; + INVALID_TOKEN: string; + ID_TOKEN_NOT_SUPPORTED: string; + FAILED_TO_GET_USER_INFO: string; + USER_EMAIL_NOT_FOUND: string; + EMAIL_NOT_VERIFIED: string; + PASSWORD_TOO_SHORT: string; + PASSWORD_TOO_LONG: string; + USER_ALREADY_EXISTS: string; + EMAIL_CAN_NOT_BE_UPDATED: string; + CREDENTIAL_ACCOUNT_NOT_FOUND: string; + }; }; type AuthSession = Awaited>; @@ -9089,7 +9263,7 @@ declare const _routes: hono_hono_base.HonoBase Date: Mon, 16 Dec 2024 19:38:29 +0800 Subject: [PATCH 15/39] chore: use correct tailwind config for apps server for lint --- eslint.config.mjs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index b805be8cd8..8edf22f8ad 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,4 +1,6 @@ // @ts-check +import path from "node:path" + import { defineConfig, GLOB_TS_SRC } from "eslint-config-hyoban" import checkI18nJson from "./plugins/eslint/eslint-check-i18n-json.js" @@ -64,6 +66,14 @@ export default defineConfig( ], }, }, + { + files: ["apps/server/**/*"], + settings: { + tailwindcss: { + config: path.join(import.meta.dirname, "apps/server/tailwind.config.ts"), + }, + }, + }, { files: ["**/*.tsx"], rules: { From efcbecbd94dc7009364f59c8945d08dc3ecc47b0 Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 16 Dec 2024 19:52:57 +0800 Subject: [PATCH 16/39] perf(external): hydrate auth providers Signed-off-by: Innei --- .../(login)/{login.tsx => login/index.tsx} | 0 .../client/pages/(login)/login/metadata.ts | 32 +++++++++++++++++++ apps/server/client/query/users.ts | 10 +++++- apps/server/helper/meta-map.ts | 9 ++++-- apps/server/src/lib/api-client.ts | 20 ++++++++---- apps/server/src/meta-handler.map.ts | 12 ++++--- 6 files changed, 67 insertions(+), 16 deletions(-) rename apps/server/client/pages/(login)/{login.tsx => login/index.tsx} (100%) create mode 100644 apps/server/client/pages/(login)/login/metadata.ts diff --git a/apps/server/client/pages/(login)/login.tsx b/apps/server/client/pages/(login)/login/index.tsx similarity index 100% rename from apps/server/client/pages/(login)/login.tsx rename to apps/server/client/pages/(login)/login/index.tsx diff --git a/apps/server/client/pages/(login)/login/metadata.ts b/apps/server/client/pages/(login)/login/metadata.ts new file mode 100644 index 0000000000..fecdef8158 --- /dev/null +++ b/apps/server/client/pages/(login)/login/metadata.ts @@ -0,0 +1,32 @@ +import type { GetHydrateData } from "@client/lib/helper" +import type { AuthProvider } from "@client/query/users" + +import { createApiFetch } from "~/lib/api-client" +import { defineMetadata } from "~/meta-handler" + +const getTypedProviders = async () => { + const apiFetch = createApiFetch() + const data = (await apiFetch("/better-auth/get-providers")) as Record + + return data +} + +const meta = defineMetadata(async () => { + const providers = await getTypedProviders() + + return [ + { + type: "title", + title: "Login", + }, + { + type: "hydrate", + data: providers, + path: "/login", + key: `betterAuth`, + }, + ] as const +}) + +export type LoginHydrateData = GetHydrateData +export default meta diff --git a/apps/server/client/query/users.ts b/apps/server/client/query/users.ts index cdebcba5d2..586a909d81 100644 --- a/apps/server/client/query/users.ts +++ b/apps/server/client/query/users.ts @@ -1,5 +1,6 @@ import { apiClient } from "@client/lib/api-fetch" import { getHydrateData } from "@client/lib/helper" +import type { LoginHydrateData } from "@client/pages/(login)/login/metadata" import { getProviders } from "@follow/shared/auth" import { capitalizeFirstLetter, isBizId, parseUrl } from "@follow/utils/utils" import { useQuery } from "@tanstack/react-query" @@ -62,9 +63,16 @@ export interface AuthProvider { color: string icon: string } +const getTypedProviders = async () => { + const providers = await getProviders() + return providers.data as Record +} export const useAuthProviders = () => { return useQuery({ queryKey: ["providers"], - queryFn: async () => (await getProviders()).data as Record, + queryFn: async () => getTypedProviders(), + initialData: getHydrateData(`betterAuth`) as LoginHydrateData, }) } + +export { getTypedProviders as getAuthProviders } diff --git a/apps/server/helper/meta-map.ts b/apps/server/helper/meta-map.ts index 487934c2b4..ec6b64b87b 100644 --- a/apps/server/helper/meta-map.ts +++ b/apps/server/helper/meta-map.ts @@ -9,7 +9,7 @@ import fg from "fast-glob" const __dirname = dirname(fileURLToPath(import.meta.url)) async function generateMetaMap() { - const files = await fg.glob("./client/pages/(main)/**/metadata.ts", { + const files = await fg.glob("./client/pages/**/*/metadata.ts", { cwd: path.resolve(__dirname, ".."), }) @@ -18,10 +18,13 @@ async function generateMetaMap() { files.forEach((file, index) => { const routePath = file - .replace("client/pages/(main)", "") + .replace("client/pages/", "") .replace("/metadata.ts", "") .replaceAll(/\[([^\]]+)\]/g, ":$1") + .replaceAll(/\(([^)]+)\)\//g, "") + .replace(/^\./, "") + const importName = `i${index}` imports.push(`import ${importName} from "../${file.replace(".ts", "")}"`) routes[routePath] = importName @@ -47,7 +50,7 @@ ${Object.entries(routes) } async function watch() { - const watchPath = path.resolve(__dirname, "..", "./client/pages/(main)") + const watchPath = path.resolve(__dirname, "..", "./client/pages") console.info("Watching metadata files...") await generateMetaMap() diff --git a/apps/server/src/lib/api-client.ts b/apps/server/src/lib/api-client.ts index afed762e0d..c71b2d75c6 100644 --- a/apps/server/src/lib/api-client.ts +++ b/apps/server/src/lib/api-client.ts @@ -9,13 +9,9 @@ import { ofetch } from "ofetch" import PKG from "../../../../package.json" import { isDev } from "./env" -export const createApiClient = () => { - const authSessionToken = getTokenFromCookie(requestContext.get("req")?.headers.cookie || "") - +const getBaseURL = () => { const req = requestContext.get("req")! - const { host } = req.headers - let baseURL = env.VITE_EXTERNAL_API_URL || env.VITE_API_URL if (env.VITE_EXTERNAL_API_URL?.startsWith("/")) { @@ -29,11 +25,14 @@ export const createApiClient = () => { if (upstreamEnv === "prod" && env.VITE_EXTERNAL_PROD_API_URL) { baseURL = env.VITE_EXTERNAL_PROD_API_URL } + return baseURL +} +export const createApiFetch = () => { + const baseURL = getBaseURL() - const apiFetch = ofetch.create({ + return ofetch.create({ credentials: "include", retry: false, - onRequest(context) { if (isDev) console.info(`request: ${context.request}`) @@ -44,7 +43,14 @@ export const createApiClient = () => { return } }, + baseURL, }) +} +export const createApiClient = () => { + const authSessionToken = getTokenFromCookie(requestContext.get("req")?.headers.cookie || "") + + const baseURL = getBaseURL() + const apiFetch = createApiFetch() const apiClient = hc(baseURL, { fetch: async (input: any, options = {}) => apiFetch(input.toString(), options), diff --git a/apps/server/src/meta-handler.map.ts b/apps/server/src/meta-handler.map.ts index 52b725b99e..5ba72ad65b 100644 --- a/apps/server/src/meta-handler.map.ts +++ b/apps/server/src/meta-handler.map.ts @@ -1,10 +1,12 @@ // This file is generated by `pnpm run meta` -import i1 from "../client/pages/(main)/share/feeds/[id]/metadata" -import i2 from "../client/pages/(main)/share/lists/[id]/metadata" -import i0 from "../client/pages/(main)/share/users/[id]/metadata" +import i0 from ".././client/pages/(login)/login/metadata" +import i3 from ".././client/pages/(main)/share/feeds/[id]/metadata" +import i2 from ".././client/pages/(main)/share/lists/[id]/metadata" +import i1 from ".././client/pages/(main)/share/users/[id]/metadata" export default { - "/share/users/:id": i0, - "/share/feeds/:id": i1, + "/login": i0, + "/share/users/:id": i1, "/share/lists/:id": i2, + "/share/feeds/:id": i3, } From 5ac203f6ee4a4997b53db0562e2426fc83502d16 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:06:39 +0800 Subject: [PATCH 17/39] chore: rewrite for vercel --- vercel.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vercel.json b/vercel.json index 6db0f8a726..1824d6ae4b 100644 --- a/vercel.json +++ b/vercel.json @@ -20,6 +20,18 @@ "source": "/login", "destination": "https://follow-external-ssr.vercel.app/login" }, + { + "source": "/register", + "destination": "https://follow-external-ssr.vercel.app/register" + }, + { + "source": "/forget-password", + "destination": "https://follow-external-ssr.vercel.app/forget-password" + }, + { + "source": "/reset-password", + "destination": "https://follow-external-ssr.vercel.app/reset-password" + }, { "source": "/external-dist/:path*", "destination": "https://follow-external-ssr.vercel.app/external-dist/:path*" From 8ad40c004ef2bf8924e2362729f42be83c09d102 Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 16 Dec 2024 20:15:00 +0800 Subject: [PATCH 18/39] feat(settings): clean web app service worker cache Signed-off-by: Innei --- .../modules/settings/tabs/data-control.tsx | 33 +++++++++++++++++-- apps/renderer/src/workers/sw/index.ts | 16 +++++++-- apps/server/helper/meta-map.ts | 3 +- locales/settings/en.json | 1 + 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/apps/renderer/src/modules/settings/tabs/data-control.tsx b/apps/renderer/src/modules/settings/tabs/data-control.tsx index 24aee6ba30..dcabec5d52 100644 --- a/apps/renderer/src/modules/settings/tabs/data-control.tsx +++ b/apps/renderer/src/modules/settings/tabs/data-control.tsx @@ -111,7 +111,7 @@ export const SettingDataControl = () => { value: t("general.cache"), }, isElectronBuild && AppCacheLimit, - isElectronBuild && CleanCache, + isElectronBuild ? CleanElectronCache : CleanCacheStorage, isElectronBuild && { type: "title", value: t("general.data_file.label"), @@ -129,7 +129,36 @@ export const SettingDataControl = () => {
) } -const CleanCache = () => { + +/** + * @description clean web app service worker cache + */ +const CleanCacheStorage = () => { + const { t } = useTranslation("settings") + + return ( + + {t("data_control.clean_cache.button")} + } + action={async () => { + const keys = await caches.keys() + return Promise.all( + keys.map((key) => { + if (key.startsWith("workbox-precache-")) return null + return caches.delete(key) + }), + ) + }} + buttonText={t("data_control.clean_cache.button")} + /> + {t("data_control.clean_cache.description_web")} + + ) +} + +const CleanElectronCache = () => { const { t } = useTranslation("settings") return ( diff --git a/apps/renderer/src/workers/sw/index.ts b/apps/renderer/src/workers/sw/index.ts index d0003b6053..9e0503975d 100644 --- a/apps/renderer/src/workers/sw/index.ts +++ b/apps/renderer/src/workers/sw/index.ts @@ -1,5 +1,7 @@ /// -import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching" +import { CacheableResponsePlugin } from "workbox-cacheable-response" +import { ExpirationPlugin } from "workbox-expiration" +import { precacheAndRoute } from "workbox-precaching" import { registerRoute } from "workbox-routing" import { CacheFirst } from "workbox-strategies" @@ -19,9 +21,17 @@ registerRoute( ({ request }) => request.destination === "image", new CacheFirst({ cacheName: "image-assets", + plugins: [ + new CacheableResponsePlugin({ + statuses: [0, 200], + }), + new ExpirationPlugin({ + maxEntries: 300, + maxAgeSeconds: 30 * 24 * 60 * 60, + purgeOnQuotaError: true, + }), + ], }), ) precacheAndRoute(precacheManifest) - -cleanupOutdatedCaches() diff --git a/apps/server/helper/meta-map.ts b/apps/server/helper/meta-map.ts index ec6b64b87b..0b3f957e66 100644 --- a/apps/server/helper/meta-map.ts +++ b/apps/server/helper/meta-map.ts @@ -21,8 +21,7 @@ async function generateMetaMap() { .replace("client/pages/", "") .replace("/metadata.ts", "") .replaceAll(/\[([^\]]+)\]/g, ":$1") - - .replaceAll(/\(([^)]+)\)\//g, "") + .replaceAll(/\([^)]+\)\//g, "") .replace(/^\./, "") const importName = `i${index}` diff --git a/locales/settings/en.json b/locales/settings/en.json index 1c770ab962..b10f4e61a7 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -102,6 +102,7 @@ "data_control.app_cache_limit.label": "App Cache Limit", "data_control.clean_cache.button": "Clean Cache", "data_control.clean_cache.description": "Clean the app cache to free up space.", + "data_control.clean_cache.description_web": "Clean the web app service worker cache to free up space.", "feeds.claimTips": "To claim your feeds and receive tips, right-click on the feed in your subscription list and select Claim.", "feeds.noFeeds": "No claimed feeds", "feeds.tableHeaders.name": "Name", From d4905fd0082b41de3f76afa597d67030761a4ae8 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Mon, 16 Dec 2024 22:02:55 +0800 Subject: [PATCH 19/39] feat: email verification --- .../modules/profile/profile-setting-form.tsx | 33 +++++++++++++++++-- locales/settings/en.json | 4 +++ packages/components/src/ui/button/index.tsx | 9 ++--- packages/shared/src/auth.ts | 1 + packages/shared/src/hono.ts | 8 +++++ 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/apps/renderer/src/modules/profile/profile-setting-form.tsx b/apps/renderer/src/modules/profile/profile-setting-form.tsx index 9ee4a65e97..33da144cc6 100644 --- a/apps/renderer/src/modules/profile/profile-setting-form.tsx +++ b/apps/renderer/src/modules/profile/profile-setting-form.tsx @@ -11,7 +11,7 @@ import { } from "@follow/components/ui/form/index.jsx" import { Input } from "@follow/components/ui/input/index.js" import { Label } from "@follow/components/ui/label/index.js" -import { updateUser } from "@follow/shared/auth" +import { sendVerificationEmail, updateUser } from "@follow/shared/auth" import { cn } from "@follow/utils/utils" import { zodResolver } from "@hookform/resolvers/zod" import { useMutation } from "@tanstack/react-query" @@ -68,6 +68,18 @@ export const ProfileSettingForm = ({ }, }) + const verifyEmailMutation = useMutation({ + mutationFn: async () => { + if (!user?.email) return + return sendVerificationEmail({ + email: user.email, + }) + }, + onSuccess: () => { + toast.success(t("profile.email.verification_sent")) + }, + }) + function onSubmit(values: z.infer) { updateMutation.mutate(values) } @@ -77,7 +89,24 @@ export const ProfileSettingForm = ({
-

{user?.email}

+

+ {user?.email} + + {user?.emailVerified ? t("profile.email.verified") : t("profile.email.unverified")} + +

+ {!user?.emailVerified && ( + + )}
->(({ className, buttonClassName, isLoading, variant, status, ...props }, ref) => { +>(({ className, buttonClassName, disabled, isLoading, variant, status, ...props }, ref) => { const handleClick: React.MouseEventHandler = React.useCallback( (e) => { - if (isLoading || props.disabled) { + if (isLoading || disabled) { e.preventDefault() return } props.onClick?.(e) }, - [isLoading, props], + [disabled, isLoading, props], ) return ( diff --git a/packages/shared/src/auth.ts b/packages/shared/src/auth.ts index 089292a818..ac8ddfabfb 100644 --- a/packages/shared/src/auth.ts +++ b/packages/shared/src/auth.ts @@ -40,6 +40,7 @@ export const { linkSocial, listAccounts, resetPassword, + sendVerificationEmail, signIn, signOut, signUp, diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts index 738643bb77..dee77e0db6 100644 --- a/packages/shared/src/hono.ts +++ b/packages/shared/src/hono.ts @@ -9038,6 +9038,14 @@ declare const auth: { token: string; }): Promise; }; + emailVerification: { + sendOnSignUp: true; + sendVerificationEmail({ user, url }: { + user: better_auth.User; + url: string; + token: string; + }): Promise; + }; plugins: ({ id: "custom-session"; endpoints: { From edc9e3008ef135e25bf7db6e16356d48ac2f8bd9 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Mon, 16 Dec 2024 22:11:29 +0800 Subject: [PATCH 20/39] chore: update hono api type --- packages/shared/src/hono.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts index dee77e0db6..327785c918 100644 --- a/packages/shared/src/hono.ts +++ b/packages/shared/src/hono.ts @@ -9039,7 +9039,6 @@ declare const auth: { }): Promise; }; emailVerification: { - sendOnSignUp: true; sendVerificationEmail({ user, url }: { user: better_auth.User; url: string; From d0bf0fb657cf7dfd7c93f6ab51c7c963878944df Mon Sep 17 00:00:00 2001 From: Innei Date: Tue, 17 Dec 2024 00:32:28 +0800 Subject: [PATCH 21/39] refactor: replace useGetCommand with getCommand for improved command retrieval - Updated the command retrieval method in various hooks and components to use the new getCommand function instead of the deprecated useGetCommand. - Refactored related tests to ensure type correctness with the new command retrieval approach. - Enhanced the PictureWaterFallItem component by optimizing mouse event handling with useCallback for better performance. - Improved animation handling in the PictureWaterFallItem component for smoother transitions. This change streamlines command access and enhances component performance. Signed-off-by: Innei --- .../src/hooks/biz/useEntryActions.tsx | 6 ++-- .../command/hooks/use-command.test-d.ts | 8 ++--- .../src/modules/command/hooks/use-command.ts | 22 ++++++------ .../command/hooks/use-register-hotkey.ts | 3 +- .../entry-column/Items/picture-item.tsx | 34 +++++++++++++------ 5 files changed, 42 insertions(+), 31 deletions(-) diff --git a/apps/renderer/src/hooks/biz/useEntryActions.tsx b/apps/renderer/src/hooks/biz/useEntryActions.tsx index 64b095f488..ff791fc5af 100644 --- a/apps/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/renderer/src/hooks/biz/useEntryActions.tsx @@ -16,7 +16,7 @@ import { whoami } from "~/atoms/user" import { shortcuts } from "~/constants/shortcuts" import { tipcClient } from "~/lib/client" import { COMMAND_ID } from "~/modules/command/commands/id" -import { useGetCommand, useRunCommandFn } from "~/modules/command/hooks/use-command" +import { getCommand, useRunCommandFn } from "~/modules/command/hooks/use-command" import type { FollowCommandId } from "~/modules/command/types" import { useEntry } from "~/store/entry" import { useFeedById } from "~/store/feed" @@ -87,7 +87,6 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee const isShowAISummary = useShowAISummary() const isShowAITranslation = useShowAITranslation() - const getCmd = useGetCommand() const runCmdFn = useRunCommandFn() const actionConfigs = useMemo(() => { if (!entryId) return [] @@ -202,7 +201,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee ] .filter((config) => !config.hide) .map((config) => { - const cmd = getCmd(config.id) + const cmd = getCommand(config.id) if (!cmd) return null return { ...config, @@ -216,7 +215,6 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee entryId, feed?.id, feed?.ownerUserId, - getCmd, inList, isInbox, isShowAISummary, diff --git a/apps/renderer/src/modules/command/hooks/use-command.test-d.ts b/apps/renderer/src/modules/command/hooks/use-command.test-d.ts index 316f3d3f6c..04e6eef5a7 100644 --- a/apps/renderer/src/modules/command/hooks/use-command.test-d.ts +++ b/apps/renderer/src/modules/command/hooks/use-command.test-d.ts @@ -2,12 +2,10 @@ import { assertType, expectTypeOf, test } from "vitest" import { COMMAND_ID } from "../commands/id" import type { TipCommand } from "../commands/types" -import { useCommand, useGetCommand, useRunCommandFn } from "./use-command" +import { getCommand, useCommand, useRunCommandFn } from "./use-command" -test("useGetCommand types work properly", () => { - const getCmd = useGetCommand() - expectTypeOf(getCmd).toBeFunction() - expectTypeOf(getCmd(COMMAND_ID.entry.tip)).toMatchTypeOf() +test("getCommand types work properly", () => { + expectTypeOf(getCommand(COMMAND_ID.entry.tip)).toMatchTypeOf() // @ts-expect-error - get an unknown command should throw an error assertType(getCmd("unknown command")) diff --git a/apps/renderer/src/modules/command/hooks/use-command.ts b/apps/renderer/src/modules/command/hooks/use-command.ts index 0300e13198..ed1e9fa91e 100644 --- a/apps/renderer/src/modules/command/hooks/use-command.ts +++ b/apps/renderer/src/modules/command/hooks/use-command.ts @@ -1,30 +1,32 @@ +import { jotaiStore } from "@follow/utils/jotai" import { useAtomValue } from "jotai" -import { useCallback } from "react" +import { selectAtom } from "jotai/utils" +import { useCallback, useMemo } from "react" import { CommandRegistry } from "../registry/registry" import type { FollowCommandId, FollowCommandMap } from "../types" -export function useGetCommand() { - const commands = useAtomValue(CommandRegistry.atom) as FollowCommandMap - return (id: T): FollowCommandMap[T] | null => - id in commands ? commands[id] : null +export const getCommand = (id: T) => { + const commands = jotaiStore.get(CommandRegistry.atom) as FollowCommandMap + return id in commands ? commands[id] : null } export function useCommand(id: T): FollowCommandMap[T] | null { - const getCmd = useGetCommand() - return getCmd(id) + const commands = useAtomValue( + useMemo(() => selectAtom(CommandRegistry.atom, (commands) => commands[id]), [id]), + ) + return commands as FollowCommandMap[T] | null } const noop = () => {} export function useRunCommandFn() { - const getCmd = useGetCommand() return useCallback( (id: T, args: Parameters) => { - const cmd = getCmd(id) + const cmd = getCommand(id) if (!cmd) return noop // @ts-expect-error - The type should be discriminated return () => cmd.run(...args) }, - [getCmd], + [], ) } diff --git a/apps/renderer/src/modules/command/hooks/use-register-hotkey.ts b/apps/renderer/src/modules/command/hooks/use-register-hotkey.ts index 0e1fa17507..5c4f1ff503 100644 --- a/apps/renderer/src/modules/command/hooks/use-register-hotkey.ts +++ b/apps/renderer/src/modules/command/hooks/use-register-hotkey.ts @@ -1,7 +1,7 @@ import { useHotkeys } from "react-hotkeys-hook" import type { FollowCommand, FollowCommandId } from "../types" -import { useGetCommand } from "./use-command" +import { getCommand } from "./use-command" interface RegisterHotkeyOptions { shortcut: string @@ -17,7 +17,6 @@ export const useCommandHotkey = ({ when, args, }: RegisterHotkeyOptions) => { - const getCommand = useGetCommand() useHotkeys( shortcut, () => { diff --git a/apps/renderer/src/modules/entry-column/Items/picture-item.tsx b/apps/renderer/src/modules/entry-column/Items/picture-item.tsx index 57d84546ae..903d135006 100644 --- a/apps/renderer/src/modules/entry-column/Items/picture-item.tsx +++ b/apps/renderer/src/modules/entry-column/Items/picture-item.tsx @@ -9,7 +9,7 @@ import { FeedViewType } from "@follow/constants" import { cn } from "@follow/utils/utils" import { AnimatePresence, m } from "framer-motion" import type { PropsWithChildren } from "react" -import { memo, useContext, useEffect, useMemo, useState } from "react" +import { memo, useCallback, useContext, useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { SwipeMedia } from "~/components/ui/media/SwipeMedia" @@ -64,6 +64,9 @@ const proxySize = { width: 600, height: 0, } + +const footerAnimate = { opacity: 1, y: 0 } +const footerExit = { opacity: 0, y: 10 } export const PictureWaterFallItem = memo(function PictureWaterFallItem({ entryId, entryPreview, @@ -93,19 +96,26 @@ export const PictureWaterFallItem = memo(function PictureWaterFallItem({ const [isMouseEnter, setIsMouseEnter] = useState(false) - if (!entry) return null + const media = useMemo(() => filterSmallMedia(entry?.entries.media || []), [entry?.entries.media]) - const media = filterSmallMedia(entry.entries.media) + const handleMouseEnter = useCallback(() => { + setIsMouseEnter(true) + }, []) + const handleMouseLeave = useCallback(() => { + setIsMouseEnter(false) + }, []) + if (media?.length === 0) return null + if (!entry) return null return (
setIsMouseEnter(true)} - onMouseLeave={() => setIsMouseEnter(false)} - onTouchMove={() => setIsMouseEnter(true)} - onTouchEnd={() => setIsMouseEnter(false)} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} + onTouchMove={handleMouseEnter} + onTouchEnd={handleMouseLeave} className={className} > {isMouseEnter && ( @@ -200,6 +210,10 @@ const MasonryItemFixedDimensionWrapper = ( ) } +MasonryItemFixedDimensionWrapper.whyDidYouRender = { + logOnDifferentValues: true, +} + export const PictureItemSkeleton = (
From ffca1301b4f89a27b995b79b6e43d6b7f581e5c4 Mon Sep 17 00:00:00 2001 From: Innei Date: Tue, 17 Dec 2024 00:45:23 +0800 Subject: [PATCH 22/39] fix: commands in action responsive Signed-off-by: Innei --- apps/renderer/src/hooks/biz/useEntryActions.tsx | 7 ++++++- apps/renderer/src/modules/command/hooks/use-command.ts | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/renderer/src/hooks/biz/useEntryActions.tsx b/apps/renderer/src/hooks/biz/useEntryActions.tsx index ff791fc5af..709e07296f 100644 --- a/apps/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/renderer/src/hooks/biz/useEntryActions.tsx @@ -16,7 +16,7 @@ import { whoami } from "~/atoms/user" import { shortcuts } from "~/constants/shortcuts" import { tipcClient } from "~/lib/client" import { COMMAND_ID } from "~/modules/command/commands/id" -import { getCommand, useRunCommandFn } from "~/modules/command/hooks/use-command" +import { getCommand, useCommands, useRunCommandFn } from "~/modules/command/hooks/use-command" import type { FollowCommandId } from "~/modules/command/types" import { useEntry } from "~/store/entry" import { useFeedById } from "~/store/feed" @@ -87,8 +87,11 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee const isShowAISummary = useShowAISummary() const isShowAITranslation = useShowAITranslation() + const commands = useCommands() + const runCmdFn = useRunCommandFn() const actionConfigs = useMemo(() => { + void commands if (!entryId) return [] return [ { @@ -222,6 +225,8 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee isShowSourceContent, runCmdFn, view, + + commands, ]) return actionConfigs diff --git a/apps/renderer/src/modules/command/hooks/use-command.ts b/apps/renderer/src/modules/command/hooks/use-command.ts index ed1e9fa91e..396dd341e1 100644 --- a/apps/renderer/src/modules/command/hooks/use-command.ts +++ b/apps/renderer/src/modules/command/hooks/use-command.ts @@ -11,6 +11,7 @@ export const getCommand = (id: T) => { return id in commands ? commands[id] : null } +export const useCommands = () => useAtomValue(CommandRegistry.atom) export function useCommand(id: T): FollowCommandMap[T] | null { const commands = useAtomValue( useMemo(() => selectAtom(CommandRegistry.atom, (commands) => commands[id]), [id]), From 0dcfde5ea40d688eaf8acc9f15f7a298421a12f0 Mon Sep 17 00:00:00 2001 From: Eric Zhu Date: Tue, 17 Dec 2024 00:51:34 +0800 Subject: [PATCH 23/39] fix: update zh-CN translations and remove unused setting (#2132) * chore(translations): update zh-CN locale files for accuracy * chore: auto-fix linting and formatting issues * update * update * chore: auto-fix linting and formatting issues * update * update --------- Co-authored-by: ericyzhu --- .../client/components/layout/header/index.tsx | 2 +- locales/app/zh-CN.json | 11 +++++--- locales/external/zh-CN.json | 28 ++++++++++++++++++- locales/settings/en.json | 1 - locales/settings/zh-CN.json | 12 ++++++++ locales/settings/zh-TW.json | 1 - 6 files changed, 47 insertions(+), 8 deletions(-) diff --git a/apps/server/client/components/layout/header/index.tsx b/apps/server/client/components/layout/header/index.tsx index da76968c59..4e16942b1f 100644 --- a/apps/server/client/components/layout/header/index.tsx +++ b/apps/server/client/components/layout/header/index.tsx @@ -96,7 +96,7 @@ export const Header = () => { onClick={handleToApp} > Open app - + L diff --git a/locales/app/zh-CN.json b/locales/app/zh-CN.json index 5008f97dee..a95ec12621 100644 --- a/locales/app/zh-CN.json +++ b/locales/app/zh-CN.json @@ -74,8 +74,8 @@ "discover.import.result": "成功导入了 个订阅源, 个已订阅, 个导入失败。", "discover.import.successfulItems": "成功项目", "discover.inbox.actions": "操作", - "discover.inbox.description": "你可以通过电子邮件和 Webhook 在收件箱接收信息。", - "discover.inbox.email": "电子邮件", + "discover.inbox.description": "你可以通过邮件和 Webhook 在收件箱接收信息。", + "discover.inbox.email": "邮件地址", "discover.inbox.handle": "唯一标识", "discover.inbox.secret": "密钥", "discover.inbox.title": "标题", @@ -88,7 +88,7 @@ "discover.inbox_destroy_confirm": "确认删除收件箱?", "discover.inbox_destroy_error": "删除收件箱失败。", "discover.inbox_destroy_success": "收件箱删除成功。", - "discover.inbox_destroy_warning": "警告:一旦删除,电子邮件将不再可用,所有内容将被永久删除且无法恢复。", + "discover.inbox_destroy_warning": "警告:一旦删除,邮件地址将不再可用,所有内容将被永久删除且无法恢复。", "discover.inbox_handle": "唯一标识", "discover.inbox_title": "标题", "discover.inbox_update": "更新", @@ -219,6 +219,7 @@ "feed_item.claimed_by_unknown": "未知所有者", "feed_item.claimed_by_you": "订阅源由你提供", "feed_item.claimed_feed": "已认证源", + "feed_item.claimed_list": "已认证列表", "feed_item.error_since": "源失效:", "feed_item.not_publicly_visible": "在个人页面上隐藏", "feed_view_type.articles": "文章", @@ -307,7 +308,7 @@ "sidebar.category_remove_dialog.title": "删除分类", "sidebar.feed_actions.claim": "认证", "sidebar.feed_actions.claim_feed": "认证订阅", - "sidebar.feed_actions.copy_email_address": "复制电子邮件地址", + "sidebar.feed_actions.copy_email_address": "复制邮件地址", "sidebar.feed_actions.copy_feed_id": "复制 ID", "sidebar.feed_actions.copy_feed_url": "复制链接", "sidebar.feed_actions.copy_list_id": "复制列表 ID", @@ -372,6 +373,7 @@ "trending.user": "热门用户", "user_button.account": "账号", "user_button.achievement": "成就", + "user_button.actions": "自动化", "user_button.download_desktop_app": "下载客户端", "user_button.log_out": "登出", "user_button.power": "Power", @@ -384,6 +386,7 @@ "user_profile.share": "分享", "user_profile.toggle_item_style": "切换列表样式", "words.achievement": "成就", + "words.actions": "自动化", "words.add": "添加", "words.boost": "助力", "words.browser": "浏览器", diff --git a/locales/external/zh-CN.json b/locales/external/zh-CN.json index 7dacbff91c..5dd07ef841 100644 --- a/locales/external/zh-CN.json +++ b/locales/external/zh-CN.json @@ -9,6 +9,8 @@ "feed.follower_other": "关注者", "feed.followsAndFeeds": "在 {{appName}} 上有 {{subscriptionCount}} 个{{subscriptionNoun}}和 {{feedsCount}} 个{{feedsNoun}}", "feed.followsAndReads": "在 {{appName}} 上有 {{subscriptionCount}} 个{{subscriptionNoun}},{{readCount}} 次近期{{readNoun}}", + "feed.madeby": "创建者", + "feed.preview": "预览", "feed.read_one": "阅读", "feed.read_other": "阅读", "feed.view_feed_url": "查看链接", @@ -16,6 +18,7 @@ "feed_item.claimed_by_unknown": "未知所有者", "feed_item.claimed_by_you": "订阅源由你提供", "feed_item.claimed_feed": "已认证源", + "feed_item.claimed_list": "已认证列表", "feed_item.error_since": "源失效:", "feed_item.not_publicly_visible": "在个人资料页面上隐藏", "header.app": "应用", @@ -31,14 +34,37 @@ "invitation.getCodeMessage": "通过以下方式获取:", "invitation.title": "邀请码", "login.backToWebApp": "返回网页版", + "login.confirm_password.label": "确认密码", "login.continueWith": "使用 {{provider}} 登入", + "login.email": "邮件地址", + "login.forget_password.description": "请输入与你的帐户关联的邮件地址,我们将向你发送一封关于如何重置密码的邮件。", + "login.forget_password.label": "忘记密码", + "login.forget_password.note": "忘记了密码?", + "login.forget_password.success": "邮件已成功发送。", "login.logInTo": "登入至 ", + "login.logInWithEmail": "使用邮件地址登入", + "login.new_password.label": "新密码", "login.openApp": "打开应用", + "login.or": "或", + "login.password": "密码", "login.redirecting": "正在重定向", + "login.register": "创建账户", + "login.reset_password.description": "请输入新密码并确认以重置你的密码。", + "login.reset_password.label": "重置密码", + "login.reset_password.success": "密码已成功重置。", "login.signOut": "登出", + "login.signUp": "使用邮件地址注册", + "login.submit": "提交", "login.welcomeTo": "欢迎来到 ", "redirect.continueInBrowser": "在浏览器中继续", "redirect.instruction": "现在可以打开 {{app_name}} 并关闭此页面。", "redirect.openApp": "打开 {{app_name}}", - "redirect.successMessage": "已成功连接到 {{app_name}} 账户。" + "redirect.successMessage": "已成功连接到 {{app_name}} 账户。", + "register.confirm_password": "确认密码", + "register.email": "邮件地址", + "register.label": "创建 {{app_name}} 账户", + "register.login": "登入", + "register.note": "已有账户?", + "register.password": "密码", + "register.submit": "创建账户" } diff --git a/locales/settings/en.json b/locales/settings/en.json index c8d957f96e..10fd23a90f 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -7,7 +7,6 @@ "about.socialMedia": "Social Media", "actions.actionName": "Action {{number}}", "actions.action_card.add": "Add", - "actions.action_card.add_action": "Add Action", "actions.action_card.all": "All", "actions.action_card.and": "And", "actions.action_card.block": "Block", diff --git a/locales/settings/zh-CN.json b/locales/settings/zh-CN.json index a6aec49370..f9e9d2459a 100644 --- a/locales/settings/zh-CN.json +++ b/locales/settings/zh-CN.json @@ -101,6 +101,7 @@ "data_control.app_cache_limit.label": "应用缓存限制", "data_control.clean_cache.button": "清理缓存", "data_control.clean_cache.description": "清理应用缓存以释放空间。", + "data_control.clean_cache.description_web": "清理 Web 应用的 Service Worker 缓存以释放空间。", "feeds.claimTips": "要认证你的订阅源并接收打赏,请在订阅列表中右键点击订阅源并选择「认证」。", "feeds.noFeeds": "没有已认证的订阅源", "feeds.tableHeaders.name": "名称", @@ -258,14 +259,25 @@ "lists.title": "标题", "lists.view": "视图", "profile.avatar.label": "头像", + "profile.change_password.label": "更改密码", + "profile.confirm_password.label": "确认密码", + "profile.current_password.label": "当前密码", + "profile.email.label": "邮件地址", + "profile.email.send_verification": "发送验证邮件", + "profile.email.unverified": "未验证", + "profile.email.verification_sent": "验证邮件已发送。", + "profile.email.verified": "已验证", "profile.handle.description": "你的唯一标识。", "profile.handle.label": "唯一标识", "profile.name.description": "你的公开显示名称。", "profile.name.label": "显示名称", + "profile.new_password.label": "新密码", + "profile.reset_password_mail_sent": "重置密码邮件已发送。", "profile.sidebar_title": "个人资料", "profile.submit": "提交", "profile.title": "个人资料设置", "profile.updateSuccess": "个人资料已更新。", + "profile.update_password_success": "密码已更新。", "titles.about": "关于", "titles.actions": "自动化", "titles.appearance": "外观", diff --git a/locales/settings/zh-TW.json b/locales/settings/zh-TW.json index b8fb3ddbc6..73b2b71b65 100644 --- a/locales/settings/zh-TW.json +++ b/locales/settings/zh-TW.json @@ -7,7 +7,6 @@ "about.socialMedia": "社群媒體", "actions.actionName": "操作 {{number}}", "actions.action_card.add": "新增", - "actions.action_card.add_action": "新增自動化指令", "actions.action_card.all": "全部", "actions.action_card.and": "和", "actions.action_card.block": "封鎖", From b3ee5727e29d809092a8f4631d3e13c8d81095b9 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:38:50 +0800 Subject: [PATCH 24/39] feat: copy button for ai summary --- .../src/modules/entry-content/index.shared.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/renderer/src/modules/entry-content/index.shared.tsx b/apps/renderer/src/modules/entry-content/index.shared.tsx index d8550f7484..879673a5e9 100644 --- a/apps/renderer/src/modules/entry-content/index.shared.tsx +++ b/apps/renderer/src/modules/entry-content/index.shared.tsx @@ -25,6 +25,7 @@ import { useEntryReadabilityContent, } from "~/atoms/readability" import { enableShowSourceContent } from "~/atoms/source-content" +import { CopyButton } from "~/components/ui/code-highlighter" import { Toc } from "~/components/ui/markdown/components/Toc" import { IconOpacityTransition } from "~/components/ux/transition/icon" import { isWebBuild } from "~/constants" @@ -342,10 +343,18 @@ export function AISummary({ entryId }: { entryId: string }) { } return ( -
-
- - {t("entry_content.ai_summary")} +
+
+
+ + {t("entry_content.ai_summary")} +
+ {summary.data && ( + + )}
{summary.isLoading ? SummaryLoadingSkeleton : summary.data} From 3ff8e244a6e06c154cd6430b94ba9511cac0aab6 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:43:46 +0800 Subject: [PATCH 25/39] chore: remove default category when following from user profile --- apps/renderer/src/modules/profile/user-profile-modal.shared.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/renderer/src/modules/profile/user-profile-modal.shared.tsx b/apps/renderer/src/modules/profile/user-profile-modal.shared.tsx index c3c8c83f83..057afcf4ae 100644 --- a/apps/renderer/src/modules/profile/user-profile-modal.shared.tsx +++ b/apps/renderer/src/modules/profile/user-profile-modal.shared.tsx @@ -133,7 +133,6 @@ const SubscriptionItem: FC<{ isList: false, defaultValues: { view: defaultView.toString(), - category: subscription.category, }, }) }) From faebe6c819cd0742470f8aebe9be5f978d3827e6 Mon Sep 17 00:00:00 2001 From: Whitewater Date: Tue, 17 Dec 2024 09:54:51 +0800 Subject: [PATCH 26/39] fix: update linkedom to version 0.18.6 (#2185) --- apps/main/package.json | 2 +- apps/server/package.json | 2 +- pnpm-lock.yaml | 29 +++++++++++++++-------------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/apps/main/package.json b/apps/main/package.json index fafe0ea6b4..2dc29b6b59 100644 --- a/apps/main/package.json +++ b/apps/main/package.json @@ -41,7 +41,7 @@ "font-list": "1.5.1", "i18next": "^24.0.5", "js-yaml": "4.1.0", - "linkedom": "^0.18.5", + "linkedom": "^0.18.6", "lowdb": "7.0.1", "msedge-tts": "1.3.4", "node-machine-id": "1.1.12", diff --git a/apps/server/package.json b/apps/server/package.json index b4d5898a2f..90603c3a40 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -22,7 +22,7 @@ "i18next": "^24.0.5", "jotai": "2.10.3", "kose-font": "1.0.0", - "linkedom": "^0.18.5", + "linkedom": "^0.18.6", "ofetch": "1.4.1", "rc-modal-sheet": "0.3.2", "react": "^18.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88ffaf05d0..4104e06493 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,7 +105,7 @@ importers: version: 0.5.0(patch_hash=43niildbdafdxi7qfcwhpkkxwa) '@pengx17/electron-forge-maker-appimage': specifier: 1.2.1 - version: 1.2.1(patch_hash=vov3v67fgv3lrfz3n24bnubw4m)(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + version: 1.2.1(patch_hash=vov3v67fgv3lrfz3n24bnubw4m)(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)) '@sentry/vite-plugin': specifier: 2.22.7 version: 2.22.7(encoding@0.1.13) @@ -341,8 +341,8 @@ importers: specifier: 4.1.0 version: 4.1.0 linkedom: - specifier: ^0.18.5 - version: 0.18.5 + specifier: ^0.18.6 + version: 0.18.6 lowdb: specifier: 7.0.1 version: 7.0.1 @@ -793,8 +793,8 @@ importers: specifier: 1.0.0 version: 1.0.0 linkedom: - specifier: ^0.18.5 - version: 0.18.5 + specifier: ^0.18.6 + version: 0.18.6 ofetch: specifier: 1.4.1 version: 1.4.1 @@ -8302,8 +8302,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - linkedom@0.18.5: - resolution: {integrity: sha512-JGLaGGtqtu+eOhYrC1wkWYTBcpVWL4AsnwAtMtgO1Q0gI0PuPJKI0zBBE+a/1BrhOE3Uw8JI/ycByAv5cLrAuQ==} + linkedom@0.18.6: + resolution: {integrity: sha512-6G8euAJ84s7MTXTli5JIOO5tzEpyoUBw2/zcqAunSurbCtC83YcgrK+VTcO8HZ/rdR3eaaZM573FP9rNo1uXIA==} lint-staged@15.2.10: resolution: {integrity: sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==} @@ -10673,6 +10673,7 @@ packages: sudo-prompt@9.2.1: resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} @@ -14565,11 +14566,11 @@ snapshots: pvtsutils: 1.3.6 tslib: 2.8.1 - '@pengx17/electron-forge-maker-appimage@1.2.1(patch_hash=vov3v67fgv3lrfz3n24bnubw4m)(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)': + '@pengx17/electron-forge-maker-appimage@1.2.1(patch_hash=vov3v67fgv3lrfz3n24bnubw4m)(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))': dependencies: '@electron-forge/maker-base': 7.6.0 '@electron-forge/shared-types': 7.6.0 - app-builder-lib: 24.13.3(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + app-builder-lib: 24.13.3(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)) transitivePeerDependencies: - bluebird - dmg-builder @@ -16388,7 +16389,7 @@ snapshots: app-builder-bin@5.0.0-alpha.10: {} - app-builder-lib@24.13.3(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8): + app-builder-lib@24.13.3(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.2.1 @@ -16422,7 +16423,7 @@ snapshots: transitivePeerDependencies: - supports-color - app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8): + app-builder-lib@25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.5.0 @@ -17784,7 +17785,7 @@ snapshots: dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): dependencies: - app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)) builder-util: 25.1.7 builder-util-runtime: 9.2.10 fs-extra: 10.1.0 @@ -17909,7 +17910,7 @@ snapshots: electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8): dependencies: - app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)) archiver: 5.3.2 builder-util: 25.1.7 fs-extra: 10.1.0 @@ -20275,7 +20276,7 @@ snapshots: lines-and-columns@1.2.4: {} - linkedom@0.18.5: + linkedom@0.18.6: dependencies: css-select: 5.1.0 cssom: 0.5.0 From ead7b3a2f1d091078f7f12c66e018ae63115fe6e Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:36:42 +0800 Subject: [PATCH 27/39] chore: fix merge conflict --- .../src/modules/profile/profile-setting-form.tsx | 12 ------------ packages/shared/src/hono.ts | 13 ------------- 2 files changed, 25 deletions(-) diff --git a/apps/renderer/src/modules/profile/profile-setting-form.tsx b/apps/renderer/src/modules/profile/profile-setting-form.tsx index e031175e32..ba991b9aa3 100644 --- a/apps/renderer/src/modules/profile/profile-setting-form.tsx +++ b/apps/renderer/src/modules/profile/profile-setting-form.tsx @@ -80,18 +80,6 @@ export const ProfileSettingForm = ({ }, }) - const verifyEmailMutation = useMutation({ - mutationFn: async () => { - if (!user?.email) return - return sendVerificationEmail({ - email: user.email, - }) - }, - onSuccess: () => { - toast.success(t("profile.email.verification_sent")) - }, - }) - function onSubmit(values: z.infer) { updateMutation.mutate(values) } diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts index 8105b4a8be..327785c918 100644 --- a/packages/shared/src/hono.ts +++ b/packages/shared/src/hono.ts @@ -9044,19 +9044,6 @@ declare const auth: { url: string; token: string; }): Promise; - sendResetPassword({ user, url }: { - user: better_auth.User; - url: string; - token: string; - }): Promise; - }; - emailVerification: { - sendOnSignUp: true; - sendVerificationEmail({ user, url }: { - user: better_auth.User; - url: string; - token: string; - }): Promise; }; plugins: ({ id: "custom-session"; From 1e1258811163d72ae36f29714eef5d50834c348b Mon Sep 17 00:00:00 2001 From: Innei Date: Tue, 17 Dec 2024 19:37:06 +0800 Subject: [PATCH 28/39] feat: copy profile email Signed-off-by: Innei --- .../modules/profile/profile-setting-form.tsx | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/apps/renderer/src/modules/profile/profile-setting-form.tsx b/apps/renderer/src/modules/profile/profile-setting-form.tsx index ba991b9aa3..24c51ae55a 100644 --- a/apps/renderer/src/modules/profile/profile-setting-form.tsx +++ b/apps/renderer/src/modules/profile/profile-setting-form.tsx @@ -21,6 +21,7 @@ import { toast } from "sonner" import { z } from "zod" import { setWhoami, useWhoami } from "~/atoms/user" +import { CopyButton } from "~/components/ui/code-highlighter" import { toastFetchError } from "~/lib/error-parser" const formSchema = z.object({ @@ -89,29 +90,15 @@ export const ProfileSettingForm = ({
-

- {user?.email} - - {user?.emailVerified ? t("profile.email.verified") : t("profile.email.unverified")} - -

- {!user?.emailVerified && ( - - )} -
-
- -

+

{user?.email} + + {user?.email && ( + + )} {user?.emailVerified ? t("profile.email.verified") : t("profile.email.unverified")} From 77fa6088fb2887806b3bcc49e71bce8b1a3aeab6 Mon Sep 17 00:00:00 2001 From: Innei Date: Tue, 17 Dec 2024 20:07:33 +0800 Subject: [PATCH 29/39] refactor: streamline entry action handling and replace ActionButton with CommandActionButton - Removed unused imports and simplified the use of commands in `useEntryActions`. - Updated `EntryItemWrapper`, `HeaderActions`, and `MoreActions` components to utilize `CommandActionButton` for better command handling. - Introduced `CommandDropdownMenuItem` for consistent command representation in dropdowns. - Enhanced readability and maintainability by reducing redundant code and improving type safety. Signed-off-by: Innei --- .../components/ui/button/command-button.tsx | 28 +++ .../src/hooks/biz/useEntryActions.tsx | 46 ++--- .../entry-column/Items/social-media-item.tsx | 9 +- .../entry-column/layouts/EntryItemWrapper.tsx | 19 +- .../entry-content/actions/header-actions.tsx | 11 +- .../entry-content/actions/more-actions.tsx | 27 ++- .../modules/entry-content/header.mobile.tsx | 48 +++-- .../src/ui/button/action-button.tsx | 169 ++++++++++++++++++ packages/components/src/ui/button/index.tsx | 168 +---------------- .../components/src/ui/button/interface.ts | 25 +++ 10 files changed, 316 insertions(+), 234 deletions(-) create mode 100644 apps/renderer/src/components/ui/button/command-button.tsx create mode 100644 packages/components/src/ui/button/action-button.tsx create mode 100644 packages/components/src/ui/button/interface.ts diff --git a/apps/renderer/src/components/ui/button/command-button.tsx b/apps/renderer/src/components/ui/button/command-button.tsx new file mode 100644 index 0000000000..e2f6a254e6 --- /dev/null +++ b/apps/renderer/src/components/ui/button/command-button.tsx @@ -0,0 +1,28 @@ +import type { ActionButtonProps } from "@follow/components/ui/button/action-button.js" +import { ActionButton } from "@follow/components/ui/button/action-button.js" +import { forwardRef } from "react" + +import { useCommand } from "~/modules/command/hooks/use-command" +import type { FollowCommandId } from "~/modules/command/types" + +export interface CommandActionButtonProps extends ActionButtonProps { + commandId: FollowCommandId + onClick: () => void +} +export const CommandActionButton = forwardRef( + (props, ref) => { + const command = useCommand(props.commandId) + if (!command) return null + const { icon, label } = command + + return ( + + ) + }, +) diff --git a/apps/renderer/src/hooks/biz/useEntryActions.tsx b/apps/renderer/src/hooks/biz/useEntryActions.tsx index 709e07296f..a6332c6669 100644 --- a/apps/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/renderer/src/hooks/biz/useEntryActions.tsx @@ -1,6 +1,5 @@ import { isMobile } from "@follow/components/hooks/useMobile.js" import { FeedViewType } from "@follow/constants" -import type { ReactNode } from "react" import { useCallback, useMemo } from "react" import { useShowAISummary } from "~/atoms/ai-summary" @@ -16,7 +15,7 @@ import { whoami } from "~/atoms/user" import { shortcuts } from "~/constants/shortcuts" import { tipcClient } from "~/lib/client" import { COMMAND_ID } from "~/modules/command/commands/id" -import { getCommand, useCommands, useRunCommandFn } from "~/modules/command/hooks/use-command" +import { useRunCommandFn } from "~/modules/command/hooks/use-command" import type { FollowCommandId } from "~/modules/command/types" import { useEntry } from "~/store/entry" import { useFeedById } from "~/store/feed" @@ -62,11 +61,10 @@ export const useEntryReadabilityToggle = ({ id, url }: { id: string; url: string export type EntryActionItem = { id: FollowCommandId - name: string - icon?: ReactNode - active?: boolean - shortcut?: string onClick: () => void + hide?: boolean + shortcut?: string + active?: boolean } export const useEntryActions = ({ entryId, view }: { entryId: string; view?: FeedViewType }) => { @@ -87,12 +85,11 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee const isShowAISummary = useShowAISummary() const isShowAITranslation = useShowAITranslation() - const commands = useCommands() - const runCmdFn = useRunCommandFn() - const actionConfigs = useMemo(() => { - void commands - if (!entryId) return [] + const hasEntry = !!entry + + const actionConfigs: EntryActionItem[] = useMemo(() => { + if (!hasEntry) return [] return [ { id: COMMAND_ID.integration.saveToEagle, @@ -192,32 +189,27 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee { id: COMMAND_ID.entry.read, onClick: runCmdFn(COMMAND_ID.entry.read, [{ entryId }]), - hide: !entry || !!entry.read || !!entry.collections || !!inList, + hide: !hasEntry || !entry.read || !!entry.collections || !!inList, shortcut: shortcuts.entry.toggleRead.key, }, { id: COMMAND_ID.entry.unread, onClick: runCmdFn(COMMAND_ID.entry.unread, [{ entryId }]), - hide: !entry || !entry.read || !!entry.collections || !!inList, + hide: !hasEntry || !entry.read || !!entry.collections || !!inList, shortcut: shortcuts.entry.toggleRead.key, }, - ] - .filter((config) => !config.hide) - .map((config) => { - const cmd = getCommand(config.id) - if (!cmd) return null - return { - ...config, - name: cmd.label.title, - icon: cmd.icon, - } - }) - .filter((i) => i !== null) + ].filter((config) => !config.hide) }, [ - entry, + entry?.collections, + entry?.entries.url, + entry?.read, + entry?.settings?.summary, + entry?.settings?.translation, + entry?.view, entryId, feed?.id, feed?.ownerUserId, + hasEntry, inList, isInbox, isShowAISummary, @@ -225,8 +217,6 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee isShowSourceContent, runCmdFn, view, - - commands, ]) return actionConfigs diff --git a/apps/renderer/src/modules/entry-column/Items/social-media-item.tsx b/apps/renderer/src/modules/entry-column/Items/social-media-item.tsx index 35665fc7b4..98db5a781c 100644 --- a/apps/renderer/src/modules/entry-column/Items/social-media-item.tsx +++ b/apps/renderer/src/modules/entry-column/Items/social-media-item.tsx @@ -1,7 +1,6 @@ import { PassviseFragment } from "@follow/components/common/Fragment.js" import { useMobile } from "@follow/components/hooks/useMobile.js" import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.js" -import { ActionButton } from "@follow/components/ui/button/index.js" import { Skeleton } from "@follow/components/ui/skeleton/index.jsx" import type { MediaModel } from "@follow/shared/hono" import { LRUCache } from "@follow/utils/lru-cache" @@ -11,6 +10,7 @@ import { useLayoutEffect, useMemo, useRef, useState } from "react" import { useTranslation } from "react-i18next" import { useGeneralSettingKey } from "~/atoms/settings/general" +import { CommandActionButton } from "~/components/ui/button/command-button" import { RelativeTime } from "~/components/ui/datetime" import { Media } from "~/components/ui/media" import { usePreviewMedia } from "~/components/ui/media/hooks" @@ -143,12 +143,7 @@ const ActionBar = ({ entryId }: { entryId: string }) => { item.id !== COMMAND_ID.entry.openInBrowser, ) .map((item) => ( - + ))}

) diff --git a/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx b/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx index 306c8feb2e..e38c42d79a 100644 --- a/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx +++ b/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx @@ -15,6 +15,7 @@ import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry" import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams" import { useContextMenu } from "~/hooks/common/useContextMenu" import { COMMAND_ID } from "~/modules/command/commands/id" +import { getCommand } from "~/modules/command/hooks/use-command" import type { FlatEntryModel } from "~/store/entry" import { entryActions } from "~/store/entry" @@ -106,12 +107,18 @@ export const EntryItemWrapper: FC< ] as string[] ).includes(item.id), ) - .map((item) => ({ - type: "text" as const, - label: item.name, - click: () => item.onClick(), - shortcut: item.shortcut, - })), + .map((item) => { + const cmd = getCommand(item.id) + + if (!cmd) return null + + return { + type: "text" as const, + label: cmd?.label.title || "aa", + click: () => item.onClick(), + shortcut: item.shortcut, + } + }), { type: "separator" as const, }, diff --git a/apps/renderer/src/modules/entry-content/actions/header-actions.tsx b/apps/renderer/src/modules/entry-content/actions/header-actions.tsx index fa91255077..df00a3292a 100644 --- a/apps/renderer/src/modules/entry-content/actions/header-actions.tsx +++ b/apps/renderer/src/modules/entry-content/actions/header-actions.tsx @@ -1,6 +1,6 @@ -import { ActionButton } from "@follow/components/ui/button/index.js" import type { FeedViewType } from "@follow/constants" +import { CommandActionButton } from "~/components/ui/button/command-button" import { useHasModal } from "~/components/ui/modal/stacked/hooks" import { shortcuts } from "~/constants/shortcuts" import { useEntryActions } from "~/hooks/biz/useEntryActions" @@ -36,14 +36,13 @@ export const EntryHeaderActions = ({ entryId, view }: { entryId: string; view?: ) .map((config) => { return ( - ) }) diff --git a/apps/renderer/src/modules/entry-content/actions/more-actions.tsx b/apps/renderer/src/modules/entry-content/actions/more-actions.tsx index e007e3be09..cab0b16dda 100644 --- a/apps/renderer/src/modules/entry-content/actions/more-actions.tsx +++ b/apps/renderer/src/modules/entry-content/actions/more-actions.tsx @@ -10,6 +10,8 @@ import { } from "~/components/ui/dropdown-menu/dropdown-menu" import { useEntryActions } from "~/hooks/biz/useEntryActions" import { COMMAND_ID } from "~/modules/command/commands/id" +import { useCommand } from "~/modules/command/hooks/use-command" +import type { FollowCommandId } from "~/modules/command/types" export const MoreActions = ({ entryId, view }: { entryId: string; view?: FeedViewType }) => { const actionConfigs = useEntryActions({ entryId, view }) @@ -36,16 +38,25 @@ export const MoreActions = ({ entryId, view }: { entryId: string; view?: FeedVie {availableActions.map((config) => ( - - {config.name} - + ))} ) } + +const CommandDropdownMenuItem = ({ + commandId, + onClick, +}: { + commandId: FollowCommandId + onClick: () => void +}) => { + const command = useCommand(commandId) + if (!command) return null + return ( + + {command.label.title} + + ) +} diff --git a/apps/renderer/src/modules/entry-content/header.mobile.tsx b/apps/renderer/src/modules/entry-content/header.mobile.tsx index f399497367..96e08b351f 100644 --- a/apps/renderer/src/modules/entry-content/header.mobile.tsx +++ b/apps/renderer/src/modules/entry-content/header.mobile.tsx @@ -1,4 +1,4 @@ -import { ActionButton, MotionButtonBase } from "@follow/components/ui/button/index.js" +import { MotionButtonBase } from "@follow/components/ui/button/index.js" import { RootPortal } from "@follow/components/ui/portal/index.js" import { findElementInShadowDOM } from "@follow/utils/dom" import { clsx, cn } from "@follow/utils/utils" @@ -10,6 +10,7 @@ import { useEventCallback } from "usehooks-ts" import { useUISettingKey } from "~/atoms/settings/ui" import { HeaderTopReturnBackButton } from "~/components/mobile/button" +import { CommandActionButton } from "~/components/ui/button/command-button" import { useScrollTracking, useTocItems } from "~/components/ui/markdown/components/hooks" import { ENTRY_CONTENT_RENDER_CONTAINER_ID } from "~/constants/dom" import type { EntryActionItem } from "~/hooks/biz/useEntryActions" @@ -17,6 +18,8 @@ import { useEntryActions } from "~/hooks/biz/useEntryActions" import { useEntry } from "~/store/entry/hooks" import { COMMAND_ID } from "../command/commands/id" +import { useCommand } from "../command/hooks/use-command" +import type { FollowCommandId } from "../command/types" import { useEntryContentScrollToTop, useEntryTitleMeta } from "./atoms" import type { EntryHeaderProps } from "./header.shared" @@ -87,13 +90,12 @@ function EntryHeaderImpl({ view, entryId, className }: EntryHeaderProps) { )} > {actionConfigs.map((item) => ( - ))}
@@ -177,18 +179,14 @@ const HeaderRightActions = ({ >
{actions.map((item) => ( - { setCtxOpen(false) item.onClick?.() }} - key={item.name} - layout={false} - className="flex w-full items-center gap-2 px-4 py-2" - > - {item.icon} - {item.name} - + /> ))}
@@ -200,6 +198,28 @@ const HeaderRightActions = ({
) } + +const CommandMotionButton = ({ + commandId, + onClick, +}: { + commandId: FollowCommandId + onClick: () => void +}) => { + const command = useCommand(commandId) + if (!command) return null + return ( + + {command.icon} + {command.label.title} + + ) +} + const TableOfContentsIcon = ({ className = "size-6" }) => { return ( + tooltip?: React.ReactNode + tooltipSide?: "top" | "bottom" + active?: boolean + disabled?: boolean + shortcut?: string + disableTriggerShortcut?: boolean + enableHoverableContent?: boolean + size?: "sm" | "md" | "base" + + /** + * @description only trigger shortcut when focus with in `` + * @default false + */ + shortcutOnlyFocusWithIn?: boolean +} + +const actionButtonStyleVariant = { + size: { + base: tw`text-xl size-8`, + sm: tw`text-sm size-6`, + }, +} + +export const ActionButton = React.forwardRef< + HTMLButtonElement, + ComponentType & React.HTMLAttributes +>( + ( + { + icon, + + tooltip, + className, + tooltipSide, + children, + active, + shortcut, + disabled, + disableTriggerShortcut, + enableHoverableContent, + size = "base", + shortcutOnlyFocusWithIn, + onClick, + ...rest + }, + ref, + ) => { + const finalShortcut = + getOS() === "Windows" ? shortcut?.replace("meta", "ctrl").replace("Meta", "Ctrl") : shortcut + const buttonRef = React.useRef(null) + React.useImperativeHandle(ref, () => buttonRef.current!) + + const [loading, setLoading] = useState(false) + + const Trigger = ( + + ) : typeof icon === "function" ? ( + React.createElement(icon, { + className: "size-4 grayscale text-current", + }) + ) : ( + icon + )} + + {children} + + ) + + return ( + <> + {finalShortcut && !disableTriggerShortcut && ( + buttonRef.current?.click()} + shortcutOnlyFocusWithIn={shortcutOnlyFocusWithIn} + /> + )} + {tooltip ? ( + + + {Trigger} + + + + {tooltip} + {!!finalShortcut && ( +
+ {finalShortcut} +
+ )} +
+
+
+ ) : ( + Trigger + )} + + ) + }, +) + +const HotKeyTrigger = ({ + shortcut, + fn, + options, + shortcutOnlyFocusWithIn, +}: { + shortcut: string + fn: () => void + options?: Options + shortcutOnlyFocusWithIn?: boolean +}) => { + const isFocusWithIn = useFocusable() + const enabledInOptions = options?.enabled || true + + useHotkeys(shortcut, fn, { + preventDefault: true, + enabled: shortcutOnlyFocusWithIn + ? isFocusWithIn + ? enabledInOptions + : false + : enabledInOptions, + ...options, + }) + return null +} diff --git a/packages/components/src/ui/button/index.tsx b/packages/components/src/ui/button/index.tsx index b47183700a..4cf502e573 100644 --- a/packages/components/src/ui/button/index.tsx +++ b/packages/components/src/ui/button/index.tsx @@ -1,18 +1,11 @@ -import { useFocusable } from "@follow/components/common/Focusable.jsx" import { useMobile } from "@follow/components/hooks/useMobile.js" import { LoadingCircle } from "@follow/components/ui/loading/index.jsx" -import { stopPropagation } from "@follow/utils/dom" -import { cn, getOS } from "@follow/utils/utils" +import { cn } from "@follow/utils/utils" import type { VariantProps } from "class-variance-authority" import type { HTMLMotionProps } from "framer-motion" import { m } from "framer-motion" import * as React from "react" -import { useState } from "react" -import type { Options } from "react-hotkeys-hook" -import { useHotkeys } from "react-hotkeys-hook" -import { KbdCombined } from "../kbd/Kbd" -import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "../tooltip" import { styledButtonVariant } from "./variants" export interface BaseButtonProps { @@ -21,163 +14,6 @@ export interface BaseButtonProps { // BIZ buttons -export interface ActionButtonProps { - icon?: React.ReactNode | React.FC - tooltip?: React.ReactNode - tooltipSide?: "top" | "bottom" - active?: boolean - disabled?: boolean - shortcut?: string - disableTriggerShortcut?: boolean - enableHoverableContent?: boolean - size?: "sm" | "md" | "base" - - /** - * @description only trigger shortcut when focus with in `` - * @default false - */ - shortcutOnlyFocusWithIn?: boolean -} - -const actionButtonStyleVariant = { - size: { - base: tw`text-xl size-8`, - sm: tw`text-sm size-6`, - }, -} -export const ActionButton = React.forwardRef< - HTMLButtonElement, - ComponentType & React.HTMLAttributes ->( - ( - { - icon, - - tooltip, - className, - tooltipSide, - children, - active, - shortcut, - disabled, - disableTriggerShortcut, - enableHoverableContent, - size = "base", - shortcutOnlyFocusWithIn, - onClick, - ...rest - }, - ref, - ) => { - const finalShortcut = - getOS() === "Windows" ? shortcut?.replace("meta", "ctrl").replace("Meta", "Ctrl") : shortcut - const buttonRef = React.useRef(null) - React.useImperativeHandle(ref, () => buttonRef.current!) - - const [loading, setLoading] = useState(false) - - const Trigger = ( - - ) - - return ( - <> - {finalShortcut && !disableTriggerShortcut && ( - buttonRef.current?.click()} - shortcutOnlyFocusWithIn={shortcutOnlyFocusWithIn} - /> - )} - {tooltip ? ( - - - {Trigger} - - - - {tooltip} - {!!finalShortcut && ( -
- {finalShortcut} -
- )} -
-
-
- ) : ( - Trigger - )} - - ) - }, -) - -const HotKeyTrigger = ({ - shortcut, - fn, - options, - shortcutOnlyFocusWithIn, -}: { - shortcut: string - fn: () => void - options?: Options - shortcutOnlyFocusWithIn?: boolean -}) => { - const isFocusWithIn = useFocusable() - const enabledInOptions = options?.enabled || true - - useHotkeys(shortcut, fn, { - preventDefault: true, - enabled: shortcutOnlyFocusWithIn - ? isFocusWithIn - ? enabledInOptions - : false - : enabledInOptions, - ...options, - }) - return null -} const motionBaseMap = { pc: { whileFocus: { scale: 1.02 }, @@ -298,3 +134,5 @@ export const IconButton = React.forwardRef< ) }) + +export { ActionButton, type ActionButtonProps } from "./action-button" diff --git a/packages/components/src/ui/button/interface.ts b/packages/components/src/ui/button/interface.ts new file mode 100644 index 0000000000..cebfd37be9 --- /dev/null +++ b/packages/components/src/ui/button/interface.ts @@ -0,0 +1,25 @@ +import type * as React from "react" + +export interface BaseButtonProps { + isLoading?: boolean +} + +// BIZ buttons + +export interface ActionButtonProps { + icon?: React.ReactNode | React.FC + tooltip?: React.ReactNode + tooltipSide?: "top" | "bottom" + active?: boolean + disabled?: boolean + shortcut?: string + disableTriggerShortcut?: boolean + enableHoverableContent?: boolean + size?: "sm" | "md" | "base" + + /** + * @description only trigger shortcut when focus with in `` + * @default false + */ + shortcutOnlyFocusWithIn?: boolean +} From 88458bd7a3e32854cd5b7b93f36c72f6cf3abac5 Mon Sep 17 00:00:00 2001 From: dai Date: Tue, 17 Dec 2024 21:46:23 +0900 Subject: [PATCH 30/39] fix: update Japanese translations for user actions and login prompts (#2188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update ja.json * chore: update 6 ja.json files chore: update and bump 6 ja.json files. * chore: update error/ja.json fix locale/error/ja.json * chore: auto-fix linting and formatting issues * chore: update app/ja.json * chore: update ja.json * chore: update ja.json * chore: update ja.json and tested local. * chore: update ja.json update settings/ja.json * feat(i18n): update Japanese locale files with new error messages and dialog options * chore(vscode): set default formatter for JSON and JSONC files * Remove JSON and JSONC formatter settings so sorry, @hyoban 😢 Perhaps my local settings are in. I have restored it to the original. Thanks for your kindness. You have always been a great help. * chore: auto-fix linting and formatting issues * chore: update ja.json sync and update settings/ja.json * chore: update Japanese translations for clarity and consistency * chore: update some ja.json * chore(locales): update Japanese translations with new entry actions and settings * fix: update Japanese translations for user actions and login prompts --------- Co-authored-by: dai --- locales/app/ja.json | 2 ++ locales/external/ja.json | 31 +++++++++++++++++++++++++++---- locales/settings/ja.json | 18 +++++++++++++++--- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/locales/app/ja.json b/locales/app/ja.json index f93cba1321..defb47c4ab 100644 --- a/locales/app/ja.json +++ b/locales/app/ja.json @@ -373,6 +373,7 @@ "trending.user": "人気ユーザー", "user_button.account": "アカウント", "user_button.achievement": "実績", + "user_button.actions": "アクション", "user_button.download_desktop_app": "アプリをダウンロード", "user_button.log_out": "ログアウト", "user_button.power": "Power", @@ -385,6 +386,7 @@ "user_profile.share": "共有", "user_profile.toggle_item_style": "アイテムスタイルを切り替え", "words.achievement": "実績", + "words.actions": "アクション", "words.add": "追加", "words.boost": "ブースト", "words.browser": "ブラウザー", diff --git a/locales/external/ja.json b/locales/external/ja.json index 26171f63c8..0611142808 100644 --- a/locales/external/ja.json +++ b/locales/external/ja.json @@ -7,8 +7,8 @@ "feed.follow_to_view_all": " {{count}} 件のフィードをすべてフォローします...", "feed.follower_one": "フォロー", "feed.follower_other": "フォロー", - "feed.followsAndFeeds": "{{subscriptionCount}} {{subscriptionNoun}} と {{feedsCount}} {{feedsNoun}} on {{appName}}", - "feed.followsAndReads": "{{appName}} で {{subscriptionCount}} {{subscriptionNoun}} と {{readCount}} {{readNoun}}", + "feed.followsAndFeeds": "{{subscriptionCount}} {{subscriptionNoun}} 、 {{feedsCount}} {{feedsNoun}} on {{appName}}", + "feed.followsAndReads": "{{appName}} 、 {{subscriptionCount}} {{subscriptionNoun}} 、 {{readCount}} {{readNoun}}", "feed.madeby": "作成者", "feed.preview": "プレビュー", "feed.read_one": "読む", @@ -34,14 +34,37 @@ "invitation.getCodeMessage": "以下の方法で招待コードを入手できます:", "invitation.title": "招待コード", "login.backToWebApp": "ウェブアプリに戻る", + "login.confirm_password.label": "パスワードの確認", "login.continueWith": "{{provider}} で続ける", + "login.email": "Email", + "login.forget_password.description": "あなたのアカウントに関連付けられたメールを入力してください。パスワードのリセット方法を記したメールを送信します。", + "login.forget_password.label": "パスワードを忘れた場合", + "login.forget_password.note": "パスワードを忘れましたか?", + "login.forget_password.success": "メールの送信に成功しました。", "login.logInTo": "ログイン ", + "login.logInWithEmail": "メールでログイン", + "login.new_password.label": "新しいパスワード", "login.openApp": "アプリを開く", + "login.or": "または", + "login.password": "パスワード", "login.redirecting": "リダイレクト中", + "login.register": "アカウントを作成", + "login.reset_password.description": "パスワードをリセットするには新しいパスワードを入力して確認を押してください", + "login.reset_password.label": "パスワードをリセット", + "login.reset_password.success": "パスワードは正常にリセットされました", "login.signOut": "サインアウト", + "login.signUp": "メールでサインアップ", + "login.submit": "送信", "login.welcomeTo": "ようこそ ", - "redirect.continueInBrowser": "ブラウザで続行", + "redirect.continueInBrowser": "ブラウザーで続行", "redirect.instruction": "今が{{app_name}}を開き、このページを安全に閉じる時です。", "redirect.openApp": "{{app_name}}を開く", - "redirect.successMessage": "{{app_name}}アカウントに正常に接続されました。" + "redirect.successMessage": "{{app_name}}アカウントに正常に接続されました。", + "register.confirm_password": "パスワードを確認", + "register.email": "Email", + "register.label": "{{app_name}} のアカウントを作成する", + "register.login": "ログイン", + "register.note": "アカウントを既にお持ちですか? ", + "register.password": "パスワード", + "register.submit": "アカウントを作成" } diff --git a/locales/settings/ja.json b/locales/settings/ja.json index 7614c57b11..86f26675f4 100644 --- a/locales/settings/ja.json +++ b/locales/settings/ja.json @@ -37,7 +37,7 @@ "actions.action_card.operation_options.is_not_equal_to": "等しくない", "actions.action_card.operation_options.matches_regex": "正規表現に一致する", "actions.action_card.operator": "オペレーター", - "actions.action_card.or": "なたは", + "actions.action_card.or": "または", "actions.action_card.rewrite_rules": "リライトルール", "actions.action_card.silence": "サイレント", "actions.action_card.source_content": "ソースコンテンツを表示する", @@ -101,6 +101,7 @@ "data_control.app_cache_limit.label": "キャッシュリミット", "data_control.clean_cache.button": "キャッシュをクリアー", "data_control.clean_cache.description": "空き容量を確保するためにキャッシュをクリアーします。", + "data_control.clean_cache.description_web": "サービスワーカーのキャッシュを削除して空き容量を確保します。", "feeds.claimTips": "フィードを認証してチップを受け取るには、購読リストのフィードを右クリックして「フィードをクレーム」を選択してください。", "feeds.noFeeds": "認証されたフィードはありません", "feeds.tableHeaders.name": "名前", @@ -258,15 +259,26 @@ "lists.title": "タイトル", "lists.view": "画像", "profile.avatar.label": "アバター", - "profile.handle.description": "あなたの一意の識別子", + "profile.change_password.label": "パスワードを変更", + "profile.confirm_password.label": "パスワードの確認", + "profile.current_password.label": "現在のパスワード", + "profile.email.label": "Email", + "profile.email.send_verification": "確認メールを送信する", + "profile.email.unverified": "未確認", + "profile.email.verification_sent": "確認メールを送信しました", + "profile.email.verified": "確認済み", + "profile.handle.description": "あなた個人の識別子です", "profile.handle.label": "ハンドル", "profile.name.description": "公開表示名", "profile.name.label": "表示名", + "profile.new_password.label": "新しいパスワード", + "profile.reset_password_mail_sent": "パスワードリセットメールを送信しました", "profile.sidebar_title": "プロフィール", "profile.submit": "送信", "profile.title": "プロフィール設定", "profile.updateSuccess": "プロフィールが更新されました。", - "titles.about": "アバウト", + "profile.update_password_success": "パスワードが更新されました。", + "titles.about": "about", "titles.actions": "アクション", "titles.appearance": "外観", "titles.data_control": "データのコントロール", From 926b409611f5d711b5e4bd415a9ceceb61cb5335 Mon Sep 17 00:00:00 2001 From: Innei Date: Wed, 18 Dec 2024 23:51:58 +0800 Subject: [PATCH 31/39] chore: update gitignore Signed-off-by: Innei --- apps/mobile/.gitignore | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 apps/mobile/.gitignore diff --git a/apps/mobile/.gitignore b/apps/mobile/.gitignore new file mode 100644 index 0000000000..130dba91b9 --- /dev/null +++ b/apps/mobile/.gitignore @@ -0,0 +1,40 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +app-example +ios +android \ No newline at end of file From 49470821dca772a040b2b845c3c019f057a6047c Mon Sep 17 00:00:00 2001 From: Innei Date: Thu, 19 Dec 2024 00:01:50 +0800 Subject: [PATCH 32/39] feat: re-design pwa updated notice Signed-off-by: Innei --- apps/renderer/src/atoms/updater.ts | 2 +- .../src/components/common/ReloadPrompt.tsx | 32 +++------------ .../app-layout/feed-column/desktop.tsx | 4 +- .../app-layout/feed-column/index.mobile.tsx | 3 ++ .../update-notice/UpdateNotice.mobile.tsx | 41 +++++++++++++++++++ .../UpdateNotice.tsx} | 6 ++- locales/app/en.json | 1 + 7 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 apps/renderer/src/modules/update-notice/UpdateNotice.mobile.tsx rename apps/renderer/src/modules/{feed-column/auto-updater.tsx => update-notice/UpdateNotice.tsx} (96%) diff --git a/apps/renderer/src/atoms/updater.ts b/apps/renderer/src/atoms/updater.ts index 70c566f2c7..eb2665b3a1 100644 --- a/apps/renderer/src/atoms/updater.ts +++ b/apps/renderer/src/atoms/updater.ts @@ -4,7 +4,7 @@ import { createAtomHooks } from "~/lib/jotai" export type UpdaterStatus = "ready" export type UpdaterStatusAtom = { - type: "app" | "renderer" + type: "app" | "renderer" | "pwa" status: UpdaterStatus } | null export const [, , useUpdaterStatus, , getUpdaterStatus, setUpdaterStatus] = createAtomHooks( diff --git a/apps/renderer/src/components/common/ReloadPrompt.tsx b/apps/renderer/src/components/common/ReloadPrompt.tsx index 2643f67a10..31006b6a84 100644 --- a/apps/renderer/src/components/common/ReloadPrompt.tsx +++ b/apps/renderer/src/components/common/ReloadPrompt.tsx @@ -1,13 +1,13 @@ import { useEffect } from "react" -import { toast } from "sonner" import { useRegisterSW } from "virtual:pwa-register/react" +import { setUpdaterStatus } from "~/atoms/updater" + // check for updates every hour const period = 60 * 60 * 1000 export function ReloadPrompt() { const { - // offlineReady: [offlineReady, setOfflineReady], needRefresh: [needRefresh], updateServiceWorker, } = useRegisterSW({ @@ -24,33 +24,11 @@ export function ReloadPrompt() { }, }) - // const close = useCallback(() => { - // setOfflineReady(false) - // setNeedRefresh(false) - // }, [setNeedRefresh, setOfflineReady]) - - // useEffect(() => { - // if (offlineReady) { - // toast.info("App is ready to work offline", { - // action: { - // label: "Close", - // onClick: close, - // }, - // duration: Infinity, - // }) - // } - // }, [offlineReady, close]) - useEffect(() => { if (needRefresh) { - toast.info("New version available", { - action: { - label: "Refresh", - onClick: () => { - updateServiceWorker(true) - }, - }, - duration: Infinity, + setUpdaterStatus({ + type: "pwa", + status: "ready", }) } }, [needRefresh, updateServiceWorker]) diff --git a/apps/renderer/src/modules/app-layout/feed-column/desktop.tsx b/apps/renderer/src/modules/app-layout/feed-column/desktop.tsx index 05e46c1814..828d9a5dd7 100644 --- a/apps/renderer/src/modules/app-layout/feed-column/desktop.tsx +++ b/apps/renderer/src/modules/app-layout/feed-column/desktop.tsx @@ -42,12 +42,12 @@ import { LoginModalContent } from "~/modules/auth/LoginModalContent" import { DebugRegistry } from "~/modules/debug/registry" import { FeedColumn } from "~/modules/feed-column" import { getSelectedFeedIds, resetSelectedFeedIds } from "~/modules/feed-column/atom" -import { AutoUpdater } from "~/modules/feed-column/auto-updater" import { useShortcutsModal } from "~/modules/modal/shortcuts" import { CmdF } from "~/modules/panel/cmdf" import { SearchCmdK } from "~/modules/panel/cmdk" import { CmdNTrigger } from "~/modules/panel/cmdn" import { CornerPlayer } from "~/modules/player/corner-player" +import { UpdateNotice } from "~/modules/update-notice/UpdateNotice" import { AppNotificationContainer } from "~/modules/upgrade/lazy/index" import { AppLayoutGridContainerProvider } from "~/providers/app-grid-layout-container-provider" @@ -113,7 +113,7 @@ export function MainDestopLayout() { - {ELECTRON && } + diff --git a/apps/renderer/src/modules/app-layout/feed-column/index.mobile.tsx b/apps/renderer/src/modules/app-layout/feed-column/index.mobile.tsx index 43db8498b1..3986a88fce 100644 --- a/apps/renderer/src/modules/app-layout/feed-column/index.mobile.tsx +++ b/apps/renderer/src/modules/app-layout/feed-column/index.mobile.tsx @@ -5,6 +5,7 @@ import { Outlet } from "react-router" import { useLoginModalShow, useWhoami } from "~/atoms/user" import { useDailyTask } from "~/hooks/biz/useDailyTask" import { LoginModalContent } from "~/modules/auth/LoginModalContent" +import { UpdateNotice } from "~/modules/update-notice/UpdateNotice.mobile" import { NewUserGuide } from "./index.shared" @@ -17,6 +18,8 @@ export const MobileRootLayout = () => { + + {isAuthFail && !user && ( { + const updaterStatus = useUpdaterStatus() + const { t } = useTranslation() + + const handleClick = useCallback(() => { + const status = getUpdaterStatus() + if (!status) return + + switch (status.type) { + case "pwa": { + window.location.reload() + break + } + } + setUpdaterStatus(null) + }, []) + + if (!updaterStatus) return null + + return ( + +
{t("notify.update_info", { app_name: APP_NAME })}
+
{t("notify.update_info_3")}
+
+ ) +} diff --git a/apps/renderer/src/modules/feed-column/auto-updater.tsx b/apps/renderer/src/modules/update-notice/UpdateNotice.tsx similarity index 96% rename from apps/renderer/src/modules/feed-column/auto-updater.tsx rename to apps/renderer/src/modules/update-notice/UpdateNotice.tsx index d414df73f9..0b079b3cd4 100644 --- a/apps/renderer/src/modules/feed-column/auto-updater.tsx +++ b/apps/renderer/src/modules/update-notice/UpdateNotice.tsx @@ -9,7 +9,7 @@ import { softBouncePreset } from "~/components/ui/constants/spring" import { tipcClient } from "~/lib/client" import { handlers } from "~/tipc" -export const AutoUpdater = () => { +export const UpdateNotice = () => { const updaterStatus = useUpdaterStatus() const { t } = useTranslation() @@ -38,6 +38,10 @@ export const AutoUpdater = () => { tipcClient?.rendererUpdateReload() break } + case "pwa": { + window.location.reload() + break + } } setUpdaterStatus(null) }, []) diff --git a/locales/app/en.json b/locales/app/en.json index b82fc81e86..94bf861c3f 100644 --- a/locales/app/en.json +++ b/locales/app/en.json @@ -271,6 +271,7 @@ "notify.update_info": "{{app_name}} is ready to update!", "notify.update_info_1": "Click to restart", "notify.update_info_2": "Click to reload page", + "notify.update_info_3": "Touch to reload page", "player.back_10s": "Back 10s", "player.close": "Close", "player.download": "Download", From aab23d3a347ee445f9a53d4053624e9daad4662c Mon Sep 17 00:00:00 2001 From: X Date: Thu, 19 Dec 2024 13:36:45 +0800 Subject: [PATCH 33/39] fix(animation): preserve exit transition for smoother animations (#2237) --- apps/renderer/src/components/common/Motion.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/renderer/src/components/common/Motion.tsx b/apps/renderer/src/components/common/Motion.tsx index b4d26341e9..f6d092d485 100644 --- a/apps/renderer/src/components/common/Motion.tsx +++ b/apps/renderer/src/components/common/Motion.tsx @@ -1,4 +1,4 @@ -import type { MotionProps } from "framer-motion" +import type { MotionProps, TargetAndTransition } from "framer-motion" import { m as M } from "framer-motion" import { createElement, forwardRef } from "react" @@ -19,6 +19,7 @@ export const m: typeof M = new Proxy(M, { if (props.exit) { nextProps.exit = { opacity: 0, + transition: (props.exit as TargetAndTransition).transition, } } From 3f91883f5eaa0d398af00509762e497fbd2d3e13 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:08:26 +0800 Subject: [PATCH 34/39] fix: secure session token --- CONTRIBUTE.md | 2 +- README.md | 2 +- apps/server/src/lib/api-client.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md index 692ce9d39a..e7d471a11e 100644 --- a/CONTRIBUTE.md +++ b/CONTRIBUTE.md @@ -48,7 +48,7 @@ If you prefer to develop in Electron, follow these steps: pnpm run dev ``` -> **Tip:** If you encounter login issues, copy the `better-auth.session_token` from your browser's cookies into the app. +> **Tip:** If you encounter login issues, copy the `__Secure-better-auth.session_token` from your browser's cookies into the app. ## Contribution Guidelines diff --git a/README.md b/README.md index d40dd254d9..3f8996328f 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ pnpm run dev Since it is not very convenient to develop in Electron, the first way to develop and contribute is recommended at this stage. > [!TIP] -> If you can't log in to the app, copy the `better-auth.session_token` in the cookie from your browser into the app. +> If you can't log in to the app, copy the `__Secure-better-auth.session_token` in the cookie from your browser into the app. ## 📝 License diff --git a/apps/server/src/lib/api-client.ts b/apps/server/src/lib/api-client.ts index c71b2d75c6..3cf0a937d6 100644 --- a/apps/server/src/lib/api-client.ts +++ b/apps/server/src/lib/api-client.ts @@ -59,7 +59,7 @@ export const createApiClient = () => { "X-App-Version": PKG.version, "X-App-Dev": isDev ? "1" : "0", "User-Agent": `Follow External Server Api Client/${PKG.version}`, - Cookie: authSessionToken ? `better-auth.session_token=${authSessionToken}` : "", + Cookie: authSessionToken ? `__Secure-better-auth.session_token=${authSessionToken}` : "", } }, }) @@ -78,7 +78,7 @@ export const getTokenFromCookie = (cookie: string) => { }, {} as Record, ) - return parsedCookieMap["better-auth.session_token"] + return parsedCookieMap["__Secure-better-auth.session_token"] } export type ApiClient = ReturnType From f9ee93d91fadc1aaaf3e5a61b146dfb4b77f1755 Mon Sep 17 00:00:00 2001 From: Innei Date: Thu, 19 Dec 2024 16:47:43 +0800 Subject: [PATCH 35/39] chore(release): release v0.2.8-beta.0 --- CHANGELOG.md | 22 +++++++++++++++++++++- changelog/0.2.8.md | 9 +++++++++ changelog/next.md | 2 -- package.json | 4 ++-- 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 changelog/0.2.8.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bd18e1471..657e1345f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG -## [0.2.7-beta.1](https://github.com/RSSNext/follow/compare/v0.2.7-beta.0...v0.2.7-beta.1) (2024-12-16) +## [0.2.8-beta.0](https://github.com/RSSNext/follow/compare/v0.2.7-beta.0...v0.2.8-beta.0) (2024-12-19) ### Bug Fixes @@ -31,6 +31,7 @@ * allow select one feed ([3c21d92](https://github.com/RSSNext/follow/commit/3c21d92baf0a03373224ac9d5a7a416947788388)), closes [#1512](https://github.com/RSSNext/follow/issues/1512) * allow selecting when meta key is pressed ([6b8572c](https://github.com/RSSNext/follow/commit/6b8572c37aa287aa544e6ddb2847693ba597b0be)) * always exclude routeParams ([1466886](https://github.com/RSSNext/follow/commit/1466886c426095aa59bbc06f93a44245bfa3b480)) +* **animation:** preserve exit transition for smoother animations ([#2237](https://github.com/RSSNext/follow/issues/2237)) ([aab23d3](https://github.com/RSSNext/follow/commit/aab23d3a347ee445f9a53d4053624e9daad4662c)) * app notification font size ([1b5f944](https://github.com/RSSNext/follow/commit/1b5f9448062bde224770dfe5129304d7a812585d)) * app update logic ([9680351](https://github.com/RSSNext/follow/commit/9680351f2c5f6c6ca6542396a722719a69fa5064)) * app upgrade toast open link should in new window ([fc81546](https://github.com/RSSNext/follow/commit/fc81546bed6b07626215a57543a735001728f520)) @@ -65,6 +66,7 @@ * ci ([#1943](https://github.com/RSSNext/follow/issues/1943)) ([f7eca73](https://github.com/RSSNext/follow/commit/f7eca73f5b2a5f1d3d24a3189fd4708da282b45b)) * **ci:** concurrency group ([fb0152e](https://github.com/RSSNext/follow/commit/fb0152eaec76b28ed9fc26381917ca7ec2aecb0b)) * **ci:** forge build error ([#1500](https://github.com/RSSNext/follow/issues/1500)) ([a5e3e02](https://github.com/RSSNext/follow/commit/a5e3e0228c7088e78061801466267ebdbd288e2c)) +* **ci:** should upload render assets ([fb08ee9](https://github.com/RSSNext/follow/commit/fb08ee964d8bdeedc243e0e5242abd274aef5f85)) * **ci:** turbo not found ([c812bd7](https://github.com/RSSNext/follow/commit/c812bd71aabc63b5c9ebbe032f25aba5ac51bd64)) * **ci:** windows build ([9f72c26](https://github.com/RSSNext/follow/commit/9f72c263a6945fe90899f85f732c0866c7f61b25)) * claim modal style in mobile ([a360235](https://github.com/RSSNext/follow/commit/a3602350236cf642825b24caad5801f37e7d093d)) @@ -74,6 +76,7 @@ * clear selection when pointer down, fixed [#1448](https://github.com/RSSNext/follow/issues/1448) ([33d3906](https://github.com/RSSNext/follow/commit/33d39067b5c5fdc8f302ca3ea13a740bbb014088)) * click outside to click area ([51194ac](https://github.com/RSSNext/follow/commit/51194aca6c51c9550703f416fa204f367d8a00de)) * **cmdk:** change default cmdk search type to feed ([4a8fdea](https://github.com/RSSNext/follow/commit/4a8fdea84a4df90313f8c577d77d3944a34aa149)) +* commands in action responsive ([ffca130](https://github.com/RSSNext/follow/commit/ffca1301b4f89a27b995b79b6e43d6b7f581e5c4)) * condition key ([ffb3b94](https://github.com/RSSNext/follow/commit/ffb3b943b0c502cf940d7f3c0d4d506489b65d37)) * content ([5a30f38](https://github.com/RSSNext/follow/commit/5a30f384082949f17a71f6d1f837b30966368157)) * context menu for multi select ([d68de79](https://github.com/RSSNext/follow/commit/d68de7933235351d64df68e3e2c49623184f4284)) @@ -130,6 +133,7 @@ * fallback for code can not render to html ([08acded](https://github.com/RSSNext/follow/commit/08acded2d549bc80d2ed8f180a6d89cc3a3c861a)), closes [#1142](https://github.com/RSSNext/follow/issues/1142) * feed action in selection ([ef9db5c](https://github.com/RSSNext/follow/commit/ef9db5c8b39c8229b1e907c6a0e6e74431f677de)) * feed boost modal title wrap ([a3eb287](https://github.com/RSSNext/follow/commit/a3eb2875e9456e8ec30ed96294b2bad82ccd5a4d)) +* feed column flickering ([#2143](https://github.com/RSSNext/follow/issues/2143)) ([ffe535d](https://github.com/RSSNext/follow/commit/ffe535d5e7f2aea0b7a0d1d1a13cdc49559044c5)) * feed column pb in mobile ([4035a6b](https://github.com/RSSNext/follow/commit/4035a6b7b839a284e452f51e75c759f25136784a)) * feed icon fallback delay ms ([89239d8](https://github.com/RSSNext/follow/commit/89239d89aa52429eef348e500c7ed7d6170c766a)) * feed icon margin right ([0f32364](https://github.com/RSSNext/follow/commit/0f3236406dc354adc9830c51e73f9fb4790f159a)) @@ -146,6 +150,7 @@ * handle tipUsers migration for older feed versions ([#1384](https://github.com/RSSNext/follow/issues/1384)) ([2ca6d80](https://github.com/RSSNext/follow/commit/2ca6d807e3c0626eca79dd6789ae20cdc0639fc3)) * handle undefined arguments in executeJavaScript call ([#1958](https://github.com/RSSNext/follow/issues/1958)) ([51519e2](https://github.com/RSSNext/follow/commit/51519e2d7a1a7aee81041c88ec978864903d0b2d)) * handling default port in proxy configuration ([0bd76a2](https://github.com/RSSNext/follow/commit/0bd76a24a240951782d44b7e38040c305bb2b9b9)) +* **header:** improve border visibility for meta display ([#2138](https://github.com/RSSNext/follow/issues/2138)) ([0505e3d](https://github.com/RSSNext/follow/commit/0505e3df3f7b2bce393440d101f7a33b5b5fa773)) * hide cache control in web ([961bd0f](https://github.com/RSSNext/follow/commit/961bd0fccd63e7e9a21c9bea62cd426bac9a8423)) * hide debug in mobile ([930be73](https://github.com/RSSNext/follow/commit/930be73c7e543d695bde22b677d37f42cd5d7b94)) * hide to tray on close window ([392135b](https://github.com/RSSNext/follow/commit/392135b8c4b4ec00aa49e66b28bcb0e77390683a)) @@ -246,6 +251,7 @@ * scroll out to mark read in mobile ([a44d598](https://github.com/RSSNext/follow/commit/a44d59820ba7e62b5db9de478f813cd9f287f670)) * scroll state for mobile float bar ([126206b](https://github.com/RSSNext/follow/commit/126206b995f47c17ed60c2a07296987dd0b8dc95)) * scrollbar z-index ([2b2d56c](https://github.com/RSSNext/follow/commit/2b2d56c746fc5cb44cd344cb7911f100f17114fe)) +* secure session token ([3f91883](https://github.com/RSSNext/follow/commit/3f91883f5eaa0d398af00509762e497fbd2d3e13)) * **security:** add dompurify for raw document string pre-cleaning on readability to avoid xss attack ([#2000](https://github.com/RSSNext/follow/issues/2000)) ([f2ed678](https://github.com/RSSNext/follow/commit/f2ed678afc5a17b099a5931aa5af3a36138750d0)) * selected id state ([36cdbbd](https://github.com/RSSNext/follow/commit/36cdbbd706c41b10b6fd19d84756b35f8c0a3f8b)) * selecting state ([146fe8f](https://github.com/RSSNext/follow/commit/146fe8fa5ccf4c6211bb5d72be9efbb0714de878)) @@ -318,8 +324,13 @@ * update electron to resolve present context menu ([2d31b64](https://github.com/RSSNext/follow/commit/2d31b646fd896e6ba55782c447b5d6ad9b327623)) * update empty category correctly ([c7efc67](https://github.com/RSSNext/follow/commit/c7efc67636b4d8b39ec50e26eb2d4839d0cd8750)) * update i18n-detector ([7969022](https://github.com/RSSNext/follow/commit/7969022657b4fe2b5c9a0a0f93467604f697d796)) +* update Japanese translations for user actions and login prompts ([#2188](https://github.com/RSSNext/follow/issues/2188)) ([88458bd](https://github.com/RSSNext/follow/commit/88458bd7a3e32854cd5b7b93f36c72f6cf3abac5)) +* update linkedom to version 0.18.6 ([#2185](https://github.com/RSSNext/follow/issues/2185)) ([faebe6c](https://github.com/RSSNext/follow/commit/faebe6c819cd0742470f8aebe9be5f978d3827e6)) +* update SupportCreator component styles for better layout ([351f342](https://github.com/RSSNext/follow/commit/351f34289fab89f9866405f8910e6226cb88ec87)) * update tray icon path for windows and improve tray behavior ([#1511](https://github.com/RSSNext/follow/issues/1511)) ([3706874](https://github.com/RSSNext/follow/commit/3706874e42c215a84df71eae1c135a114be8985a)) +* update user profile ([aeaf795](https://github.com/RSSNext/follow/commit/aeaf79522be23abb64ae940a4377a92448dfd923)) * update version toast ([25e7cea](https://github.com/RSSNext/follow/commit/25e7cea3c139335960c3de2922cf0ffa9c9c6c39)), closes [#1450](https://github.com/RSSNext/follow/issues/1450) +* update zh-CN translations and remove unused setting ([#2132](https://github.com/RSSNext/follow/issues/2132)) ([0dcfde5](https://github.com/RSSNext/follow/commit/0dcfde5ea40d688eaf8acc9f15f7a298421a12f0)) * **upgrade:** changelog container should w-full ([1551bf6](https://github.com/RSSNext/follow/commit/1551bf648c511cbf615f515b2e2d93077b0e42b4)) * use external link for login button ([d1975c2](https://github.com/RSSNext/follow/commit/d1975c20217290b2b7dc39866826c99f803dbd57)) * use replace for discover form navigation ([63602f2](https://github.com/RSSNext/follow/commit/63602f266390781a3552a14eefa35455f19c92b6)) @@ -369,6 +380,8 @@ * **app:** support cache limit and clean cache ([dee294d](https://github.com/RSSNext/follow/commit/dee294dda589ca9e2eaee8a7e55197635b6323c7)) * bigger font size in mobile ([3e9a92d](https://github.com/RSSNext/follow/commit/3e9a92d16a80139c851671748367f009a3af4de9)) * bring rehypeUrlToAnchor back ([6f0cc4d](https://github.com/RSSNext/follow/commit/6f0cc4d566c9f2f552f0fedf1a8e570a817e0f56)), closes [#1373](https://github.com/RSSNext/follow/issues/1373) +* copy button for ai summary ([b3ee572](https://github.com/RSSNext/follow/commit/b3ee5727e29d809092a8f4631d3e13c8d81095b9)) +* copy profile email ([1e12588](https://github.com/RSSNext/follow/commit/1e1258811163d72ae36f29714eef5d50834c348b)) * customizable columns for masonry view, closed [#1749](https://github.com/RSSNext/follow/issues/1749) ([0e0ce84](https://github.com/RSSNext/follow/commit/0e0ce843235f01f33f4c5b9708aa67dac5901b46)) * discover rsshub card background use single color ([7eeea5e](https://github.com/RSSNext/follow/commit/7eeea5e694c142803a37564ef8886d4fc4d2dab4)) * **discover:** enhance RSSHub recommendations with filters ([#1481](https://github.com/RSSNext/follow/issues/1481)) ([eb70126](https://github.com/RSSNext/follow/commit/eb70126b8283b6e0b246f86751e588a37cb34902)) @@ -376,6 +389,7 @@ * dnd ([#1471](https://github.com/RSSNext/follow/issues/1471)) ([c9333d5](https://github.com/RSSNext/follow/commit/c9333d5004c170955ab96b9f7f5a2825e1042271)) * dynamic auth providers ([b031ce5](https://github.com/RSSNext/follow/commit/b031ce57b8d7835c15fd9eecc6f99eaf50a368d1)) * email verification ([3497623](https://github.com/RSSNext/follow/commit/3497623b853f8321ed81788e32d43846be6d6135)) +* email verification ([d4905fd](https://github.com/RSSNext/follow/commit/d4905fd0082b41de3f76afa597d67030761a4ae8)) * entry image gallery modal ([e0d3e17](https://github.com/RSSNext/follow/commit/e0d3e17da4ee17217d7b78871b546f43af87d893)) * export database ([85b4502](https://github.com/RSSNext/follow/commit/85b4502f9c113b8de73ccf4a167aa514a3c149ea)) * **external:** move `login` and `redirect` route to external ([7916803](https://github.com/RSSNext/follow/commit/791680332d5e1c2c52eda792dd7ff69281f25adb)) @@ -388,9 +402,11 @@ * **i18n:** added multiple text translations (zh-TW) ([#1621](https://github.com/RSSNext/follow/issues/1621)) ([a15f23a](https://github.com/RSSNext/follow/commit/a15f23a5c7bb697c75c7fecf8603612b2584baa8)) * **i18n:** discover categories and mark all read undo button ([#1506](https://github.com/RSSNext/follow/issues/1506)) ([06bdf6c](https://github.com/RSSNext/follow/commit/06bdf6cfb90b03e1624189ebc05a389495e8bedc)) * **i18n:** translations (zh-TW) ([#1942](https://github.com/RSSNext/follow/issues/1942)) ([fea74b4](https://github.com/RSSNext/follow/commit/fea74b47829921822e0c701ea36ab1d4ac1e2083)) +* **i18n:** translations (zh-TW) ([#2166](https://github.com/RSSNext/follow/issues/2166)) ([45e37a0](https://github.com/RSSNext/follow/commit/45e37a07e8eee65ae5b02b524b5e09185c804c08)) * **icon:** use gradient fallback background ([e827002](https://github.com/RSSNext/follow/commit/e8270025e469d9a0463d267d3da591a2991951bd)) * image zoom ([1e47ba2](https://github.com/RSSNext/follow/commit/1e47ba25671000408e69fc3bae2c4626d0bd664e)), closes [#1183](https://github.com/RSSNext/follow/issues/1183) * **infra:** electron app can hot update renderer layer ([#1209](https://github.com/RSSNext/follow/issues/1209)) ([ca4751a](https://github.com/RSSNext/follow/commit/ca4751acd275579614a477d133ed643fca3fbf1a)) +* integrate react-query for fetching unread feed items by view ([d4dd4fb](https://github.com/RSSNext/follow/commit/d4dd4fb68d5a1cebdaeeea1aff88c2fbe2fba5e9)) * **integration:** add outline integration ([#1229](https://github.com/RSSNext/follow/issues/1229)) ([0d0266b](https://github.com/RSSNext/follow/commit/0d0266b25189a74efdc63ce0062f3a379d4a0729)) * **integration:** Add readeck integration ([#1972](https://github.com/RSSNext/follow/issues/1972)) ([1ce3f5b](https://github.com/RSSNext/follow/commit/1ce3f5b8ba95b16ad1b12e702027cede3590491a)) * List delete add secondary confirmation ([#1254](https://github.com/RSSNext/follow/issues/1254)) ([14f2bac](https://github.com/RSSNext/follow/commit/14f2bac8f3ba7e8f2dbd3958a4394c5b11ea2537)) @@ -409,10 +425,12 @@ * **obsidian:** use readability content when available ([b4a3197](https://github.com/RSSNext/follow/commit/b4a3197e6ebd39bb259a6bc67f408569354a205e)) * optimize action pages ([a10b78a](https://github.com/RSSNext/follow/commit/a10b78a79ce836bfbd5b40e9021c30dfef64a23e)) * prefer origin addresses for content images ([d4d4345](https://github.com/RSSNext/follow/commit/d4d43451dec839b64239e1835b2ac1a1aa2478be)) +* re-design pwa updated notice ([4947082](https://github.com/RSSNext/follow/commit/49470821dca772a040b2b845c3c019f057a6047c)) * **reader:** support custom css, fixed [#256](https://github.com/RSSNext/follow/issues/256) ([b251fa9](https://github.com/RSSNext/follow/commit/b251fa9421417c75d71707c8f08850f2cc902e1a)) * redesign login modal ([77dd6ce](https://github.com/RSSNext/follow/commit/77dd6ce2f97ea659def1104119085db3201ec029)) * reduce the size of the list in feed list ([e9f7ff3](https://github.com/RSSNext/follow/commit/e9f7ff31768db6bcc269c9becfb253315386baa1)) * register or login with email and password ([#2075](https://github.com/RSSNext/follow/issues/2075)) ([ae0dc2c](https://github.com/RSSNext/follow/commit/ae0dc2c30dd62d6ef4f87239298ec89c47c55861)) +* register or login with email and password ([#2075](https://github.com/RSSNext/follow/issues/2075)) ([7a6edb4](https://github.com/RSSNext/follow/commit/7a6edb4575ce80310e2f3977a03e363b363a6387)) * remove DISABLE_PERSONAL_DAILY_POWER ([064f8f5](https://github.com/RSSNext/follow/commit/064f8f524398a451d46e88b2d8b604f807d4ea96)) * remove entries total and remaining to reduce big queries ([df343b1](https://github.com/RSSNext/follow/commit/df343b10d0745a239e3b15fe11c1d3b2cd9ac3bb)) * remove entryCount in feed claim list ([723cc43](https://github.com/RSSNext/follow/commit/723cc43891e3fe7e3ce8c09a422df9fdd42577be)) @@ -422,6 +440,7 @@ * reset feed ([#1419](https://github.com/RSSNext/follow/issues/1419)) ([9066758](https://github.com/RSSNext/follow/commit/9066758c322b8b31c1a9a137be017283fa92bea8)) * separate packaging for macOS x64 and arm64 architectures ([#1389](https://github.com/RSSNext/follow/issues/1389)) ([3e8de30](https://github.com/RSSNext/follow/commit/3e8de308a3b3a07c58ab3fd7d26aad5aa2328c99)) * set default unreadOnly to true ([8c8c765](https://github.com/RSSNext/follow/commit/8c8c765ff518b6ccc6457959fdf3fe8a4ddeee22)) +* **settings:** clean web app service worker cache ([8ad40c0](https://github.com/RSSNext/follow/commit/8ad40c004ef2bf8924e2362729f42be83c09d102)) * show progress in searching ([e592e97](https://github.com/RSSNext/follow/commit/e592e97c9e0e6da8451c09b9b5f27612e6dbb971)), closes [#1457](https://github.com/RSSNext/follow/issues/1457) * **social:** improve image gallery grid ([5dc6d9e](https://github.com/RSSNext/follow/commit/5dc6d9ee227fa714f39957ff6e744e3079a3e57d)) * support alway on top, fixed [#1740](https://github.com/RSSNext/follow/issues/1740) ([09df663](https://github.com/RSSNext/follow/commit/09df663a1c0b9a5ec5d30f2609a36b09c3e66e56)) @@ -453,6 +472,7 @@ * batch profile ([#1464](https://github.com/RSSNext/follow/issues/1464)) ([a3e9c0e](https://github.com/RSSNext/follow/commit/a3e9c0edc83628d9c0ceb998d95184c5fd635f6f)) * enable router navigation transition ([24e1aea](https://github.com/RSSNext/follow/commit/24e1aead5e9bbb98b59194166fa4fd07eb167353)) * **external:** hydrate auth providers ([e0661ff](https://github.com/RSSNext/follow/commit/e0661ff2b9cc76f1a86ba6f293c0c37b5ad8d789)) +* **external:** hydrate auth providers ([efcbecb](https://github.com/RSSNext/follow/commit/efcbecbd94dc7009364f59c8945d08dc3ecc47b0)) * lazy show action, reduce lcp time ([fcf775c](https://github.com/RSSNext/follow/commit/fcf775c976591258f83dea6e34ca3161b539b15e)) * modify some animations to CSS animations ([9ec3fe5](https://github.com/RSSNext/follow/commit/9ec3fe562bf3527ad4279a9a57c0b0bb5934dba1)) * optimize dnd re-render ([12bd659](https://github.com/RSSNext/follow/commit/12bd6594585fda9f7b83daad7f6eebae3059d985)) diff --git a/changelog/0.2.8.md b/changelog/0.2.8.md new file mode 100644 index 0000000000..e43fd4f47e --- /dev/null +++ b/changelog/0.2.8.md @@ -0,0 +1,9 @@ +# What's new in v0.2.8 + +## New Features + +- Register or Login with email and password + +## Improvements + +## Bug Fixes diff --git a/changelog/next.md b/changelog/next.md index d7f8b9d6fb..17888d80b6 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -2,8 +2,6 @@ ## New Features -- Register or Login with email and password - ## Improvements ## Bug Fixes diff --git a/package.json b/package.json index cb742c4513..ed7007f2ee 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Follow", "type": "module", - "version": "0.2.7-beta.1", + "version": "0.2.8-beta.0", "private": true, "packageManager": "pnpm@9.12.3", "description": "Follow your favorites in one inbox", @@ -177,5 +177,5 @@ ] }, "productName": "Follow", - "mainHash": "eff966f43f885985e456a48c734d2cabe9fe30723d6cdc45e2f8ba75cecd0c80" + "mainHash": "b2130db8e4fffd333fbc79e0bf9ddd4d93a5158289f61a36e968d791b6595c49" } From 2ecd89ffde7a24580fb879192abdfa7ff95c6988 Mon Sep 17 00:00:00 2001 From: Jerry Wong Date: Thu, 19 Dec 2024 16:51:11 +0800 Subject: [PATCH 36/39] feat(locales): enhance zh-HK translations (#2208) * feat: update zh-HK file * feat: add translation for "show more" feat: Improve the zh-hk file * feat: update zh-HK --- locales/app/zh-HK.json | 7 +++++++ locales/external/zh-HK.json | 27 ++++++++++++++++++++++++++- locales/settings/zh-HK.json | 14 ++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/locales/app/zh-HK.json b/locales/app/zh-HK.json index 8cd8211473..1722ee81b3 100644 --- a/locales/app/zh-HK.json +++ b/locales/app/zh-HK.json @@ -336,14 +336,19 @@ "sidebar.feed_actions.unfollow_feed_many": "取消追隨所有選取的訂閱源", "sidebar.feed_actions.unfollow_feed_many_confirm": "確認取消追隨所有選取的訂閱源?", "sidebar.feed_actions.unfollow_feed_many_warning": "警告:此操作將取消追隨所有選取的訂閱源,且無法還原。", + "sidebar.feed_column.context_menu.add_feeds_to_category": "移至分類", "sidebar.feed_column.context_menu.add_feeds_to_list": "添加訂閱源到清單", "sidebar.feed_column.context_menu.change_to_other_view": "切換到其他視圖", + "sidebar.feed_column.context_menu.create_category": "新增分類", "sidebar.feed_column.context_menu.delete_category": "刪除分類", "sidebar.feed_column.context_menu.delete_category_confirmation": "確定刪除分類 {{folderName}}?", "sidebar.feed_column.context_menu.mark_as_read": "標記為已讀", + "sidebar.feed_column.context_menu.new_category_modal.category_name": "分類名稱", + "sidebar.feed_column.context_menu.new_category_modal.create": "建立", "sidebar.feed_column.context_menu.rename_category": "重新命名分類", "sidebar.feed_column.context_menu.rename_category_error": "重命名分類失敗", "sidebar.feed_column.context_menu.rename_category_success": "分類已成功重命名", + "sidebar.feed_column.context_menu.title": "移至新分類", "sidebar.select_sort_method": "選擇排序方法", "signin.continue_with": "繼續使用 {{provider}}", "signin.sign_in_to": "登入至", @@ -368,6 +373,7 @@ "trending.user": "熱門使用者", "user_button.account": "帳號", "user_button.achievement": "成就", + "user_button.actions": "自動化", "user_button.download_desktop_app": "下載桌面應用程式", "user_button.log_out": "登出", "user_button.power": "Power", @@ -380,6 +386,7 @@ "user_profile.share": "分享", "user_profile.toggle_item_style": "切換條目樣式", "words.achievement": "成就", + "words.actions": "自動化", "words.add": "新增", "words.boost": "加成", "words.browser": "瀏覽器", diff --git a/locales/external/zh-HK.json b/locales/external/zh-HK.json index b35959ac6b..98959550d7 100644 --- a/locales/external/zh-HK.json +++ b/locales/external/zh-HK.json @@ -9,6 +9,8 @@ "feed.follower_other": "追隨者", "feed.followsAndFeeds": "在 {{appName}} 上有 {{subscriptionCount}} 個 {{subscriptionNoun}} 和 {{feedsCount}} 個 {{feedsNoun}}", "feed.followsAndReads": "在 {{appName}} 上有 {{subscriptionCount}} 個 {{subscriptionNoun}} 和 {{readCount}} 個 {{readNoun}}", + "feed.madeby": "製作者", + "feed.preview": "預覽", "feed.read_one": "閱讀", "feed.read_other": "閱讀數", "feed.view_feed_url": "查看鏈接", @@ -32,14 +34,37 @@ "invitation.getCodeMessage": "你可以通過以下方式獲取邀請碼:", "invitation.title": "邀請碼", "login.backToWebApp": "返回網頁應用程式", + "login.confirm_password.label": "確認密碼", "login.continueWith": "繼續使用 {{provider}}", + "login.email": "電子郵件", + "login.forget_password.description": "輸入與您的帳戶關聯的電子郵件地址,我們將向您發送一封有關如何重置密碼的電子郵件。", + "login.forget_password.label": "忘記密碼", + "login.forget_password.note": "忘記密碼?", + "login.forget_password.success": "電子郵件已成功發送", "login.logInTo": "登錄到", + "login.logInWithEmail": "使用電子郵件登錄", + "login.new_password.label": "新密碼", "login.openApp": "打開應用程式", + "login.or": "或", + "login.password": "密碼", "login.redirecting": "重定向中", + "login.register": "註冊", + "login.reset_password.description": "輸入新密碼並確認以重置密碼", + "login.reset_password.label": "重置密碼", + "login.reset_password.success": "密碼已成功重置", "login.signOut": "登出", + "login.signUp": "使用電子郵件註冊", + "login.submit": "提交", "login.welcomeTo": "歡迎來到", "redirect.continueInBrowser": "在瀏覽器中繼續", "redirect.instruction": "現在是時候打開 {{app_name}} 並安全地關閉此頁面。", "redirect.openApp": "打開 {{app_name}}", - "redirect.successMessage": "你已成功連接到 {{app_name}} 帳戶。" + "redirect.successMessage": "你已成功連接到 {{app_name}} 帳戶。", + "register.confirm_password": "確認密碼", + "register.email": "電子郵件", + "register.label": "建立 {{app_name}} 帳號", + "register.login": "登入", + "register.note": "已經有帳號了嗎?", + "register.password": "密碼", + "register.submit": "建立帳號" } diff --git a/locales/settings/zh-HK.json b/locales/settings/zh-HK.json index 98d6ca61c6..b1eeacab9e 100644 --- a/locales/settings/zh-HK.json +++ b/locales/settings/zh-HK.json @@ -101,12 +101,15 @@ "data_control.app_cache_limit.label": "應用程式快取限制", "data_control.clean_cache.button": "清除快取", "data_control.clean_cache.description": "清除應用程式快取以釋放空間", + "data_control.clean_cache.description_web": "清除網頁快取以釋放空間", "feeds.claimTips": "要認領你的 feed 並接收贊助,請右鍵點擊訂閱列表中的 feed,然後選擇「認領」。", "feeds.noFeeds": "沒有已認領的 feed", "feeds.tableHeaders.name": "名稱", "feeds.tableHeaders.subscriptionCount": "訂閱數", "feeds.tableHeaders.tipAmount": "收到的贊助", "general.app": "應用程式", + "general.auto_expand_long_social_media.description": "自動展開包含長篇文字的社交媒體內容", + "general.auto_expand_long_social_media.label": "展開長篇社交媒體內容", "general.auto_group.description": "自動按網站域名分組訂閱源", "general.auto_group.label": "自動分組", "general.cache": "快取", @@ -256,14 +259,25 @@ "lists.title": "標題", "lists.view": "視圖", "profile.avatar.label": "頭像", + "profile.change_password.label": "更改密碼", + "profile.confirm_password.label": "確認密碼", + "profile.current_password.label": "目前密碼", + "profile.email.label": "電子郵件", + "profile.email.send_verification": "發送驗證電子郵件", + "profile.email.unverified": "未經驗證", + "profile.email.verification_sent": "已發出電子郵件驗證", + "profile.email.verified": "已驗證", "profile.handle.description": "你的唯一識別符", "profile.handle.label": "識別符", "profile.name.description": "你的公開顯示名稱", "profile.name.label": "顯示名稱", + "profile.new_password.label": "新密碼", + "profile.reset_password_mail_sent": "重設密碼郵件已發送", "profile.sidebar_title": "個人資料", "profile.submit": "提交", "profile.title": "個人資料設置", "profile.updateSuccess": "個人資料已更新", + "profile.update_password_success": "密碼已更新", "titles.about": "關於", "titles.actions": "動作", "titles.appearance": "外觀", From acc628ade17900f0dd9ab3c7552b85cff3a4c991 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Thu, 19 Dec 2024 20:56:55 +0800 Subject: [PATCH 37/39] chore: bring credential provider back --- apps/renderer/src/modules/auth/LoginModalContent.tsx | 2 +- packages/shared/src/hono.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/renderer/src/modules/auth/LoginModalContent.tsx b/apps/renderer/src/modules/auth/LoginModalContent.tsx index 0bc306ef84..885cdc9d03 100644 --- a/apps/renderer/src/modules/auth/LoginModalContent.tsx +++ b/apps/renderer/src/modules/auth/LoginModalContent.tsx @@ -79,7 +79,7 @@ export const LoginModalContent = (props: LoginModalContentProps) => { const extraProviders = useMemo(() => { if (!authProviders) return [] return Object.entries(authProviders) - .filter(([key]) => !defaultProviders[key] && key !== "credential") + .filter(([key]) => !defaultProviders[key]) .map(([_, provider]) => provider) }, [authProviders]) diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts index 327785c918..b698d913e5 100644 --- a/packages/shared/src/hono.ts +++ b/packages/shared/src/hono.ts @@ -9016,18 +9016,15 @@ declare const auth: { google: { clientId: string; clientSecret: string; - redirectURI: string; }; github: { clientId: string; clientSecret: string; - redirectURI: string; }; apple: { enabled: boolean; clientId: string; clientSecret: string; - redirectURI: string; }; }; emailAndPassword: { @@ -9045,7 +9042,7 @@ declare const auth: { token: string; }): Promise; }; - plugins: ({ + plugins: (better_auth.BetterAuthPlugin | { id: "custom-session"; endpoints: { getSession: { From 8a666e5f1bf2a38f52463b569f6d627129f8812b Mon Sep 17 00:00:00 2001 From: DIYgod Date: Thu, 19 Dec 2024 21:25:42 +0800 Subject: [PATCH 38/39] feat: update text --- apps/main/src/tipc/app.ts | 1 - apps/renderer/src/modules/settings/tabs/about.tsx | 6 ++---- locales/settings/ar-DZ.json | 1 - locales/settings/ar-IQ.json | 1 - locales/settings/ar-KW.json | 1 - locales/settings/ar-MA.json | 1 - locales/settings/ar-SA.json | 1 - locales/settings/ar-TN.json | 1 - locales/settings/de.json | 1 - locales/settings/en.json | 2 +- locales/settings/es.json | 1 - locales/settings/fi.json | 1 - locales/settings/fr.json | 1 - locales/settings/it.json | 1 - locales/settings/ja.json | 1 - locales/settings/ko.json | 1 - locales/settings/pt.json | 1 - locales/settings/ru.json | 1 - locales/settings/tr.json | 1 - locales/settings/zh-CN.json | 1 - locales/settings/zh-HK.json | 1 - locales/settings/zh-TW.json | 1 - 22 files changed, 3 insertions(+), 25 deletions(-) diff --git a/apps/main/src/tipc/app.ts b/apps/main/src/tipc/app.ts index 991f5cf586..c8de0c2794 100644 --- a/apps/main/src/tipc/app.ts +++ b/apps/main/src/tipc/app.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/require-await */ import fs from "node:fs" import fsp from "node:fs/promises" import path from "node:path" diff --git a/apps/renderer/src/modules/settings/tabs/about.tsx b/apps/renderer/src/modules/settings/tabs/about.tsx index 4382bf3190..9ff150eb29 100644 --- a/apps/renderer/src/modules/settings/tabs/about.tsx +++ b/apps/renderer/src/modules/settings/tabs/about.tsx @@ -3,7 +3,7 @@ import { Button } from "@follow/components/ui/button/index.js" import { styledButtonVariant } from "@follow/components/ui/button/variants.js" import { Divider } from "@follow/components/ui/divider/index.js" import { getCurrentEnvironment } from "@follow/utils/environment" -import PKG, { license, repository } from "@pkg" +import PKG, { repository } from "@pkg" import { useQuery } from "@tanstack/react-query" import { Trans, useTranslation } from "react-i18next" @@ -62,9 +62,7 @@ export const SettingAbout = () => {
-

- {t("about.licenseInfo", { appName: APP_NAME, license })} -

+

{t("about.licenseInfo", { appName: APP_NAME })}

فتح مشكلة على GitHub الخاص بنا.", "about.iconLibrary": "مكتبة الأيقونات المستخدمة محمية بحقوق الطبع والنشر من قبل ولا يمكن إعادة توزيعها.", - "about.licenseInfo": "{{appName}} هو وسيظل دائمًا مشروعًا مجانيًا ومفتوح المصدر. وهو مرخص بموجب {{license}}.", "about.sidebar_title": "حول", "about.socialMedia": "وسائل التواصل الاجتماعي", "actions.actionName": "الإجراء {{number}}", diff --git a/locales/settings/ar-IQ.json b/locales/settings/ar-IQ.json index c9eda916e6..755a74751d 100644 --- a/locales/settings/ar-IQ.json +++ b/locales/settings/ar-IQ.json @@ -2,7 +2,6 @@ "about.changelog": "سجل التغييرات", "about.feedbackInfo": "{{appName}} ({{commitSha}}) في المراحل الأولى من التطوير. إذا كانت لديك أي ملاحظات أو اقتراحات، فلا تتردد في فتح مشكلة على GitHub الخاص بنا.", "about.iconLibrary": "مكتبة الأيقونات المستخدمة محمية بحقوق الطبع والنشر من قبل ولا يمكن إعادة توزيعها.", - "about.licenseInfo": "{{appName}} هو وسيظل دائمًا مشروعًا مجانيًا ومفتوح المصدر. وهو مرخص بموجب {{license}}.", "about.sidebar_title": "حول", "about.socialMedia": "وسائل التواصل الاجتماعي", "actions.actionName": "الإجراء {{number}}", diff --git a/locales/settings/ar-KW.json b/locales/settings/ar-KW.json index ca8f73268a..53983e9f9c 100644 --- a/locales/settings/ar-KW.json +++ b/locales/settings/ar-KW.json @@ -2,7 +2,6 @@ "about.changelog": "سجل التغييرات", "about.feedbackInfo": "{{appName}} ({{commitSha}}) في المراحل الأولى من التطوير. إذا كانت لديك أي ملاحظات أو اقتراحات، لا تتردد في فتح مشكلة على GitHub الخاص بنا.", "about.iconLibrary": "مكتبة الأيقونات المستخدمة محمية بحقوق الطبع والنشر بواسطة ولا يمكن إعادة توزيعها.", - "about.licenseInfo": "{{appName}} هو وسيظل دائمًا مشروعًا مجانيًا ومفتوح المصدر. وهو مرخص بموجب {{license}}.", "about.sidebar_title": "حول", "about.socialMedia": "وسائل التواصل الاجتماعي", "actions.actionName": "الإجراء {{number}}", diff --git a/locales/settings/ar-MA.json b/locales/settings/ar-MA.json index 8d4a10c5ad..d938e95330 100644 --- a/locales/settings/ar-MA.json +++ b/locales/settings/ar-MA.json @@ -2,7 +2,6 @@ "about.changelog": "سجل التغييرات", "about.feedbackInfo": "{{appName}} ({{commitSha}}) مازال فالمراحل الأولية ديال التطوير. إلى كان عندك شي ملاحظات ولا اقتراحات، مرحبا بك تفتح موضوع على GitHub ديالنا.", "about.iconLibrary": "المكتبة ديال الأيقونات مستعملة محفوظة بحقوق الطبع والنشر من طرف وما يمكنش توزع من جديد.", - "about.licenseInfo": "{{appName}} هو غادي يبقى دائما مشروع مجاني ومفتوح المصدر. مرخص تحت {{license}}.", "about.sidebar_title": "حول", "about.socialMedia": "وسائل التواصل الاجتماعي", "actions.actionName": "الإجراء {{number}}", diff --git a/locales/settings/ar-SA.json b/locales/settings/ar-SA.json index 5e9c8faec3..610a53fe0f 100644 --- a/locales/settings/ar-SA.json +++ b/locales/settings/ar-SA.json @@ -2,7 +2,6 @@ "about.changelog": "سجل التغييرات", "about.feedbackInfo": "{{appName}} ({{commitSha}}) في مراحل التطوير المبكرة. إذا كان لديك أي ملاحظات أو اقتراحات، لا تتردد في فتح مشكلة على GitHub الخاص بنا.", "about.iconLibrary": "مكتبة الأيقونات المستخدمة محمية بحقوق النشر بواسطة ولا يمكن إعادة توزيعها.", - "about.licenseInfo": "{{appName}} هو مشروع مجاني ومفتوح المصدر وسيظل دائمًا كذلك. يتم ترخيصه بموجب {{license}}.", "about.sidebar_title": "حول", "about.socialMedia": "وسائل التواصل الاجتماعي", "actions.actionName": "الإجراء {{number}}", diff --git a/locales/settings/ar-TN.json b/locales/settings/ar-TN.json index 9b231327b0..eb4324f52e 100644 --- a/locales/settings/ar-TN.json +++ b/locales/settings/ar-TN.json @@ -2,7 +2,6 @@ "about.changelog": "سجل التغييرات", "about.feedbackInfo": "{{appName}} ({{commitSha}}) لا يزال في المراحل الأولى من التطوير. إذا كان لديك أي ملاحظات أو اقتراحات، لا تتردد في فتح تذكرة على GitHub الخاص بنا.", "about.iconLibrary": "مكتبة الأيقونات المستخدمة محمية بحقوق الطبع والنشر بواسطة ولا يمكن إعادة توزيعها.", - "about.licenseInfo": "{{appName}} هو وسيظل دائمًا مشروعًا مجانيًا ومفتوح المصدر. تم ترخيصه بموجب {{license}}.", "about.sidebar_title": "حول", "about.socialMedia": "وسائل التواصل الاجتماعي", "actions.actionName": "الإجراء {{number}}", diff --git a/locales/settings/de.json b/locales/settings/de.json index c7270e3e50..fbdfd2ac10 100644 --- a/locales/settings/de.json +++ b/locales/settings/de.json @@ -2,7 +2,6 @@ "about.changelog": "Änderungsprotokoll", "about.feedbackInfo": "{{appName}} ({{commitSha}}) befindet sich in den frühen Entwicklungsstadien. Wenn Sie Feedback oder Vorschläge haben, können Sie gerne ein Issue auf GitHub eröffnen .", "about.iconLibrary": "Die verwendete Icon-Bibliothek ist urheberrechtlich geschützt von und darf nicht weiterverteilt werden.", - "about.licenseInfo": "{{appName}} ist und wird immer ein kostenloses und Open-Source-Projekt sein. Es steht unter der Lizenz {{license}}.", "about.sidebar_title": "Über", "about.socialMedia": "Soziale Medien", "actions.actionName": "Aktion {{number}}", diff --git a/locales/settings/en.json b/locales/settings/en.json index 10fd23a90f..4bb8713097 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -2,7 +2,7 @@ "about.changelog": "Changelog", "about.feedbackInfo": "{{appName}} ({{commitSha}}) is in the early stages of development. If you have any feedback or suggestions, please feel free to open an issue on our GitHub.", "about.iconLibrary": "The icon library used is copyrighted by and cannot be redistributed.", - "about.licenseInfo": "{{appName}} is and will always be a free and open-source project. It is licensed under {{license}}.", + "about.licenseInfo": "Copyright © 2024 {{appName}}. All rights reserved.", "about.sidebar_title": "About", "about.socialMedia": "Social Media", "actions.actionName": "Action {{number}}", diff --git a/locales/settings/es.json b/locales/settings/es.json index 106fdd1b9a..da38570723 100644 --- a/locales/settings/es.json +++ b/locales/settings/es.json @@ -2,7 +2,6 @@ "about.changelog": "Registro de cambios", "about.feedbackInfo": "{{appName}} ({{commitSha}}) está en las primeras etapas de desarrollo. Si tienes algún comentario o sugerencia, no dudes en abrir un issue en nuestro GitHub.", "about.iconLibrary": "La biblioteca de iconos utilizada está protegida por derechos de autor por y no puede ser redistribuida.", - "about.licenseInfo": "{{appName}} es y siempre será un proyecto gratuito y de código abierto. Está licenciado bajo {{license}}.", "about.sidebar_title": "Acerca de", "about.socialMedia": "Redes Sociales", "actions.actionName": "Acción {{number}}", diff --git a/locales/settings/fi.json b/locales/settings/fi.json index 538fcd2523..0a90cf63bf 100644 --- a/locales/settings/fi.json +++ b/locales/settings/fi.json @@ -2,7 +2,6 @@ "about.changelog": "Muutosloki", "about.feedbackInfo": "{{appName}} ({{commitSha}}) on kehityksen alkuvaiheessa. Jos sinulla on palautetta tai ehdotuksia, voit vapaasti avata ongelmaraportin GitHubissamme.", "about.iconLibrary": "Käytetty kuvakekirjasto on tekijänoikeuden alainen , eikä sitä saa levittää uudelleen.", - "about.licenseInfo": "{{appName}} on ja tulee aina olemaan ilmainen ja avoimen lähdekoodin projekti. Se on lisensoitu {{license}}-lisenssillä.", "about.sidebar_title": "Tietoa", "about.socialMedia": "Sosiaalinen media", "actions.actionName": "Toiminto {{number}}", diff --git a/locales/settings/fr.json b/locales/settings/fr.json index 7d8fbad9ad..d6ae365d6f 100644 --- a/locales/settings/fr.json +++ b/locales/settings/fr.json @@ -2,7 +2,6 @@ "about.changelog": "Journal des modifications", "about.feedbackInfo": "{{appName}} ({{commitSha}}) est en phase de développement précoce. Si vous avez des retours ou des suggestions, n'hésitez pas à ouvrir une issue sur notre GitHub.", "about.iconLibrary": "La bibliothèque d'icônes utilisée est protégée par les droits d'auteur de et ne peut être redistribuée.", - "about.licenseInfo": "{{appName}} est et sera toujours un projet libre et open-source. Il est sous licence {{license}}.", "about.sidebar_title": "À propos", "about.socialMedia": "Réseaux sociaux", "actions.actionName": "Action {{number}}", diff --git a/locales/settings/it.json b/locales/settings/it.json index a6410903ad..d457f1a35b 100644 --- a/locales/settings/it.json +++ b/locales/settings/it.json @@ -2,7 +2,6 @@ "about.changelog": "Registro modifiche", "about.feedbackInfo": "{{appName}} ({{commitSha}}) è nelle prime fasi di sviluppo. Se hai feedback o suggerimenti, sentiti libero di aprire un problema sul nostro GitHub.", "about.iconLibrary": "La libreria di icone utilizzata è protetta da copyright da e non può essere ridistribuita.", - "about.licenseInfo": "{{appName}} è e sarà sempre un progetto gratuito e open-source. È rilasciato sotto licenza {{license}}.", "about.sidebar_title": "Informazioni", "about.socialMedia": "Social Media", "actions.actionName": "Azione {{number}}", diff --git a/locales/settings/ja.json b/locales/settings/ja.json index 86f26675f4..78bfabcfae 100644 --- a/locales/settings/ja.json +++ b/locales/settings/ja.json @@ -2,7 +2,6 @@ "about.changelog": "変更履歴", "about.feedbackInfo": "{{appName}} ({{commitSha}}) は開発の初期段階にあります。フィードバックや提案があれば、気軽に GitHub で課題を報告してください 。", "about.iconLibrary": "使用されているアイコンライブラリは によって著作権が保護されており、再配布できません。", - "about.licenseInfo": "{{appName}} は、常に無料でオープンソースのプロジェクトです。{{license}}の下でライセンスされています。", "about.sidebar_title": "about", "about.socialMedia": "ソーシャルメディア", "actions.actionName": "アクション {{number}}", diff --git a/locales/settings/ko.json b/locales/settings/ko.json index 702b0d12b8..b231535c18 100644 --- a/locales/settings/ko.json +++ b/locales/settings/ko.json @@ -2,7 +2,6 @@ "about.changelog": "변경 내역", "about.feedbackInfo": "{{appName}} ({{commitSha}}) 는 개발 초기 단계에 있습니다. 피드백이나 제안이 있으시면 GitHub 에서 이슈를 열어주세요 .", "about.iconLibrary": "사용된 아이콘 라이브러리는 의 저작권으로 보호되며 재배포할 수 없습니다.", - "about.licenseInfo": "{{appName}}는 항상 무료 오픈 소스 프로젝트입니다. {{license}} 하에 라이선스가 부여됩니다.", "about.sidebar_title": "정보", "about.socialMedia": "소셜 미디어", "actions.actionName": "액션 {{number}}", diff --git a/locales/settings/pt.json b/locales/settings/pt.json index ca67c12297..7bc587b051 100644 --- a/locales/settings/pt.json +++ b/locales/settings/pt.json @@ -2,7 +2,6 @@ "about.changelog": "Histórico de Alterações", "about.feedbackInfo": "{{appName}} ({{commitSha}}) está nas fases iniciais de desenvolvimento. Se tiver algum feedback ou sugestão, fique à vontade para abrir uma issue no nosso GitHub.", "about.iconLibrary": "A biblioteca de ícones utilizada é protegida por direitos de autor por e não pode ser redistribuída.", - "about.licenseInfo": "{{appName}} é e sempre será um projeto gratuito e de código aberto. Está licenciado sob {{license}}.", "about.sidebar_title": "Sobre", "about.socialMedia": "Redes Sociais", "actions.actionName": "Ação {{number}}", diff --git a/locales/settings/ru.json b/locales/settings/ru.json index 973ac6d32c..e2d9c1a07f 100644 --- a/locales/settings/ru.json +++ b/locales/settings/ru.json @@ -2,7 +2,6 @@ "about.changelog": "Журнал изменений", "about.feedbackInfo": "{{appName}} ({{commitSha}}) находится на ранних этапах разработки. Если у вас есть отзывы или предложения, пожалуйста, откройте задачу на нашем GitHub.", "about.iconLibrary": "Библиотека иконок защищена авторскими правами и не может быть перераспределена.", - "about.licenseInfo": "{{appName}} всегда будет бесплатным и проектом с открытым исходным кодом. Лицензировано под {{license}}.", "about.sidebar_title": "О проекте", "about.socialMedia": "Социальные сети", "actions.actionName": "Действие {{number}}", diff --git a/locales/settings/tr.json b/locales/settings/tr.json index 1edaeb616a..d9f2478496 100644 --- a/locales/settings/tr.json +++ b/locales/settings/tr.json @@ -2,7 +2,6 @@ "about.changelog": "Değişiklik Günlüğü", "about.feedbackInfo": "{{appName}} ({{commitSha}}) geliştirmenin erken aşamalarındadır. Herhangi bir geri bildiriminiz veya öneriniz varsa, lütfen GitHub'ımızda bir konu açmaktan çekinmeyin.", "about.iconLibrary": "Kullanılan simge kütüphanesi tarafından telif hakkıyla korunmaktadır ve yeniden dağıtılamaz.", - "about.licenseInfo": "{{appName}} her zaman ücretsiz ve açık kaynaklı bir proje olacaktır. {{license}} lisansı altında lisanslanmıştır.", "about.sidebar_title": "Hakkında", "about.socialMedia": "Sosyal Medya", "actions.actionName": "Eylem {{number}}", diff --git a/locales/settings/zh-CN.json b/locales/settings/zh-CN.json index f9e9d2459a..660ec47ae4 100644 --- a/locales/settings/zh-CN.json +++ b/locales/settings/zh-CN.json @@ -2,7 +2,6 @@ "about.changelog": "更新日志", "about.feedbackInfo": "{{appName}}({{commitSha}})正处于开发的早期阶段。如果你有任何反馈或建议,请随时在我们的 GitHub 上提出 。", "about.iconLibrary": "使用的图标库受版权保护,版权所有者为 ,不得重新分发。", - "about.licenseInfo": "{{appName}} 现在和将来都是一个免费且开源的项目,开源协议为 {{license}}。", "about.sidebar_title": "关于", "about.socialMedia": "社交媒体", "actions.actionName": "规则 {{number}}", diff --git a/locales/settings/zh-HK.json b/locales/settings/zh-HK.json index b1eeacab9e..ff694b9ff3 100644 --- a/locales/settings/zh-HK.json +++ b/locales/settings/zh-HK.json @@ -2,7 +2,6 @@ "about.changelog": "更新日誌", "about.feedbackInfo": "{{appName}}({{commitSha}})仍處於開發初期階段。如果你有任何意見或建議,歡迎到我們的 GitHub 提交問題 。", "about.iconLibrary": "所使用的圖示庫受 版權保護,不能轉發。", - "about.licenseInfo": "{{appName}} 永遠會是一個免費和開源的項目,並根據 {{license}} 授權使用。", "about.sidebar_title": "關於", "about.socialMedia": "社交媒體", "actions.actionName": "動作 {{number}}", diff --git a/locales/settings/zh-TW.json b/locales/settings/zh-TW.json index 73b2b71b65..a43e056f44 100644 --- a/locales/settings/zh-TW.json +++ b/locales/settings/zh-TW.json @@ -2,7 +2,6 @@ "about.changelog": "更新日誌", "about.feedbackInfo": "{{appName}} ({{commitSha}}) 正處於開發早期階段。如果您有任何回饋或建議,請隨時在我們的 GitHub 上 提交問題 。", "about.iconLibrary": "所使用的圖標庫版權由 所有,不得重新分發。", - "about.licenseInfo": "{{appName}} 是且將永遠是一個免費且開源的項目。它遵循 {{license}} 授權。", "about.sidebar_title": "關於", "about.socialMedia": "社群媒體", "actions.actionName": "操作 {{number}}", From 626db9a7edf0d42208861c31c2083ab89a6e32c9 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Thu, 19 Dec 2024 22:22:13 +0800 Subject: [PATCH 39/39] fix: parse auth cookie --- apps/main/package.json | 1 + apps/main/src/index.ts | 23 ++++++++++++++--------- pnpm-lock.yaml | 22 +++++++++++++++------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/apps/main/package.json b/apps/main/package.json index 2dc29b6b59..4df5592b97 100644 --- a/apps/main/package.json +++ b/apps/main/package.json @@ -31,6 +31,7 @@ "@openpanel/web": "1.0.1", "@sentry/electron": "5.7.0", "builder-util-runtime": "9.2.10", + "cookie-es": "^1.2.2", "dompurify": "~3.2.2", "electron-context-menu": "4.0.4", "electron-log": "5.2.4", diff --git a/apps/main/src/index.ts b/apps/main/src/index.ts index af6922af38..a6ca124ba3 100644 --- a/apps/main/src/index.ts +++ b/apps/main/src/index.ts @@ -3,6 +3,7 @@ import { callWindowExpose } from "@follow/shared/bridge" import { APP_PROTOCOL } from "@follow/shared/constants" import { env } from "@follow/shared/env" import { imageRefererMatches, selfRefererMatches } from "@follow/shared/image" +import { parse } from "cookie-es" import { app, BrowserWindow, session } from "electron" import type { Cookie } from "electron/main" import squirrelStartup from "electron-squirrel-startup" @@ -194,16 +195,20 @@ function bootstrap() { if (ck && apiURL) { setBetterAuthSessionCookie(ck) - const cookie = atob(ck) - mainWindow.webContents.session.cookies.set({ - url: apiURL, - name: cookie.split("=")[0], - value: cookie.split("=")[1], - secure: true, - httpOnly: true, - domain: new URL(apiURL).hostname, - sameSite: "no_restriction", + const cookie = parse(atob(ck)) + Object.keys(cookie).forEach((name) => { + const value = cookie[name] + mainWindow.webContents.session.cookies.set({ + url: apiURL, + name, + value, + secure: true, + httpOnly: true, + domain: new URL(apiURL).hostname, + sameSite: "no_restriction", + }) }) + userId && (await callWindowExpose(mainWindow).clearIfLoginOtherAccount(userId)) mainWindow.reload() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4104e06493..52ec13a534 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,7 +105,7 @@ importers: version: 0.5.0(patch_hash=43niildbdafdxi7qfcwhpkkxwa) '@pengx17/electron-forge-maker-appimage': specifier: 1.2.1 - version: 1.2.1(patch_hash=vov3v67fgv3lrfz3n24bnubw4m)(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)) + version: 1.2.1(patch_hash=vov3v67fgv3lrfz3n24bnubw4m)(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) '@sentry/vite-plugin': specifier: 2.22.7 version: 2.22.7(encoding@0.1.13) @@ -310,6 +310,9 @@ importers: builder-util-runtime: specifier: 9.2.10 version: 9.2.10 + cookie-es: + specifier: ^1.2.2 + version: 1.2.2 dompurify: specifier: ~3.2.2 version: 3.2.2 @@ -5859,6 +5862,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} @@ -14566,11 +14572,11 @@ snapshots: pvtsutils: 1.3.6 tslib: 2.8.1 - '@pengx17/electron-forge-maker-appimage@1.2.1(patch_hash=vov3v67fgv3lrfz3n24bnubw4m)(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8))': + '@pengx17/electron-forge-maker-appimage@1.2.1(patch_hash=vov3v67fgv3lrfz3n24bnubw4m)(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8)': dependencies: '@electron-forge/maker-base': 7.6.0 '@electron-forge/shared-types': 7.6.0 - app-builder-lib: 24.13.3(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)) + app-builder-lib: 24.13.3(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) transitivePeerDependencies: - bluebird - dmg-builder @@ -16389,7 +16395,7 @@ snapshots: app-builder-bin@5.0.0-alpha.10: {} - app-builder-lib@24.13.3(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)): + app-builder-lib@24.13.3(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.2.1 @@ -16423,7 +16429,7 @@ snapshots: transitivePeerDependencies: - supports-color - app-builder-lib@25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)): + app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8): dependencies: '@develar/schema-utils': 2.6.5 '@electron/notarize': 2.5.0 @@ -17378,6 +17384,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie-es@1.2.2: {} + cookie@0.7.1: {} cookie@1.0.2: {} @@ -17785,7 +17793,7 @@ snapshots: dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): dependencies: - app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)) + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) builder-util: 25.1.7 builder-util-runtime: 9.2.10 fs-extra: 10.1.0 @@ -17910,7 +17918,7 @@ snapshots: electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8): dependencies: - app-builder-lib: 25.1.8(dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8))(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)) + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) archiver: 5.3.2 builder-util: 25.1.7 fs-extra: 10.1.0