[WEB-3797] fix: remove leading slash from URL to copy (#6890)

* fix: remove prefix slash if present

* chore: make use of URL class to generate a valid URL
This commit is contained in:
Aaryan Khandelwal 2025-04-08 15:22:23 +05:30 committed by GitHub
parent 27cec64c56
commit 37699362ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 24 additions and 45 deletions

View file

@ -86,8 +86,11 @@ export const copyTextToClipboard = async (text: string): Promise<void> => {
* await copyUrlToClipboard("issues/123") // copies "https://example.com/issues/123" * await copyUrlToClipboard("issues/123") // copies "https://example.com/issues/123"
*/ */
export const copyUrlToClipboard = async (path: string) => { export const copyUrlToClipboard = async (path: string) => {
const originUrl = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; // get origin or default to empty string if not in browser
await copyTextToClipboard(`${originUrl}/${path}`); 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());
}; };
/** /**

View file

@ -1,9 +1,10 @@
"use client"; "use client";
import React, { FC, useState } from "react"; import React, { FC, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
// plane imports
import { copyUrlToClipboard } from "@plane/utils";
// helpers // helpers
import { generateWorkItemLink } from "@/helpers/issue.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper";
import { copyUrlToClipboard } from "@/helpers/string.helper";
// hooks // hooks
import { useIssueDetail, useProject } from "@/hooks/store"; import { useIssueDetail, useProject } from "@/hooks/store";
@ -41,7 +42,7 @@ export const CreateIssueToastActionItems: FC<TCreateIssueToastActionItems> = obs
const copyToClipboard = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { const copyToClipboard = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
try { try {
await copyUrlToClipboard(workItemLink, false); await copyUrlToClipboard(workItemLink);
setCopied(true); setCopied(true);
setTimeout(() => setCopied(false), 3000); setTimeout(() => setCopied(false), 3000);
} catch (error) { } catch (error) {

View file

@ -5,17 +5,16 @@ import omit from "lodash/omit";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { Copy, ExternalLink, Link, Pencil, Trash2 } from "lucide-react"; import { Copy, ExternalLink, Link, Pencil, Trash2 } from "lucide-react";
// types // plane imports
import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType } from "@plane/constants"; import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType } from "@plane/constants";
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
// ui
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
import { copyUrlToClipboard } from "@plane/utils";
// components // components
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { generateWorkItemLink } from "@/helpers/issue.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper";
import { copyUrlToClipboard } from "@/helpers/string.helper";
// hooks // hooks
import { useEventTracker, useProject, useProjectState } from "@/hooks/store"; import { useEventTracker, useProject, useProjectState } from "@/hooks/store";
// types // types
@ -62,7 +61,7 @@ export const AllIssueQuickActions: React.FC<IQuickActionProps> = observer((props
const handleOpenInNewTab = () => window.open(workItemLink, "_blank"); const handleOpenInNewTab = () => window.open(workItemLink, "_blank");
const handleCopyIssueLink = () => const handleCopyIssueLink = () =>
copyUrlToClipboard(workItemLink, false).then(() => copyUrlToClipboard(workItemLink).then(() =>
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: "Link copied", title: "Link copied",

View file

@ -5,17 +5,16 @@ import omit from "lodash/omit";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { Copy, ExternalLink, Link, Pencil, Trash2, XCircle } from "lucide-react"; import { Copy, ExternalLink, Link, Pencil, Trash2, XCircle } from "lucide-react";
// types // plane imports
import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
// ui
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
import { copyUrlToClipboard } from "@plane/utils";
// components // components
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { generateWorkItemLink } from "@/helpers/issue.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper";
import { copyUrlToClipboard } from "@/helpers/string.helper";
// hooks // hooks
import { useEventTracker, useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store"; import { useEventTracker, useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store";
// types // types
@ -70,7 +69,7 @@ export const CycleIssueQuickActions: React.FC<IQuickActionProps> = observer((pro
const handleOpenInNewTab = () => window.open(workItemLink, "_blank"); const handleOpenInNewTab = () => window.open(workItemLink, "_blank");
const handleCopyIssueLink = () => const handleCopyIssueLink = () =>
copyUrlToClipboard(workItemLink, false).then(() => copyUrlToClipboard(workItemLink).then(() =>
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: "Link copied", title: "Link copied",

View file

@ -5,17 +5,16 @@ import omit from "lodash/omit";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { Copy, ExternalLink, Link, Pencil, Trash2, XCircle } from "lucide-react"; import { Copy, ExternalLink, Link, Pencil, Trash2, XCircle } from "lucide-react";
// types // plane imports
import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
// ui
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
import { copyUrlToClipboard } from "@plane/utils";
// components // components
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { generateWorkItemLink } from "@/helpers/issue.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper";
import { copyUrlToClipboard } from "@/helpers/string.helper";
// hooks // hooks
import { useIssues, useEventTracker, useProjectState, useUserPermissions, useProject } from "@/hooks/store"; import { useIssues, useEventTracker, useProjectState, useUserPermissions, useProject } from "@/hooks/store";
// types // types
@ -70,7 +69,7 @@ export const ModuleIssueQuickActions: React.FC<IQuickActionProps> = observer((pr
const handleOpenInNewTab = () => window.open(workItemLink, "_blank"); const handleOpenInNewTab = () => window.open(workItemLink, "_blank");
const handleCopyIssueLink = () => const handleCopyIssueLink = () =>
copyUrlToClipboard(workItemLink, false).then(() => copyUrlToClipboard(workItemLink).then(() =>
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: "Link copied", title: "Link copied",

View file

@ -5,18 +5,17 @@ import omit from "lodash/omit";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation"; import { useParams, usePathname } from "next/navigation";
import { Copy, ExternalLink, Link, Pencil, Trash2 } from "lucide-react"; import { Copy, ExternalLink, Link, Pencil, Trash2 } from "lucide-react";
// plane imports
import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants"; import { ARCHIVABLE_STATE_GROUPS, EIssuesStoreType, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// types
import { TIssue } from "@plane/types"; import { TIssue } from "@plane/types";
// ui
import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui"; import { ArchiveIcon, ContextMenu, CustomMenu, TContextMenuItem, TOAST_TYPE, setToast } from "@plane/ui";
import { copyUrlToClipboard } from "@plane/utils";
// components // components
import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues"; import { ArchiveIssueModal, CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { generateWorkItemLink } from "@/helpers/issue.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper";
import { copyUrlToClipboard } from "@/helpers/string.helper";
// hooks // hooks
import { useEventTracker, useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store"; import { useEventTracker, useIssues, useProject, useProjectState, useUserPermissions } from "@/hooks/store";
// types // types
@ -75,7 +74,7 @@ export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = observer((p
}); });
const handleCopyIssueLink = () => const handleCopyIssueLink = () =>
copyUrlToClipboard(workItemLink, false).then(() => copyUrlToClipboard(workItemLink).then(() =>
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: "Link copied", title: "Link copied",

View file

@ -6,11 +6,8 @@ import Link from "next/link";
import { ArchiveRestoreIcon, Link2, MoveDiagonal, MoveRight, Trash2 } from "lucide-react"; import { ArchiveRestoreIcon, Link2, MoveDiagonal, MoveRight, Trash2 } from "lucide-react";
// plane imports // plane imports
import { ARCHIVABLE_STATE_GROUPS } from "@plane/constants"; import { ARCHIVABLE_STATE_GROUPS } from "@plane/constants";
// i18n
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// types
import { TNameDescriptionLoader } from "@plane/types"; import { TNameDescriptionLoader } from "@plane/types";
// ui
import { import {
ArchiveIcon, ArchiveIcon,
CenterPanelIcon, CenterPanelIcon,
@ -21,12 +18,12 @@ import {
Tooltip, Tooltip,
setToast, setToast,
} from "@plane/ui"; } from "@plane/ui";
import { copyUrlToClipboard } from "@plane/utils";
// components // components
import { IssueSubscription, NameDescriptionUpdateStatus } from "@/components/issues"; import { IssueSubscription, NameDescriptionUpdateStatus } from "@/components/issues";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { generateWorkItemLink } from "@/helpers/issue.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper";
import { copyUrlToClipboard } from "@/helpers/string.helper";
// store hooks // store hooks
import { useIssueDetail, useProject, useProjectState, useUser } from "@/hooks/store"; import { useIssueDetail, useProject, useProjectState, useUser } from "@/hooks/store";
// hooks // hooks
@ -110,7 +107,7 @@ export const IssuePeekOverviewHeader: FC<PeekOverviewHeaderProps> = observer((pr
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => { const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
copyUrlToClipboard(workItemLink, false).then(() => { copyUrlToClipboard(workItemLink).then(() => {
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: t("common.link_copied"), title: t("common.link_copied"),

View file

@ -1,14 +1,10 @@
import { useMemo } from "react"; import { useMemo } from "react";
// plane constants // plane imports
import { IS_FAVORITE_MENU_OPEN } from "@plane/constants"; import { IS_FAVORITE_MENU_OPEN } from "@plane/constants";
// plane editor
import { EditorRefApi } from "@plane/editor"; import { EditorRefApi } from "@plane/editor";
// plane types
import { EPageAccess } from "@plane/types/src/enums"; import { EPageAccess } from "@plane/types/src/enums";
// plane ui
import { setToast, TOAST_TYPE } from "@plane/ui"; import { setToast, TOAST_TYPE } from "@plane/ui";
// helpers import { copyUrlToClipboard } from "@plane/utils";
import { copyUrlToClipboard } from "@/helpers/string.helper";
// hooks // hooks
import { useCollaborativePageActions } from "@/hooks/use-collaborative-page-actions"; import { useCollaborativePageActions } from "@/hooks/use-collaborative-page-actions";
// store types // store types

View file

@ -65,20 +65,6 @@ export const copyTextToClipboard = async (text: string) => {
await navigator.clipboard.writeText(text); 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 => { export const generateRandomColor = (string: string): string => {
if (!string) return "rgb(var(--color-primary-100))"; if (!string) return "rgb(var(--color-primary-100))";