diff --git a/apps/admin/package.json b/apps/admin/package.json index 83c01a57e..6aa37876f 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -1,7 +1,7 @@ { "name": "admin", "description": "Admin UI for Plane", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "private": true, "scripts": { diff --git a/apps/live/package.json b/apps/live/package.json index 54899d691..b9de661bb 100644 --- a/apps/live/package.json +++ b/apps/live/package.json @@ -1,6 +1,6 @@ { "name": "live", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "description": "A realtime collaborative server powers Plane's rich text editor", "main": "./src/server.ts", diff --git a/apps/server/package.json b/apps/server/package.json index 6b3118020..46d5fc5e6 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "plane-api", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "private": true, "description": "API server powering Plane's backend" diff --git a/apps/space/package.json b/apps/space/package.json index 2159adf56..13356047e 100644 --- a/apps/space/package.json +++ b/apps/space/package.json @@ -1,6 +1,6 @@ { "name": "space", - "version": "0.27.0", + "version": "0.27.1", "private": true, "license": "AGPL-3.0", "scripts": { diff --git a/apps/web/core/components/settings/sidebar/nav-item.tsx b/apps/web/core/components/settings/sidebar/nav-item.tsx index 6dcc36fa3..af1d139d6 100644 --- a/apps/web/core/components/settings/sidebar/nav-item.tsx +++ b/apps/web/core/components/settings/sidebar/nav-item.tsx @@ -6,7 +6,7 @@ import { Disclosure } from "@headlessui/react"; // plane imports import { useTranslation } from "@plane/i18n"; import { EUserWorkspaceRoles } from "@plane/types"; -import { cn } from "@plane/utils"; +import { cn, joinUrlPath } from "@plane/utils"; // hooks import { useUserSettings } from "@/hooks/store"; @@ -72,7 +72,11 @@ const SettingsSidebarNavItem = observer((props: TSettingsSidebarNavItemProps) => {renderChildren ? (
{titleElement}
) : ( - toggleSidebar(true)}> + toggleSidebar(true)} + > {titleElement} )} diff --git a/apps/web/core/components/settings/sidebar/root.tsx b/apps/web/core/components/settings/sidebar/root.tsx index b29425f9e..c681a8cc1 100644 --- a/apps/web/core/components/settings/sidebar/root.tsx +++ b/apps/web/core/components/settings/sidebar/root.tsx @@ -46,10 +46,11 @@ export const SettingsSidebar = observer((props: SettingsSidebarProps) => { {/* Navigation */}
- {categories.map((category) => ( -
- {t(category)} - {groupedSettings[category].length > 0 && ( + {categories.map((category) => { + if (groupedSettings[category].length === 0) return null; + return ( +
+ {t(category)}
{groupedSettings[category].map( (setting) => @@ -66,9 +67,9 @@ export const SettingsSidebar = observer((props: SettingsSidebarProps) => { ) )}
- )} -
- ))} +
+ ); + })}
); diff --git a/apps/web/package.json b/apps/web/package.json index c1f4d1d2c..74ab1dde2 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "0.27.0", + "version": "0.27.1", "private": true, "license": "AGPL-3.0", "scripts": { diff --git a/package.json b/package.json index 6faf02651..c29c323a2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "plane", "description": "Open-source project management that unlocks customer value", "repository": "https://github.com/makeplane/plane.git", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "private": true, "workspaces": [ diff --git a/packages/constants/package.json b/packages/constants/package.json index f0de91401..f3e176772 100644 --- a/packages/constants/package.json +++ b/packages/constants/package.json @@ -1,6 +1,6 @@ { "name": "@plane/constants", - "version": "0.27.0", + "version": "0.27.1", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/packages/editor/package.json b/packages/editor/package.json index d1d854336..ed1794c5c 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@plane/editor", - "version": "0.27.0", + "version": "0.27.1", "description": "Core Editor that powers Plane", "license": "AGPL-3.0", "private": true, diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 0e7e2382b..98a831bbc 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -1,7 +1,7 @@ { "name": "@plane/eslint-config", "private": true, - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "files": [ "library.js", diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 81484513d..52b57de7e 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@plane/hooks", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "description": "React hooks that are shared across multiple apps internally", "private": true, diff --git a/packages/i18n/package.json b/packages/i18n/package.json index d4faaf017..ce07c6c86 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@plane/i18n", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "description": "I18n shared across multiple apps internally", "private": true, diff --git a/packages/logger/package.json b/packages/logger/package.json index ca2a0dbe2..24dc3c789 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@plane/logger", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "description": "Logger shared across multiple apps internally", "private": true, diff --git a/packages/propel/package.json b/packages/propel/package.json index e6922c718..2a683a1e5 100644 --- a/packages/propel/package.json +++ b/packages/propel/package.json @@ -1,6 +1,6 @@ { "name": "@plane/propel", - "version": "0.27.0", + "version": "0.27.1", "private": true, "license": "AGPL-3.0", "scripts": { diff --git a/packages/services/package.json b/packages/services/package.json index 449e9efed..43c44dc95 100644 --- a/packages/services/package.json +++ b/packages/services/package.json @@ -1,6 +1,6 @@ { "name": "@plane/services", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "private": true, "main": "./src/index.ts", diff --git a/packages/shared-state/package.json b/packages/shared-state/package.json index 167c1794a..a68adb403 100644 --- a/packages/shared-state/package.json +++ b/packages/shared-state/package.json @@ -1,6 +1,6 @@ { "name": "@plane/shared-state", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "description": "Shared state shared across multiple apps internally", "private": true, diff --git a/packages/tailwind-config/package.json b/packages/tailwind-config/package.json index 8e6f51822..524062c5e 100644 --- a/packages/tailwind-config/package.json +++ b/packages/tailwind-config/package.json @@ -1,6 +1,6 @@ { "name": "@plane/tailwind-config", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "description": "common tailwind configuration across monorepo", "main": "tailwind.config.js", diff --git a/packages/types/package.json b/packages/types/package.json index f4ae50831..5fc431692 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@plane/types", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "private": true, "type": "module", diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json index f5b3d1a85..58fe486eb 100644 --- a/packages/typescript-config/package.json +++ b/packages/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@plane/typescript-config", - "version": "0.27.0", + "version": "0.27.1", "license": "AGPL-3.0", "private": true, "files": [ diff --git a/packages/ui/package.json b/packages/ui/package.json index bf388c8fb..720b9e0aa 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -2,7 +2,7 @@ "name": "@plane/ui", "description": "UI components shared across multiple apps internally", "private": true, - "version": "0.27.0", + "version": "0.27.1", "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", diff --git a/packages/utils/package.json b/packages/utils/package.json index ca53a18a0..f4d329494 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@plane/utils", - "version": "0.27.0", + "version": "0.27.1", "description": "Helper functions shared across multiple apps internally", "license": "AGPL-3.0", "private": true, diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts index 1bb23c81d..58822e1e9 100644 --- a/packages/utils/src/string.ts +++ b/packages/utils/src/string.ts @@ -318,3 +318,57 @@ export const copyTextToClipboard = async (text: string): Promise => { } await navigator.clipboard.writeText(text); }; + + +/** + * @description Joins URL path segments properly, removing duplicate slashes using URL encoding + * @param {...string} segments - URL path segments to join + * @returns {string} Properly joined URL path + * @example + * joinUrlPath("/workspace", "/projects") => "/workspace/projects" + * joinUrlPath("/workspace", "projects") => "/workspace/projects" + * joinUrlPath("workspace", "projects") => "/workspace/projects" + * joinUrlPath("/workspace/", "/projects/") => "/workspace/projects/" + */ +export const joinUrlPath = (...segments: string[]): string => { + if (segments.length === 0) return ""; + + // Filter out empty segments + const validSegments = segments.filter((segment) => segment !== ""); + if (validSegments.length === 0) return ""; + + // Process segments to normalize slashes + const processedSegments = validSegments.map((segment, index) => { + let processed = segment; + + // Remove leading slashes from all segments except the first + if (index > 0) { + while (processed.startsWith("/")) { + processed = processed.substring(1); + } + } + + // Remove trailing slashes from all segments except the last + if (index < validSegments.length - 1) { + while (processed.endsWith("/")) { + processed = processed.substring(0, processed.length - 1); + } + } + + return processed; + }); + + // Join segments with single slash + const joined = processedSegments.join("/"); + + // Use URL constructor to normalize the path and handle double slashes + try { + // Create a dummy URL to leverage browser's URL normalization + const dummyUrl = new URL(`http://example.com/${joined}`); + return dummyUrl.pathname; + } catch { + // Fallback: manually handle double slashes by splitting and filtering + const pathParts = joined.split("/").filter((part) => part !== ""); + return pathParts.length > 0 ? `/${pathParts.join("/")}` : ""; + } +}; \ No newline at end of file