From a3a92af814370821833965603f598601587066aa Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 8 Apr 2025 14:29:53 +0530 Subject: [PATCH 1/2] fix: remove prefix slash if present --- packages/utils/src/string.ts | 1 + .../issues/create-issue-toast-action-items.tsx | 5 +++-- .../quick-action-dropdowns/all-issue.tsx | 7 +++---- .../quick-action-dropdowns/cycle-issue.tsx | 7 +++---- .../quick-action-dropdowns/module-issue.tsx | 7 +++---- .../quick-action-dropdowns/project-issue.tsx | 7 +++---- .../components/issues/peek-overview/header.tsx | 7 ++----- web/core/hooks/use-page-operations.ts | 8 ++------ web/helpers/string.helper.ts | 14 -------------- 9 files changed, 20 insertions(+), 43 deletions(-) diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts index 2fc52a254ef..acaae53e3a8 100644 --- a/packages/utils/src/string.ts +++ b/packages/utils/src/string.ts @@ -87,6 +87,7 @@ export const copyTextToClipboard = async (text: string): Promise => { */ export const copyUrlToClipboard = async (path: string) => { const originUrl = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; + if (path[0] === "/") path = path.slice(1); await copyTextToClipboard(`${originUrl}/${path}`); }; diff --git a/web/core/components/issues/create-issue-toast-action-items.tsx b/web/core/components/issues/create-issue-toast-action-items.tsx index 16f2a2d7576..f324710f216 100644 --- a/web/core/components/issues/create-issue-toast-action-items.tsx +++ b/web/core/components/issues/create-issue-toast-action-items.tsx @@ -1,9 +1,10 @@ "use client"; import React, { FC, useState } from "react"; import { observer } from "mobx-react"; +// plane imports +import { copyUrlToClipboard } from "@plane/utils"; // helpers import { generateWorkItemLink } from "@/helpers/issue.helper"; -import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useIssueDetail, useProject } from "@/hooks/store"; @@ -41,7 +42,7 @@ export const CreateIssueToastActionItems: FC = obs const copyToClipboard = async (e: React.MouseEvent) => { try { - await copyUrlToClipboard(workItemLink, false); + await copyUrlToClipboard(workItemLink); setCopied(true); setTimeout(() => setCopied(false), 3000); } catch (error) { diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx index 1dca91a9345..752f909a75f 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx @@ -5,17 +5,16 @@ import omit from "lodash/omit"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Copy, ExternalLink, Link, Pencil, Trash2 } from "lucide-react"; -// types +// plane imports import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType } from "@plane/constants"; import { TIssue } from "@plane/types"; -// ui import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; +import { copyUrlToClipboard } from "@plane/utils"; // components import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper"; -import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useEventTracker, useProject, useProjectState } from "@/hooks/store"; // types @@ -62,7 +61,7 @@ export const AllIssueQuickActions: React.FC = observer((props const handleOpenInNewTab = () => window.open(workItemLink, "_blank"); const handleCopyIssueLink = () => - copyUrlToClipboard(workItemLink, false).then(() => + copyUrlToClipboard(workItemLink).then(() => setToast({ type: TOAST_TYPE.SUCCESS, title: "Link copied", diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx index 9d752b6308a..d6af3bf1f78 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx @@ -5,17 +5,16 @@ import omit from "lodash/omit"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Copy, ExternalLink, Link, Pencil, Trash2, XCircle } from "lucide-react"; -// types +// plane imports import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { TIssue } from "@plane/types"; -// ui import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; +import { copyUrlToClipboard } from "@plane/utils"; // components import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper"; -import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useEventTracker, useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store"; // types @@ -70,7 +69,7 @@ export const CycleIssueQuickActions: React.FC = observer((pro const handleOpenInNewTab = () => window.open(workItemLink, "_blank"); const handleCopyIssueLink = () => - copyUrlToClipboard(workItemLink, false).then(() => + copyUrlToClipboard(workItemLink).then(() => setToast({ type: TOAST_TYPE.SUCCESS, title: "Link copied", diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx index c2f0aa99418..c7f949fcf98 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx @@ -5,17 +5,16 @@ import omit from "lodash/omit"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Copy, ExternalLink, Link, Pencil, Trash2, XCircle } from "lucide-react"; -// types +// plane imports import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { TIssue } from "@plane/types"; -// ui import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; +import { copyUrlToClipboard } from "@plane/utils"; // components import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper"; -import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useIssues, useEventTracker, useProjectState, useUserPermissions, useProject } from "@/hooks/store"; // types @@ -70,7 +69,7 @@ export const ModuleIssueQuickActions: React.FC = observer((pr const handleOpenInNewTab = () => window.open(workItemLink, "_blank"); const handleCopyIssueLink = () => - copyUrlToClipboard(workItemLink, false).then(() => + copyUrlToClipboard(workItemLink).then(() => setToast({ type: TOAST_TYPE.SUCCESS, title: "Link copied", diff --git a/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx b/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx index ac346a4f4a5..902351f2bbe 100644 --- a/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx +++ b/web/core/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx @@ -5,18 +5,17 @@ import omit from "lodash/omit"; import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; import { Copy, ExternalLink, Link, Pencil, Trash2 } from "lucide-react"; +// plane imports import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; -// types import { TIssue } from "@plane/types"; -// ui import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; +import { copyUrlToClipboard } from "@plane/utils"; // components import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper"; -import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useEventTracker, useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store"; // types @@ -75,7 +74,7 @@ export const ProjectIssueQuickActions: React.FC = observer((p }); const handleCopyIssueLink = () => - copyUrlToClipboard(workItemLink, false).then(() => + copyUrlToClipboard(workItemLink).then(() => setToast({ type: TOAST_TYPE.SUCCESS, title: "Link copied", diff --git a/web/core/components/issues/peek-overview/header.tsx b/web/core/components/issues/peek-overview/header.tsx index b877183dc8f..a489ad17468 100644 --- a/web/core/components/issues/peek-overview/header.tsx +++ b/web/core/components/issues/peek-overview/header.tsx @@ -6,11 +6,8 @@ import Link from "next/link"; import { ArchiveRestoreIcon, Link2, MoveDiagonal, MoveRight, Trash2 } from "lucide-react"; // plane imports import { ARCHIVABLE_STATE_GROUPS } from "@plane/constants"; -// i18n import { useTranslation } from "@plane/i18n"; -// types import { TNameDescriptionLoader } from "@plane/types"; -// ui import { ArchiveIcon, CenterPanelIcon, @@ -21,12 +18,12 @@ import { Tooltip, setToast, } from "@plane/ui"; +import { copyUrlToClipboard } from "@plane/utils"; // components import { IssueSubscription, NameDescriptionUpdateStatus } from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper"; -import { copyUrlToClipboard } from "@/helpers/string.helper"; // store hooks import { useIssueDetail, useProject, useProjectState, useUser } from "@/hooks/store"; // hooks @@ -110,7 +107,7 @@ export const IssuePeekOverviewHeader: FC = observer((pr const handleCopyText = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); - copyUrlToClipboard(workItemLink, false).then(() => { + copyUrlToClipboard(workItemLink).then(() => { setToast({ type: TOAST_TYPE.SUCCESS, title: t("common.link_copied"), diff --git a/web/core/hooks/use-page-operations.ts b/web/core/hooks/use-page-operations.ts index 85862d828ba..c893126b8b8 100644 --- a/web/core/hooks/use-page-operations.ts +++ b/web/core/hooks/use-page-operations.ts @@ -1,14 +1,10 @@ import { useMemo } from "react"; -// plane constants +// plane imports import { IS_FAVORITE_MENU_OPEN } from "@plane/constants"; -// plane editor import { EditorRefApi } from "@plane/editor"; -// plane types import { EPageAccess } from "@plane/types/src/enums"; -// plane ui import { setToast, TOAST_TYPE } from "@plane/ui"; -// helpers -import { copyUrlToClipboard } from "@/helpers/string.helper"; +import { copyUrlToClipboard } from "@plane/utils"; // hooks import { useCollaborativePageActions } from "@/hooks/use-collaborative-page-actions"; // store types diff --git a/web/helpers/string.helper.ts b/web/helpers/string.helper.ts index 3f31d73ac1a..d0973109171 100644 --- a/web/helpers/string.helper.ts +++ b/web/helpers/string.helper.ts @@ -65,20 +65,6 @@ export const copyTextToClipboard = async (text: string) => { await navigator.clipboard.writeText(text); }; -/** - * @description: This function copies the url to clipboard after prepending the origin URL to it - * @param {string} path - * @param {boolean} addSlash - * @example: - * const text = copyUrlToClipboard("path"); - * copied URL: origin_url/path - */ -export const copyUrlToClipboard = async (path: string, addSlash: boolean = true) => { - const originUrl = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; - - await copyTextToClipboard(`${originUrl}${addSlash ? "/" : ""}${path}`); -}; - export const generateRandomColor = (string: string): string => { if (!string) return "rgb(var(--color-primary-100))"; From a2f84bb5cf690c0fbec89a6d17069e9ef9f14576 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Tue, 8 Apr 2025 15:12:32 +0530 Subject: [PATCH 2/2] chore: make use of URL class to generate a valid URL --- packages/utils/src/string.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts index acaae53e3a8..1ad761f972a 100644 --- a/packages/utils/src/string.ts +++ b/packages/utils/src/string.ts @@ -86,9 +86,11 @@ export const copyTextToClipboard = async (text: string): Promise => { * await copyUrlToClipboard("issues/123") // copies "https://example.com/issues/123" */ export const copyUrlToClipboard = async (path: string) => { - const originUrl = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; - if (path[0] === "/") path = path.slice(1); - await copyTextToClipboard(`${originUrl}/${path}`); + // get origin or default to empty string if not in browser + const originUrl = typeof window !== "undefined" ? window.location.origin : ""; + // create URL object and ensure proper path formatting + const url = new URL(path, originUrl); + await copyTextToClipboard(url.toString()); }; /**