From 2ebdc2baea17e6ab2d6fc9493e881393f5be37f2 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Mon, 16 Feb 2026 00:33:16 +0000 Subject: [PATCH 01/10] empty states --- .../public/locales/en-US/translation.json | 3 ++ .../src/components/common/recent-changes.tsx | 11 ++++--- .../src/components/ui/empty-state.module.css | 8 +++++ apps/client/src/components/ui/empty-state.tsx | 30 +++++++++++++++++++ .../page/tree/components/space-tree.tsx | 12 ++++++-- apps/client/src/pages/page/page.tsx | 26 ++++++++++++++-- 6 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 apps/client/src/components/ui/empty-state.module.css create mode 100644 apps/client/src/components/ui/empty-state.tsx diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index 7011de7c..e46dd2c8 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -357,6 +357,9 @@ "Multiple": "Multiple", "Turn into": "Turn into", "Text align": "Text align", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "Heading {{level}}", "Toggle title": "Toggle title", "Write anything. Enter \"/\" for commands": "Write anything. Enter \"/\" for commands", diff --git a/apps/client/src/components/common/recent-changes.tsx b/apps/client/src/components/common/recent-changes.tsx index 81fdc899..b0bdfec7 100644 --- a/apps/client/src/components/common/recent-changes.tsx +++ b/apps/client/src/components/common/recent-changes.tsx @@ -11,7 +11,8 @@ import PageListSkeleton from "@/components/ui/page-list-skeleton.tsx"; import { buildPageUrl } from "@/features/page/page.utils.ts"; import { formattedDate } from "@/lib/time.ts"; import { useRecentChangesQuery } from "@/features/page/queries/page-query.ts"; -import { IconFileDescription } from "@tabler/icons-react"; +import { IconFileDescription, IconFiles } from "@tabler/icons-react"; +import { EmptyState } from "@/components/ui/empty-state.tsx"; import { getSpaceUrl } from "@/lib/config.ts"; import { useTranslation } from "react-i18next"; import { getInitialsColor } from "@/lib/get-initials-color.ts"; @@ -85,8 +86,10 @@ export default function RecentChanges({ spaceId }: Props) { ) : ( - - {t("No pages yet")} - + ); } diff --git a/apps/client/src/components/ui/empty-state.module.css b/apps/client/src/components/ui/empty-state.module.css new file mode 100644 index 00000000..21d0b810 --- /dev/null +++ b/apps/client/src/components/ui/empty-state.module.css @@ -0,0 +1,8 @@ +.root { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + text-align: center; +} diff --git a/apps/client/src/components/ui/empty-state.tsx b/apps/client/src/components/ui/empty-state.tsx new file mode 100644 index 00000000..dc016beb --- /dev/null +++ b/apps/client/src/components/ui/empty-state.tsx @@ -0,0 +1,30 @@ +import { Stack, Text } from "@mantine/core"; +import { type TablerIcon } from "@tabler/icons-react"; +import { ReactNode } from "react"; +import classes from "./empty-state.module.css"; + +type EmptyStateProps = { + icon: TablerIcon; + title: string; + description?: string; + action?: ReactNode; +}; + +export function EmptyState({ icon: Icon, title, description, action }: EmptyStateProps) { + return ( +
+ + + + {title} + + {description && ( + + {description} + + )} + {action} + +
+ ); +} diff --git a/apps/client/src/features/page/tree/components/space-tree.tsx b/apps/client/src/features/page/tree/components/space-tree.tsx index f6d1208b..0103c93c 100644 --- a/apps/client/src/features/page/tree/components/space-tree.tsx +++ b/apps/client/src/features/page/tree/components/space-tree.tsx @@ -16,7 +16,7 @@ import { import { useEffect, useRef, useState } from "react"; import { Link, useParams } from "react-router-dom"; import classes from "@/features/page/tree/styles/tree.module.css"; -import { ActionIcon, Box, Menu, rem } from "@mantine/core"; +import { ActionIcon, Box, Menu, rem, Text } from "@mantine/core"; import { IconArrowRight, IconChevronDown, @@ -82,6 +82,7 @@ interface SpaceTreeProps { const openTreeNodesAtom = atom({}); export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { + const { t } = useTranslation(); const { pageSlug } = useParams(); const { data, setData, controllers } = useTreeMutation>(spaceId); @@ -231,11 +232,18 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) { }; }, [setTreeApi]); + const filteredData = data.filter((node) => node?.spaceId === spaceId); + return (
+ {isDataLoaded && filteredData.length === 0 && ( + + {t("No pages yet")} + + )} {isRootReady && rootElement.current && ( node?.spaceId === spaceId)} + data={filteredData} disableDrag={readOnly} disableDrop={readOnly} disableEdit={readOnly} diff --git a/apps/client/src/pages/page/page.tsx b/apps/client/src/pages/page/page.tsx index a1dfee6f..b24348db 100644 --- a/apps/client/src/pages/page/page.tsx +++ b/apps/client/src/pages/page/page.tsx @@ -13,6 +13,10 @@ import { } from "@/features/space/permissions/permissions.type.ts"; import { useTranslation } from "react-i18next"; import React from "react"; +import { EmptyState } from "@/components/ui/empty-state.tsx"; +import { IconFileOff } from "@tabler/icons-react"; +import { Button } from "@mantine/core"; +import { Link } from "react-router-dom"; const MemoizedFullEditor = React.memo(FullEditor); const MemoizedPageHeader = React.memo(PageHeader); @@ -39,9 +43,27 @@ export default function Page() { if (isError || !page) { if ([401, 403, 404].includes(error?.["status"])) { - return
{t("Page not found")}
; + return ( + + {t("Go to homepage")} + + } + /> + ); } - return
{t("Error fetching page data.")}
; + return ( + + ); } if (!space) { From 00b53286763cefe581465521ee2f05d3ec008fe9 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Mon, 16 Feb 2026 04:06:41 +0000 Subject: [PATCH 02/10] fix page error boundary --- apps/client/src/App.tsx | 9 +-------- apps/client/src/pages/page/page.tsx | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index e0df67a7..438ffde8 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -14,7 +14,6 @@ import AccountPreferences from "@/pages/settings/account/account-preferences.tsx import SpaceHome from "@/pages/space/space-home.tsx"; import PageRedirect from "@/pages/page/page-redirect.tsx"; import Layout from "@/components/layouts/global/layout.tsx"; -import { ErrorBoundary } from "react-error-boundary"; import InviteSignup from "@/pages/auth/invite-signup.tsx"; import ForgotPassword from "@/pages/auth/forgot-password.tsx"; import PasswordReset from "./pages/auth/password-reset"; @@ -84,13 +83,7 @@ export default function App() { } /> {t("Failed to load page. An error occurred.")}} - > - - - } + element={} /> diff --git a/apps/client/src/pages/page/page.tsx b/apps/client/src/pages/page/page.tsx index b24348db..449f1e15 100644 --- a/apps/client/src/pages/page/page.tsx +++ b/apps/client/src/pages/page/page.tsx @@ -14,9 +14,10 @@ import { import { useTranslation } from "react-i18next"; import React from "react"; import { EmptyState } from "@/components/ui/empty-state.tsx"; -import { IconFileOff } from "@tabler/icons-react"; +import { IconAlertTriangle, IconFileOff } from "@tabler/icons-react"; import { Button } from "@mantine/core"; import { Link } from "react-router-dom"; +import { ErrorBoundary } from "react-error-boundary"; const MemoizedFullEditor = React.memo(FullEditor); const MemoizedPageHeader = React.memo(PageHeader); @@ -26,6 +27,29 @@ export default function Page() { const { t } = useTranslation(); const { pageSlug } = useParams(); + return ( + ( + + {t("Try again")} + + } + /> + )} + > + + + ); +} + +function PageContent({ pageSlug }: { pageSlug: string | undefined }) { + const { t } = useTranslation(); + const { data: page, isLoading, From 0ce74d34defc170988cca7cded676ecb11a54f02 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Mon, 16 Feb 2026 04:11:19 +0000 Subject: [PATCH 03/10] env validation --- apps/server/package.json | 12 +- apps/server/src/ee | 2 +- .../environment/environment.validation.ts | 7 - pnpm-lock.yaml | 165 ++++++++++-------- 4 files changed, 99 insertions(+), 87 deletions(-) diff --git a/apps/server/package.json b/apps/server/package.json index 15f93001..59defad4 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -30,9 +30,9 @@ "test:e2e": "jest --config test/jest-e2e.json" }, "dependencies": { - "@ai-sdk/google": "^3.0.9", - "@ai-sdk/openai": "^3.0.11", - "@ai-sdk/openai-compatible": "^2.0.12", + "@ai-sdk/google": "^3.0.29", + "@ai-sdk/openai": "^3.0.29", + "@ai-sdk/openai-compatible": "^2.0.30", "@aws-sdk/client-s3": "3.982.0", "@aws-sdk/lib-storage": "3.982.0", "@aws-sdk/s3-request-presigner": "3.982.0", @@ -59,11 +59,11 @@ "@react-email/components": "1.0.7", "@react-email/render": "2.0.4", "@socket.io/redis-adapter": "^8.3.0", - "ai": "^6.0.37", - "ai-sdk-ollama": "^3.1.1", + "ai": "^6.0.86", + "ai-sdk-ollama": "^3.7.0", "bcrypt": "^6.0.0", "bullmq": "^5.65.0", - "cache-manager": "^6.4.3", + "cache-manager": "^7.2.8", "cheerio": "^1.1.2", "class-transformer": "^0.5.1", "class-validator": "^0.14.3", diff --git a/apps/server/src/ee b/apps/server/src/ee index 3b7c484e..71b4323d 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit 3b7c484eebf698d6696f83d415184525054fb75c +Subproject commit 71b4323d1b6ea3fbec061b0d31be33235d4ddbcd diff --git a/apps/server/src/integrations/environment/environment.validation.ts b/apps/server/src/integrations/environment/environment.validation.ts index ced01b5d..5f65d018 100644 --- a/apps/server/src/integrations/environment/environment.validation.ts +++ b/apps/server/src/integrations/environment/environment.validation.ts @@ -91,7 +91,6 @@ export class EnvironmentVariables { @ValidateIf((obj) => obj.SEARCH_DRIVER === 'typesense') TYPESENSE_URL: string; - @IsOptional() @ValidateIf((obj) => obj.SEARCH_DRIVER === 'typesense') @IsNotEmpty() @IsString() @@ -110,18 +109,14 @@ export class EnvironmentVariables { AI_DRIVER: string; @IsOptional() - @ValidateIf((obj) => obj.AI_DRIVER) @IsString() - @IsNotEmpty() AI_EMBEDDING_MODEL: string; - @IsOptional() @ValidateIf((obj) => obj.AI_EMBEDDING_DIMENSION) @IsIn(['768', '1024', '1536', '2000', '3072']) @IsString() AI_EMBEDDING_DIMENSION: string; - @IsOptional() @ValidateIf((obj) => obj.AI_DRIVER) @IsString() @IsNotEmpty() @@ -145,13 +140,11 @@ export class EnvironmentVariables { @IsUrl({ protocols: ['http', 'https'], require_tld: false }) OPENAI_API_URL: string; - @IsOptional() @ValidateIf((obj) => obj.AI_DRIVER && obj.AI_DRIVER === 'gemini') @IsString() @IsNotEmpty() GEMINI_API_KEY: string; - @IsOptional() @ValidateIf((obj) => obj.AI_DRIVER && obj.AI_DRIVER === 'ollama') @IsUrl({ protocols: ['http', 'https'], require_tld: false }) OLLAMA_API_URL: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bbb85a41..6e0d5da0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -442,14 +442,14 @@ importers: apps/server: dependencies: '@ai-sdk/google': - specifier: ^3.0.9 - version: 3.0.10(zod@4.3.6) + specifier: ^3.0.29 + version: 3.0.29(zod@4.3.6) '@ai-sdk/openai': - specifier: ^3.0.11 - version: 3.0.12(zod@4.3.6) + specifier: ^3.0.29 + version: 3.0.29(zod@4.3.6) '@ai-sdk/openai-compatible': - specifier: ^2.0.12 - version: 2.0.13(zod@4.3.6) + specifier: ^2.0.30 + version: 2.0.30(zod@4.3.6) '@aws-sdk/client-s3': specifier: 3.982.0 version: 3.982.0 @@ -529,11 +529,11 @@ importers: specifier: ^8.3.0 version: 8.3.0(socket.io-adapter@2.5.4) ai: - specifier: ^6.0.37 - version: 6.0.38(zod@4.3.6) + specifier: ^6.0.86 + version: 6.0.86(zod@4.3.6) ai-sdk-ollama: - specifier: ^3.1.1 - version: 3.1.1(ai@6.0.38(zod@4.3.6))(zod@4.3.6) + specifier: ^3.7.0 + version: 3.7.0(ai@6.0.86(zod@4.3.6))(zod@4.3.6) bcrypt: specifier: ^6.0.0 version: 6.0.0 @@ -541,8 +541,8 @@ importers: specifier: ^5.65.0 version: 5.65.0 cache-manager: - specifier: ^6.4.3 - version: 6.4.3 + specifier: ^7.2.8 + version: 7.2.8 cheerio: specifier: ^1.1.2 version: 1.1.2 @@ -781,38 +781,38 @@ packages: '@adobe/css-tools@4.3.3': resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==} - '@ai-sdk/gateway@3.0.16': - resolution: {integrity: sha512-OOY5CfRJiHvh/8np2vs1RQaCZ5hWv2qOeEmmeiABXK3gLQHUVnCO+1hhoLsZdHM5iElu6M407dAOfyvTsKJqcQ==} + '@ai-sdk/gateway@3.0.46': + resolution: {integrity: sha512-zH1UbNRjG5woOXXFOrVCZraqZuFTtmPvLardMGcgLkzpxKV0U3tAGoyWKSZ862H+eBJfI/Hf2yj/zzGJcCkycg==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/google@3.0.10': - resolution: {integrity: sha512-qd2EM9SlD7wWFrq036hwKsuAgkCVxQbwJzctszdmzPs9yUZg795/gHtZRpKItZhbyHSNWhAHmJwEgKjD+HOzuQ==} + '@ai-sdk/google@3.0.29': + resolution: {integrity: sha512-x0hcU10AA+i1ZUQHloGD5qXWsB+Y8qnxlmFUef6Ly4rB53MGVbQExkI9nOKiCO3mu2TGiiNoQMeKWSeQVLfRUA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai-compatible@2.0.13': - resolution: {integrity: sha512-DShpuHZ9wiy3QtxJ4/Uq5csLxgNgeA3w58isYhZ34pSod2cBlRmJl3EyQzxZ1HD8e6sQDa9fvc0cwF5/EugBMw==} + '@ai-sdk/openai-compatible@2.0.30': + resolution: {integrity: sha512-iTjumHf1/u4NhjXYFn/aONM2GId3/o7J1Lp5ql8FCbgIMyRwrmanR5xy1S3aaVkfTscuDvLTzWiy1mAbGzK3nQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai@3.0.12': - resolution: {integrity: sha512-zqLWEKuaKnjXhu7xCw1jgz/+yTbd3F7EtgU4T2Q8BAo8OJC5wZv14l+kwM7Jai7M1/2Y2T/zBkrfiIu+7NsvfQ==} + '@ai-sdk/openai@3.0.29': + resolution: {integrity: sha512-ugVTIVpuSLKTjzSPe1F1DWiblJT/lwrrHx0OZEKjpMk/EYP6j6VD/F7SJqM1dsqOJryeBCJWFbUzLNqc99PrMA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@4.0.8': - resolution: {integrity: sha512-ns9gN7MmpI8vTRandzgz+KK/zNMLzhrriiKECMt4euLtQFSBgNfydtagPOX4j4pS1/3KvHF6RivhT3gNQgBZsg==} + '@ai-sdk/provider-utils@4.0.15': + resolution: {integrity: sha512-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider@3.0.4': - resolution: {integrity: sha512-5KXyBOSEX+l67elrEa+wqo/LSsSTtrPj9Uoh3zMbe/ceQX4ucHI3b9nUEfNkGF3Ry1svv90widAt+aiKdIJasQ==} + '@ai-sdk/provider@3.0.8': + resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==} engines: {node: '>=18'} '@angular-devkit/core@19.1.7': @@ -1719,6 +1719,9 @@ packages: '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + '@cacheable/utils@2.3.4': + resolution: {integrity: sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==} + '@casl/ability@6.8.0': resolution: {integrity: sha512-Ipt4mzI4gSgnomFdaPjaLgY2MWuXqAEZLrU6qqWBB7khGiBBuuEp6ytYDnq09bRXqcjaeeHiaCvCGFbBA2SpvA==} @@ -2680,8 +2683,8 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@keyv/serialize@1.0.3': - resolution: {integrity: sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==} + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} '@langchain/core@1.1.18': resolution: {integrity: sha512-vwzbtHUSZaJONBA1n9uQedZPfyFFZ6XzTggTpR28n8tiIg7e1NC/5dvGW/lGtR1Du1VwV9DvDHA5/bOrLe6cVg==} @@ -5559,14 +5562,14 @@ packages: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} - ai-sdk-ollama@3.1.1: - resolution: {integrity: sha512-1rTgOGUsE8pR2ccg0Uz9lNJYzNUJKjtHDfUB5itoc2UnWZnbzQyqyxTUloCIWjwDkEzaWBiEJRLNvwF7a6j4VQ==} + ai-sdk-ollama@3.7.0: + resolution: {integrity: sha512-RtiOsAjfjykqtqp0vpnkkUe/lSlnZy7BEapuxZl6VnHwo8hq+DqRn35RpABoAI+eCBjzLioBcZk/msnhwfc8CA==} engines: {node: '>=22'} peerDependencies: - ai: ^6.0.27 + ai: ^6.0.80 - ai@6.0.38: - resolution: {integrity: sha512-X8AaZFrdsPO1RNCAQLsaWfmE/SL9zgsiIZN3XqEHs3jIZ7ycR5aQZRg5XpNtbLWJxKXzK2b1ZXLx13AFOjksSg==} + ai@6.0.86: + resolution: {integrity: sha512-U2W2LBCHA/pr0Ui7vmmsjBiLEzBbZF3yVHNy7Rbzn7IX+SvoQPFM5rN74hhfVzZoE8zBuGD4nLLk+j0elGacvQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -5873,9 +5876,6 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bullmq@5.65.0: resolution: {integrity: sha512-fyOcyf2ad4zrNmE18vdF/ie7DrW0TwhLt5e0DkqDxbRpDNiUdYqgp2QZJW2ntnUN08T2mDMC4deUUhF2UOAmeQ==} @@ -5883,8 +5883,8 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cache-manager@6.4.3: - resolution: {integrity: sha512-VV5eq/QQ5rIVix7/aICO4JyvSeEv9eIQuKL5iFwgM2BrcYoE0A/D1mNsAHJAsB0WEbNdBlKkn6Tjz6fKzh/cKQ==} + cache-manager@7.2.8: + resolution: {integrity: sha512-0HDaDLBBY/maa/LmUVAr70XUOwsiQD+jyzCBjmUErYZUKdMS9dT59PqW59PpVqfGM7ve6H0J6307JTpkCYefHQ==} call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} @@ -7246,6 +7246,10 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + hashery@1.4.0: + resolution: {integrity: sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==} + engines: {node: '>=20'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -7263,6 +7267,9 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -7858,6 +7865,10 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonrepair@3.13.2: + resolution: {integrity: sha512-Leuly0nbM4R+S5SVJk3VHfw1oxnlEK9KygdZvfUtEtTawNDyzB4qa1xWTmFt1aeoA7sXZkVTRuIixJ8bAvqVUg==} + hasBin: true + jsonwebtoken@9.0.3: resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} engines: {node: '>=12', npm: '>=6'} @@ -7886,8 +7897,8 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - keyv@5.3.3: - resolution: {integrity: sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==} + keyv@5.6.0: + resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} khroma@2.1.0: resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} @@ -10596,39 +10607,39 @@ snapshots: '@adobe/css-tools@4.3.3': {} - '@ai-sdk/gateway@3.0.16(zod@4.3.6)': + '@ai-sdk/gateway@3.0.46(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 3.0.4 - '@ai-sdk/provider-utils': 4.0.8(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.15(zod@4.3.6) '@vercel/oidc': 3.1.0 zod: 4.3.6 - '@ai-sdk/google@3.0.10(zod@4.3.6)': + '@ai-sdk/google@3.0.29(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 3.0.4 - '@ai-sdk/provider-utils': 4.0.8(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.15(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/openai-compatible@2.0.13(zod@4.3.6)': + '@ai-sdk/openai-compatible@2.0.30(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 3.0.4 - '@ai-sdk/provider-utils': 4.0.8(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.15(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/openai@3.0.12(zod@4.3.6)': + '@ai-sdk/openai@3.0.29(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 3.0.4 - '@ai-sdk/provider-utils': 4.0.8(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.15(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/provider-utils@4.0.8(zod@4.3.6)': + '@ai-sdk/provider-utils@4.0.15(zod@4.3.6)': dependencies: - '@ai-sdk/provider': 3.0.4 + '@ai-sdk/provider': 3.0.8 '@standard-schema/spec': 1.1.0 eventsource-parser: 3.0.6 zod: 4.3.6 - '@ai-sdk/provider@3.0.4': + '@ai-sdk/provider@3.0.8': dependencies: json-schema: 0.4.0 @@ -12031,6 +12042,11 @@ snapshots: '@braintree/sanitize-url@7.1.1': {} + '@cacheable/utils@2.3.4': + dependencies: + hashery: 1.4.0 + keyv: 5.6.0 + '@casl/ability@6.8.0': dependencies: '@ucast/mongo2js': 1.3.4 @@ -13071,9 +13087,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@keyv/serialize@1.0.3': - dependencies: - buffer: 6.0.3 + '@keyv/serialize@1.1.1': {} '@langchain/core@1.1.18(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.2.0(ws@8.19.0)(zod@4.3.6))': dependencies: @@ -16177,20 +16191,21 @@ snapshots: transitivePeerDependencies: - supports-color - ai-sdk-ollama@3.1.1(ai@6.0.38(zod@4.3.6))(zod@4.3.6): + ai-sdk-ollama@3.7.0(ai@6.0.86(zod@4.3.6))(zod@4.3.6): dependencies: - '@ai-sdk/provider': 3.0.4 - '@ai-sdk/provider-utils': 4.0.8(zod@4.3.6) - ai: 6.0.38(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.15(zod@4.3.6) + ai: 6.0.86(zod@4.3.6) + jsonrepair: 3.13.2 ollama: 0.6.3 transitivePeerDependencies: - zod - ai@6.0.38(zod@4.3.6): + ai@6.0.86(zod@4.3.6): dependencies: - '@ai-sdk/gateway': 3.0.16(zod@4.3.6) - '@ai-sdk/provider': 3.0.4 - '@ai-sdk/provider-utils': 4.0.8(zod@4.3.6) + '@ai-sdk/gateway': 3.0.46(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.15(zod@4.3.6) '@opentelemetry/api': 1.9.0 zod: 4.3.6 @@ -16562,11 +16577,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - bullmq@5.65.0: dependencies: cron-parser: 4.9.0 @@ -16581,9 +16591,10 @@ snapshots: bytes@3.1.2: {} - cache-manager@6.4.3: + cache-manager@7.2.8: dependencies: - keyv: 5.3.3 + '@cacheable/utils': 2.3.4 + keyv: 5.6.0 call-bind-apply-helpers@1.0.2: dependencies: @@ -18088,6 +18099,10 @@ snapshots: dependencies: has-symbols: 1.1.0 + hashery@1.4.0: + dependencies: + hookified: 1.15.1 + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -18102,6 +18117,8 @@ snapshots: dependencies: react-is: 16.13.1 + hookified@1.15.1: {} + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 @@ -18912,6 +18929,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonrepair@3.13.2: {} + jsonwebtoken@9.0.3: dependencies: jws: 4.0.1 @@ -18960,9 +18979,9 @@ snapshots: dependencies: json-buffer: 3.0.1 - keyv@5.3.3: + keyv@5.6.0: dependencies: - '@keyv/serialize': 1.0.3 + '@keyv/serialize': 1.1.1 khroma@2.1.0: {} From 92d5d0b2371464766942e205d7f348abae7036b6 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Mon, 16 Feb 2026 04:22:40 +0000 Subject: [PATCH 04/10] New Crowdin updates (#1950) * New translations --- apps/client/public/locales/de-DE/translation.json | 3 +++ apps/client/public/locales/es-ES/translation.json | 3 +++ apps/client/public/locales/fr-FR/translation.json | 3 +++ apps/client/public/locales/it-IT/translation.json | 3 +++ apps/client/public/locales/ja-JP/translation.json | 3 +++ apps/client/public/locales/ko-KR/translation.json | 3 +++ apps/client/public/locales/nl-NL/translation.json | 3 +++ apps/client/public/locales/pt-BR/translation.json | 3 +++ apps/client/public/locales/ru-RU/translation.json | 3 +++ apps/client/public/locales/uk-UA/translation.json | 3 +++ apps/client/public/locales/zh-CN/translation.json | 3 +++ 11 files changed, 33 insertions(+) diff --git a/apps/client/public/locales/de-DE/translation.json b/apps/client/public/locales/de-DE/translation.json index ede6ef70..0822a65e 100644 --- a/apps/client/public/locales/de-DE/translation.json +++ b/apps/client/public/locales/de-DE/translation.json @@ -357,6 +357,9 @@ "Multiple": "Mehrere", "Turn into": "In verwandeln", "Text align": "Text ausrichten", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "Überschrift {{level}}", "Toggle title": "Titel umschalten", "Write anything. Enter \"/\" for commands": "Schreiben Sie irgendetwas. Geben Sie \"/\" für Befehle ein", diff --git a/apps/client/public/locales/es-ES/translation.json b/apps/client/public/locales/es-ES/translation.json index be344ee4..b9d4f359 100644 --- a/apps/client/public/locales/es-ES/translation.json +++ b/apps/client/public/locales/es-ES/translation.json @@ -357,6 +357,9 @@ "Multiple": "Múltiple", "Turn into": "Convertir en", "Text align": "Alineación del texto", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "Encabezado {{level}}", "Toggle title": "Alternar título", "Write anything. Enter \"/\" for commands": "Escribe cualquier cosa. Ingresa \"/\" para comandos", diff --git a/apps/client/public/locales/fr-FR/translation.json b/apps/client/public/locales/fr-FR/translation.json index fca27390..77d5db91 100644 --- a/apps/client/public/locales/fr-FR/translation.json +++ b/apps/client/public/locales/fr-FR/translation.json @@ -357,6 +357,9 @@ "Multiple": "Multiple", "Turn into": "Transformer en", "Text align": "Alignement du texte", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "Titre {{level}}", "Toggle title": "Basculer le titre", "Write anything. Enter \"/\" for commands": "Écrivez n'importe quoi. Entrez \"/\" pour les commandes", diff --git a/apps/client/public/locales/it-IT/translation.json b/apps/client/public/locales/it-IT/translation.json index 50e07665..dc357932 100644 --- a/apps/client/public/locales/it-IT/translation.json +++ b/apps/client/public/locales/it-IT/translation.json @@ -357,6 +357,9 @@ "Multiple": "Multiplo", "Turn into": "Trasforma in", "Text align": "Allinea testo", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "Intestazione {{level}}", "Toggle title": "Attiva/disattiva titolo", "Write anything. Enter \"/\" for commands": "Scrivi qualcosa. Digita \"/\" per i comandi", diff --git a/apps/client/public/locales/ja-JP/translation.json b/apps/client/public/locales/ja-JP/translation.json index ee5d77fc..1c884add 100644 --- a/apps/client/public/locales/ja-JP/translation.json +++ b/apps/client/public/locales/ja-JP/translation.json @@ -357,6 +357,9 @@ "Multiple": "複数", "Turn into": "変換する", "Text align": "テキストの配置", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "見出し {{level}}", "Toggle title": "タイトルの表示/非表示を切り替える", "Write anything. Enter \"/\" for commands": "文字を入力するか、「/」でコマンドを呼び出します", diff --git a/apps/client/public/locales/ko-KR/translation.json b/apps/client/public/locales/ko-KR/translation.json index fb339bad..ba6a93eb 100644 --- a/apps/client/public/locales/ko-KR/translation.json +++ b/apps/client/public/locales/ko-KR/translation.json @@ -357,6 +357,9 @@ "Multiple": "복제", "Turn into": "변경하기", "Text align": "텍스트 정렬", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "제목 {{level}}", "Toggle title": "제목 토글", "Write anything. Enter \"/\" for commands": "아무거나 입력하세요. 명령어를 사용하려면 \"/\"를 입력하세요", diff --git a/apps/client/public/locales/nl-NL/translation.json b/apps/client/public/locales/nl-NL/translation.json index f8db0c36..3f0136fc 100644 --- a/apps/client/public/locales/nl-NL/translation.json +++ b/apps/client/public/locales/nl-NL/translation.json @@ -357,6 +357,9 @@ "Multiple": "Meerdere", "Turn into": "Omzetten naar", "Text align": "Tekstuitlijning", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "Kop {{level}}", "Toggle title": "Schakel titel in/uit", "Write anything. Enter \"/\" for commands": "Schrijf iets. Voer \"/\" in voor commando's", diff --git a/apps/client/public/locales/pt-BR/translation.json b/apps/client/public/locales/pt-BR/translation.json index 7164d5e4..5fb2000f 100644 --- a/apps/client/public/locales/pt-BR/translation.json +++ b/apps/client/public/locales/pt-BR/translation.json @@ -357,6 +357,9 @@ "Multiple": "Múltiplo", "Turn into": "Transformar em", "Text align": "Alinhar texto", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "Título {{level}}", "Toggle title": "Alternar título", "Write anything. Enter \"/\" for commands": "Escreva qualquer coisa. Digite \"/\" para comandos", diff --git a/apps/client/public/locales/ru-RU/translation.json b/apps/client/public/locales/ru-RU/translation.json index 6ec16a1b..4c50f9e7 100644 --- a/apps/client/public/locales/ru-RU/translation.json +++ b/apps/client/public/locales/ru-RU/translation.json @@ -357,6 +357,9 @@ "Multiple": "Несколько", "Turn into": "Преобразовать в", "Text align": "Выравнивание текста", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "Заголовок {{level}}", "Toggle title": "Переключить заголовок", "Write anything. Enter \"/\" for commands": "Начните писать. Введите \"/\" для списка команд", diff --git a/apps/client/public/locales/uk-UA/translation.json b/apps/client/public/locales/uk-UA/translation.json index 14b34816..84cc1428 100644 --- a/apps/client/public/locales/uk-UA/translation.json +++ b/apps/client/public/locales/uk-UA/translation.json @@ -357,6 +357,9 @@ "Multiple": "Декілька", "Turn into": "Перетворити", "Text align": "Вирівнювання тексту", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "Заголовок {{level}}", "Toggle title": "Перемкнути заголовок", "Write anything. Enter \"/\" for commands": "Почніть писати. Введіть \"/\" для списку команд", diff --git a/apps/client/public/locales/zh-CN/translation.json b/apps/client/public/locales/zh-CN/translation.json index d3877420..44cbe767 100644 --- a/apps/client/public/locales/zh-CN/translation.json +++ b/apps/client/public/locales/zh-CN/translation.json @@ -357,6 +357,9 @@ "Multiple": "多个", "Turn into": "变成", "Text align": "文本对齐", + "This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.", + "Go to homepage": "Go to homepage", + "Pages you create will show up here.": "Pages you create will show up here.", "Heading {{level}}": "{{level}} 级标题", "Toggle title": "切换标题", "Write anything. Enter \"/\" for commands": "开始编写内容,输入 \"/\" 以使用指令", From 0aeaa43112f5b9d808a9bf9b437db8247b39ff03 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Mon, 16 Feb 2026 19:48:19 +0000 Subject: [PATCH 05/10] feat: replace sharp with client-side icon resize (#1951) --- apps/client/package.json | 2 + .../services/attachment-service.ts | 44 ++- apps/server/package.json | 1 - .../src/core/attachment/attachment.utils.ts | 49 --- .../attachment/services/attachment.service.ts | 7 - pnpm-lock.yaml | 295 +----------------- 6 files changed, 61 insertions(+), 337 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index 2f653f43..617bf447 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -26,6 +26,7 @@ "@tanstack/react-query": "^5.90.17", "alfaaz": "^1.1.0", "axios": "^1.13.5", + "blueimp-load-image": "^5.16.0", "clsx": "^2.1.1", "emoji-mart": "^5.6.0", "file-saver": "^2.0.5", @@ -59,6 +60,7 @@ "devDependencies": { "@eslint/js": "^9.16.0", "@tanstack/eslint-plugin-query": "^5.62.1", + "@types/blueimp-load-image": "^5.16.0", "@types/file-saver": "^2.0.7", "@types/js-cookie": "^3.0.6", "@types/katex": "^0.16.7", diff --git a/apps/client/src/features/attachments/services/attachment-service.ts b/apps/client/src/features/attachments/services/attachment-service.ts index 9550e06d..fa43da3c 100644 --- a/apps/client/src/features/attachments/services/attachment-service.ts +++ b/apps/client/src/features/attachments/services/attachment-service.ts @@ -1,20 +1,62 @@ import api from "@/lib/api-client"; +import loadImage from "blueimp-load-image"; import { AvatarIconType, IAttachment, } from "@/features/attachments/types/attachment.types.ts"; +async function compressAndResizeIcon( + file: File, + type: AvatarIconType, +): Promise { + const isPng = file.type === "image/png"; + + const { image: canvas } = await loadImage(file, { + maxWidth: 300, + maxHeight: 300, + canvas: true, + orientation: true, + imageSmoothingQuality: "high", + }); + + if (type === AvatarIconType.AVATAR || !isPng) { + const ctx = (canvas as HTMLCanvasElement).getContext("2d")!; + ctx.globalCompositeOperation = "destination-over"; + ctx.fillStyle = "#ffffff"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.globalCompositeOperation = "source-over"; + } + + const outputType = isPng ? "image/png" : "image/jpeg"; + + return new Promise((resolve, reject) => { + (canvas as HTMLCanvasElement).toBlob( + (blob) => { + if (!blob) { + reject(new Error("Failed to compress image")); + return; + } + resolve(new File([blob], file.name, { type: outputType })); + }, + outputType, + isPng ? undefined : 0.85, + ); + }); +} + export async function uploadIcon( file: File, type: AvatarIconType, spaceId?: string, ): Promise { + const processed = await compressAndResizeIcon(file, type); + const formData = new FormData(); formData.append("type", type); if (spaceId) { formData.append("spaceId", spaceId); } - formData.append("image", file); + formData.append("image", processed); return await api.post("/attachments/upload-image", formData, { headers: { diff --git a/apps/server/package.json b/apps/server/package.json index 59defad4..65cb8e59 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -100,7 +100,6 @@ "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", "sanitize-filename-ts": "1.0.2", - "sharp": "0.34.3", "socket.io": "^4.8.3", "stripe": "^17.5.0", "tmp-promise": "^3.0.3", diff --git a/apps/server/src/core/attachment/attachment.utils.ts b/apps/server/src/core/attachment/attachment.utils.ts index 23512002..8f24e765 100644 --- a/apps/server/src/core/attachment/attachment.utils.ts +++ b/apps/server/src/core/attachment/attachment.utils.ts @@ -2,7 +2,6 @@ import { MultipartFile } from '@fastify/multipart'; import * as path from 'path'; import { AttachmentType } from './attachment.constants'; import { sanitizeFileName } from '../../common/helpers'; -import * as sharp from 'sharp'; export interface PreparedFile { buffer?: Buffer; @@ -77,51 +76,3 @@ export function getAttachmentFolderPath( } export const validAttachmentTypes = Object.values(AttachmentType); - -export async function compressAndResizeIcon( - buffer: Buffer, - attachmentType?: AttachmentType, -): Promise { - try { - let sharpInstance = sharp(buffer); - const metadata = await sharpInstance.metadata(); - - const targetWidth = 300; - const targetHeight = 300; - - // Only resize if image is larger than target dimensions - if (metadata.width > targetWidth || metadata.height > targetHeight) { - sharpInstance = sharpInstance.resize(targetWidth, targetHeight, { - fit: 'inside', - withoutEnlargement: true, - }); - } - - // Handle based on original format - if (metadata.format === 'png') { - // Only flatten avatars to remove transparency - if (attachmentType === AttachmentType.Avatar) { - sharpInstance = sharpInstance.flatten({ - background: { r: 255, g: 255, b: 255 }, - }); - } - - return await sharpInstance - .png({ - quality: 85, - compressionLevel: 6, - }) - .toBuffer(); - } else { - return await sharpInstance - .jpeg({ - quality: 85, - progressive: true, - mozjpeg: true, - }) - .toBuffer(); - } - } catch (err) { - throw err; - } -} diff --git a/apps/server/src/core/attachment/services/attachment.service.ts b/apps/server/src/core/attachment/services/attachment.service.ts index 7a6b228f..bc6a1e36 100644 --- a/apps/server/src/core/attachment/services/attachment.service.ts +++ b/apps/server/src/core/attachment/services/attachment.service.ts @@ -8,7 +8,6 @@ import { Readable } from 'stream'; import { StorageService } from '../../../integrations/storage/storage.service'; import { MultipartFile } from '@fastify/multipart'; import { - compressAndResizeIcon, getAttachmentFolderPath, PreparedFile, prepareFile, @@ -154,12 +153,6 @@ export class AttachmentService { const preparedFile: PreparedFile = await prepareFile(filePromise); validateFileType(preparedFile.fileExtension, validImageExtensions); - const processedBuffer = await compressAndResizeIcon( - preparedFile.buffer, - type, - ); - preparedFile.buffer = processedBuffer; - preparedFile.fileSize = processedBuffer.length; preparedFile.fileName = uuid4() + preparedFile.fileExtension; const filePath = `${getAttachmentFolderPath(type, workspaceId)}/${preparedFile.fileName}`; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e0d5da0..161aa6f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -284,6 +284,9 @@ importers: axios: specifier: ^1.13.5 version: 1.13.5 + blueimp-load-image: + specifier: ^5.16.0 + version: 5.16.0 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -378,6 +381,9 @@ importers: '@tanstack/eslint-plugin-query': specifier: ^5.62.1 version: 5.62.1(eslint@9.39.2(jiti@2.4.2))(typescript@5.7.2) + '@types/blueimp-load-image': + specifier: ^5.16.0 + version: 5.16.6 '@types/file-saver': specifier: ^2.0.7 version: 2.0.7 @@ -651,9 +657,6 @@ importers: sanitize-filename-ts: specifier: 1.0.2 version: 1.0.2 - sharp: - specifier: 0.34.3 - version: 0.34.3 socket.io: specifier: ^4.8.3 version: 4.8.3 @@ -2162,128 +2165,6 @@ packages: '@iconify/utils@3.0.1': resolution: {integrity: sha512-A78CUEnFGX8I/WlILxJCuIJXloL0j/OJ9PSchPAfCargEIKmUBWvvEMmKWB5oONwiUqlNt+5eRufdkLxeHIWYw==} - '@img/sharp-darwin-arm64@0.34.3': - resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.34.3': - resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.2.0': - resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.2.0': - resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.2.0': - resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linux-arm@1.2.0': - resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-ppc64@1.2.0': - resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} - cpu: [ppc64] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.2.0': - resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} - cpu: [s390x] - os: [linux] - - '@img/sharp-libvips-linux-x64@1.2.0': - resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} - cpu: [x64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-arm64@1.2.0': - resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-x64@1.2.0': - resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} - cpu: [x64] - os: [linux] - - '@img/sharp-linux-arm64@0.34.3': - resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm@0.34.3': - resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-ppc64@0.34.3': - resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - - '@img/sharp-linux-s390x@0.34.3': - resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.34.3': - resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.34.3': - resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.34.3': - resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-wasm32@0.34.3': - resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.34.3': - resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.3': - resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.34.3': - resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - '@inquirer/ansi@1.0.2': resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} @@ -4907,6 +4788,9 @@ packages: '@types/bcrypt@5.0.2': resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} + '@types/blueimp-load-image@5.16.6': + resolution: {integrity: sha512-e7s6CdDCUoBQdCe62Q6OS+DF68M8+ABxCEMh2Isjt4Fl3xuddljCHMN8mak48AMSVGGwUUtNRaZbkzgL5PEWew==} + '@types/body-parser@1.19.5': resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} @@ -5821,6 +5705,9 @@ packages: bluebird@3.4.7: resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + blueimp-load-image@5.16.0: + resolution: {integrity: sha512-3DUSVdOtlfNRk7moRZuTwDmA3NnG8KIJuLcq3c0J7/BIr6X3Vb/EpX3kUH1joxUhmoVF4uCpDfz7wHkz8pQajA==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -6063,13 +5950,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -6545,10 +6425,6 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - detect-libc@2.0.4: - resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} - engines: {node: '>=8'} - detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -7410,9 +7286,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.4: - resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} - is-async-function@2.0.0: resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} @@ -9519,10 +9392,6 @@ packages: shallowequal@1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} - sharp@0.34.3: - resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -9570,9 +9439,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-swizzle@0.2.4: - resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} - simple-wcswidth@1.1.2: resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==} @@ -12502,92 +12368,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@img/sharp-darwin-arm64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.0 - optional: true - - '@img/sharp-darwin-x64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.0 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.2.0': - optional: true - - '@img/sharp-libvips-darwin-x64@1.2.0': - optional: true - - '@img/sharp-libvips-linux-arm64@1.2.0': - optional: true - - '@img/sharp-libvips-linux-arm@1.2.0': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.2.0': - optional: true - - '@img/sharp-libvips-linux-s390x@1.2.0': - optional: true - - '@img/sharp-libvips-linux-x64@1.2.0': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.2.0': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.2.0': - optional: true - - '@img/sharp-linux-arm64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.0 - optional: true - - '@img/sharp-linux-arm@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.0 - optional: true - - '@img/sharp-linux-ppc64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.0 - optional: true - - '@img/sharp-linux-s390x@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.0 - optional: true - - '@img/sharp-linux-x64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.0 - optional: true - - '@img/sharp-linuxmusl-arm64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.0 - optional: true - - '@img/sharp-wasm32@0.34.3': - dependencies: - '@emnapi/runtime': 1.5.0 - optional: true - - '@img/sharp-win32-arm64@0.34.3': - optional: true - - '@img/sharp-win32-ia32@0.34.3': - optional: true - - '@img/sharp-win32-x64@0.34.3': - optional: true - '@inquirer/ansi@1.0.2': {} '@inquirer/checkbox@4.1.2(@types/node@22.13.4)': @@ -15437,6 +15217,8 @@ snapshots: dependencies: '@types/node': 22.19.1 + '@types/blueimp-load-image@5.16.6': {} + '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 @@ -16508,6 +16290,8 @@ snapshots: bluebird@3.4.7: {} + blueimp-load-image@5.16.0: {} + boolbase@1.0.0: {} bowser@2.11.0: {} @@ -16778,16 +16562,6 @@ snapshots: color-name@1.1.4: {} - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.4 - - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - colorette@2.0.20: {} columnify@1.6.0: @@ -17252,8 +17026,6 @@ snapshots: dequal@2.0.3: {} - detect-libc@2.0.4: {} - detect-newline@3.1.0: {} detect-node-es@1.1.0: {} @@ -18285,8 +18057,6 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.4: {} - is-async-function@2.0.0: dependencies: has-tostringtag: 1.0.2 @@ -20769,35 +20539,6 @@ snapshots: shallowequal@1.1.0: {} - sharp@0.34.3: - dependencies: - color: 4.2.3 - detect-libc: 2.0.4 - semver: 7.7.2 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.3 - '@img/sharp-darwin-x64': 0.34.3 - '@img/sharp-libvips-darwin-arm64': 1.2.0 - '@img/sharp-libvips-darwin-x64': 1.2.0 - '@img/sharp-libvips-linux-arm': 1.2.0 - '@img/sharp-libvips-linux-arm64': 1.2.0 - '@img/sharp-libvips-linux-ppc64': 1.2.0 - '@img/sharp-libvips-linux-s390x': 1.2.0 - '@img/sharp-libvips-linux-x64': 1.2.0 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 - '@img/sharp-libvips-linuxmusl-x64': 1.2.0 - '@img/sharp-linux-arm': 0.34.3 - '@img/sharp-linux-arm64': 0.34.3 - '@img/sharp-linux-ppc64': 0.34.3 - '@img/sharp-linux-s390x': 0.34.3 - '@img/sharp-linux-x64': 0.34.3 - '@img/sharp-linuxmusl-arm64': 0.34.3 - '@img/sharp-linuxmusl-x64': 0.34.3 - '@img/sharp-wasm32': 0.34.3 - '@img/sharp-win32-arm64': 0.34.3 - '@img/sharp-win32-ia32': 0.34.3 - '@img/sharp-win32-x64': 0.34.3 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -20853,10 +20594,6 @@ snapshots: signal-exit@4.1.0: {} - simple-swizzle@0.2.4: - dependencies: - is-arrayish: 0.3.4 - simple-wcswidth@1.1.2: {} sisteransi@1.0.5: {} From 03a70d768a3838196e2328327d336d2ab3cfaf7e Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Wed, 18 Feb 2026 14:48:15 +0100 Subject: [PATCH 06/10] fix: allow deleting last character in headings (#1954) The copy-link decoration widget (contentEditable="false") injected inside headings prevented browsers from deleting the last remaining character via Backspace or Delete keys. Only show the widget when the heading has more than one character of content. --- packages/editor-ext/src/lib/heading/heading.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor-ext/src/lib/heading/heading.ts b/packages/editor-ext/src/lib/heading/heading.ts index 52c463e9..26f6f0d4 100644 --- a/packages/editor-ext/src/lib/heading/heading.ts +++ b/packages/editor-ext/src/lib/heading/heading.ts @@ -20,7 +20,7 @@ export const Heading = TiptapHeading.extend({ const { doc } = state; doc.descendants((node, pos) => { - if (node.type.name === "heading" && node.content.size > 0) { + if (node.type.name === "heading" && node.content.size > 1) { const deco = Decoration.widget( pos + node.nodeSize - 1, () => { From 873c9630439c04bbdca4fb07dbf6a8b0781bee4a Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:34:07 +0000 Subject: [PATCH 07/10] fix db types duplication --- .../server/src/database/types/db.interface.ts | 49 +------------------ 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/apps/server/src/database/types/db.interface.ts b/apps/server/src/database/types/db.interface.ts index 58146b9e..be66fd8c 100644 --- a/apps/server/src/database/types/db.interface.ts +++ b/apps/server/src/database/types/db.interface.ts @@ -1,51 +1,6 @@ -import { - ApiKeys, - Attachments, - AuthAccounts, - AuthProviders, - Backlinks, - Billing, - Comments, - FileTasks, - Groups, - GroupUsers, - Notifications, - PageHistory, - Pages, - Shares, - SpaceMembers, - Spaces, - UserMfa, - Users, - UserTokens, - Watchers, - WorkspaceInvitations, - Workspaces, -} from '@docmost/db/types/db'; +import { DB } from '@docmost/db/types/db'; import { PageEmbeddings } from '@docmost/db/types/embeddings.types'; -export interface DbInterface { - attachments: Attachments; - authAccounts: AuthAccounts; - authProviders: AuthProviders; - backlinks: Backlinks; - billing: Billing; - comments: Comments; - fileTasks: FileTasks; - groups: Groups; - groupUsers: GroupUsers; - notifications: Notifications; +export interface DbInterface extends DB { pageEmbeddings: PageEmbeddings; - pageHistory: PageHistory; - pages: Pages; - shares: Shares; - spaceMembers: SpaceMembers; - spaces: Spaces; - userMfa: UserMfa; - users: Users; - userTokens: UserTokens; - watchers: Watchers; - workspaceInvitations: WorkspaceInvitations; - workspaces: Workspaces; - apiKeys: ApiKeys; } From d6472f08765918b62fe2b373e7998101f5244678 Mon Sep 17 00:00:00 2001 From: b4sh2 <58754382+b4sh2@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:59:44 +0100 Subject: [PATCH 08/10] Merge commit from fork Co-authored-by: b4sh2 --- apps/server/src/core/attachment/attachment.utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server/src/core/attachment/attachment.utils.ts b/apps/server/src/core/attachment/attachment.utils.ts index 8f24e765..0bddee1a 100644 --- a/apps/server/src/core/attachment/attachment.utils.ts +++ b/apps/server/src/core/attachment/attachment.utils.ts @@ -2,6 +2,7 @@ import { MultipartFile } from '@fastify/multipart'; import * as path from 'path'; import { AttachmentType } from './attachment.constants'; import { sanitizeFileName } from '../../common/helpers'; +import { getMimeType } from '../../common/helpers/file.helper'; export interface PreparedFile { buffer?: Buffer; @@ -40,7 +41,7 @@ export async function prepareFile( fileName, fileSize, fileExtension, - mimeType: file.mimetype, + mimeType: getMimeType(file.originalname), multiPartFile: file, }; } catch (error) { From 53132acb0a9109fd34147be4c1947c9d7cb7580d Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:02:23 +0000 Subject: [PATCH 09/10] fix: redirect to original page after re-authentication (#1959) * fix: redirect to original page after re-authentication When a session expires, the current URL is now preserved as a query parameter on the login page. After successful login (including MFA flows), the user is redirected back to their original page instead of always landing on /home. * secure --------- Co-authored-by: Julien Fontanet --- .../src/ee/components/ldap-login-modal.tsx | 8 ++++---- .../src/ee/mfa/components/mfa-challenge.tsx | 4 ++-- .../src/ee/mfa/components/mfa-setup-required.tsx | 4 ++-- .../src/ee/mfa/hooks/use-mfa-page-protection.ts | 12 +++++++----- apps/client/src/features/auth/hooks/use-auth.ts | 8 ++++---- .../auth/hooks/use-redirect-if-authenticated.ts | 4 ++-- apps/client/src/lib/api-client.ts | 6 +++++- apps/client/src/lib/app-route.ts | 16 ++++++++++++++++ 8 files changed, 42 insertions(+), 20 deletions(-) diff --git a/apps/client/src/ee/components/ldap-login-modal.tsx b/apps/client/src/ee/components/ldap-login-modal.tsx index 9360651d..0a456946 100644 --- a/apps/client/src/ee/components/ldap-login-modal.tsx +++ b/apps/client/src/ee/components/ldap-login-modal.tsx @@ -7,7 +7,7 @@ import { notifications } from "@mantine/notifications"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { IAuthProvider } from "@/ee/security/types/security.types"; -import APP_ROUTE from "@/lib/app-route"; +import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route"; import { ldapLogin } from "@/ee/security/services/ldap-auth-service"; const formSchema = z.object({ @@ -59,13 +59,13 @@ export function LdapLoginModal({ // Handle MFA like the regular login if (response?.userHasMfa) { onClose(); - navigate(APP_ROUTE.AUTH.MFA_CHALLENGE); + navigate(APP_ROUTE.AUTH.MFA_CHALLENGE + window.location.search); } else if (response?.requiresMfaSetup) { onClose(); - navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED); + navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED + window.location.search); } else { onClose(); - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); } } catch (err: any) { setIsLoading(false); diff --git a/apps/client/src/ee/mfa/components/mfa-challenge.tsx b/apps/client/src/ee/mfa/components/mfa-challenge.tsx index 8a9bef53..413494ef 100644 --- a/apps/client/src/ee/mfa/components/mfa-challenge.tsx +++ b/apps/client/src/ee/mfa/components/mfa-challenge.tsx @@ -18,7 +18,7 @@ import { useNavigate } from "react-router-dom"; import { notifications } from "@mantine/notifications"; import classes from "./mfa-challenge.module.css"; import { verifyMfa } from "@/ee/mfa"; -import APP_ROUTE from "@/lib/app-route"; +import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route"; import { useTranslation } from "react-i18next"; import * as z from "zod"; import { MfaBackupCodeInput } from "./mfa-backup-code-input"; @@ -53,7 +53,7 @@ export function MfaChallenge() { setIsLoading(true); try { await verifyMfa(values.code); - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); } catch (error: any) { setIsLoading(false); notifications.show({ diff --git a/apps/client/src/ee/mfa/components/mfa-setup-required.tsx b/apps/client/src/ee/mfa/components/mfa-setup-required.tsx index c657abe9..ab327c4d 100644 --- a/apps/client/src/ee/mfa/components/mfa-setup-required.tsx +++ b/apps/client/src/ee/mfa/components/mfa-setup-required.tsx @@ -3,7 +3,7 @@ import { Container, Paper, Title, Text, Alert, Stack } from "@mantine/core"; import { IconAlertCircle } from "@tabler/icons-react"; import { useTranslation } from "react-i18next"; import { MfaSetupModal } from "@/ee/mfa"; -import APP_ROUTE from "@/lib/app-route.ts"; +import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route.ts"; import { useNavigate } from "react-router-dom"; export default function MfaSetupRequired() { @@ -11,7 +11,7 @@ export default function MfaSetupRequired() { const navigate = useNavigate(); const handleSetupComplete = () => { - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); }; return ( diff --git a/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts b/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts index 9200cac7..30b27427 100644 --- a/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts +++ b/apps/client/src/ee/mfa/hooks/use-mfa-page-protection.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import APP_ROUTE from "@/lib/app-route"; +import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route"; import { validateMfaAccess } from "@/ee/mfa"; export function useMfaPageProtection() { @@ -13,8 +13,10 @@ export function useMfaPageProtection() { const checkAccess = async () => { const result = await validateMfaAccess(); + const search = location.search; + if (!result.valid) { - navigate(APP_ROUTE.AUTH.LOGIN); + navigate(APP_ROUTE.AUTH.LOGIN + search); return; } @@ -26,17 +28,17 @@ export function useMfaPageProtection() { if (result.requiresMfaSetup && !isOnSetupPage) { // User needs to set up MFA but is on challenge page - navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED); + navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED + search); } else if ( !result.requiresMfaSetup && result.userHasMfa && !isOnChallengePage ) { // User has MFA and should be on challenge page - navigate(APP_ROUTE.AUTH.MFA_CHALLENGE); + navigate(APP_ROUTE.AUTH.MFA_CHALLENGE + search); } else if (!result.isTransferToken) { // User has a regular auth token, shouldn't be on MFA pages - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); } else { setIsValid(true); } diff --git a/apps/client/src/features/auth/hooks/use-auth.ts b/apps/client/src/features/auth/hooks/use-auth.ts index decb393f..6e1b4e34 100644 --- a/apps/client/src/features/auth/hooks/use-auth.ts +++ b/apps/client/src/features/auth/hooks/use-auth.ts @@ -23,7 +23,7 @@ import { acceptInvitation, createWorkspace, } from "@/features/workspace/services/workspace-service.ts"; -import APP_ROUTE from "@/lib/app-route.ts"; +import APP_ROUTE, { getPostLoginRedirect } from "@/lib/app-route.ts"; import { RESET } from "jotai/utils"; import { useTranslation } from "react-i18next"; import { isCloud } from "@/lib/config.ts"; @@ -44,11 +44,11 @@ export default function useAuth() { // Check if MFA is required if (response?.userHasMfa) { - navigate(APP_ROUTE.AUTH.MFA_CHALLENGE); + navigate(APP_ROUTE.AUTH.MFA_CHALLENGE + window.location.search); } else if (response?.requiresMfaSetup) { - navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED); + navigate(APP_ROUTE.AUTH.MFA_SETUP_REQUIRED + window.location.search); } else { - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); } } catch (err) { setIsLoading(false); diff --git a/apps/client/src/features/auth/hooks/use-redirect-if-authenticated.ts b/apps/client/src/features/auth/hooks/use-redirect-if-authenticated.ts index 8961ea93..10c76bd3 100644 --- a/apps/client/src/features/auth/hooks/use-redirect-if-authenticated.ts +++ b/apps/client/src/features/auth/hooks/use-redirect-if-authenticated.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; import useCurrentUser from "@/features/user/hooks/use-current-user.ts"; -import APP_ROUTE from "@/lib/app-route.ts"; +import { getPostLoginRedirect } from "@/lib/app-route.ts"; import { useNavigate } from "react-router-dom"; export function useRedirectIfAuthenticated() { @@ -9,7 +9,7 @@ export function useRedirectIfAuthenticated() { useEffect(() => { if (data && data?.user) { - navigate(APP_ROUTE.HOME); + navigate(getPostLoginRedirect()); } }, [isLoading, data]); } diff --git a/apps/client/src/lib/api-client.ts b/apps/client/src/lib/api-client.ts index d4f61b05..632811db 100644 --- a/apps/client/src/lib/api-client.ts +++ b/apps/client/src/lib/api-client.ts @@ -68,10 +68,14 @@ function redirectToLogin() { APP_ROUTE.AUTH.SIGNUP, APP_ROUTE.AUTH.FORGOT_PASSWORD, APP_ROUTE.AUTH.PASSWORD_RESET, + APP_ROUTE.AUTH.MFA_CHALLENGE, + APP_ROUTE.AUTH.MFA_SETUP_REQUIRED, "/invites", ]; if (!exemptPaths.some((path) => window.location.pathname.startsWith(path))) { - window.location.href = APP_ROUTE.AUTH.LOGIN; + const redirectTo = window.location.pathname; + const params = new URLSearchParams({ redirect: redirectTo }); + window.location.href = `${APP_ROUTE.AUTH.LOGIN}?${params.toString()}`; } } diff --git a/apps/client/src/lib/app-route.ts b/apps/client/src/lib/app-route.ts index 0151c856..c4a13093 100644 --- a/apps/client/src/lib/app-route.ts +++ b/apps/client/src/lib/app-route.ts @@ -29,4 +29,20 @@ const APP_ROUTE = { }, }; +export function getPostLoginRedirect(): string { + const params = new URLSearchParams(window.location.search); + const redirect = params.get("redirect"); + if (redirect) { + try { + const resolved = new URL(redirect, window.location.origin); + if (resolved.origin === window.location.origin) { + return resolved.pathname + resolved.search + resolved.hash; + } + } catch { + // malformed URL, fall through to default + } + } + return APP_ROUTE.HOME; +} + export default APP_ROUTE; From c172d3bd5e8cbed77d50f8c9b6c3354aa0851ea1 Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:43:49 +0000 Subject: [PATCH 10/10] fix --- apps/server/src/core/attachment/attachment.utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/core/attachment/attachment.utils.ts b/apps/server/src/core/attachment/attachment.utils.ts index 0bddee1a..88edb2af 100644 --- a/apps/server/src/core/attachment/attachment.utils.ts +++ b/apps/server/src/core/attachment/attachment.utils.ts @@ -2,7 +2,7 @@ import { MultipartFile } from '@fastify/multipart'; import * as path from 'path'; import { AttachmentType } from './attachment.constants'; import { sanitizeFileName } from '../../common/helpers'; -import { getMimeType } from '../../common/helpers/file.helper'; +import { getMimeType } from '../../common/helpers'; export interface PreparedFile { buffer?: Buffer; @@ -41,7 +41,7 @@ export async function prepareFile( fileName, fileSize, fileExtension, - mimeType: getMimeType(file.originalname), + mimeType: getMimeType(file.filename), multiPartFile: file, }; } catch (error) {