snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:14:58 +05:00
commit 23059a1de3
5252 changed files with 573834 additions and 0 deletions

View File

@@ -0,0 +1,208 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
import { observer } from "mobx-react";
import { useTranslation } from "@plane/i18n";
import { CloseOutline, DeleteOutline, EditOutline, LinkOutline } from "@makeplane/propel/icons";
// plane imports
import { Tooltip } from "@makeplane/propel/components/tooltip";
import type { TIssueRelationTypes } from "@plane/types";
import type { TIssue, TIssueServiceType } from "@plane/types";
import { EIssueServiceType } from "@plane/types";
import { ControlLink, CustomMenu } from "@plane/ui";
import { generateWorkItemLink } from "@plane/utils";
// hooks
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
import { useProject } from "@/hooks/store/use-project";
import useIssuePeekOverviewRedirection from "@/hooks/use-issue-peek-overview-redirection";
import { usePlatformOS } from "@/hooks/use-platform-os";
// components
import { IssueIdentifier } from "@/components/issues/issue-detail/issue-identifier";
// local imports
import { useRelationOperations } from "../issue-detail-widgets/relations/helper";
import { RelationIssueProperty } from "./properties";
type Props = {
workspaceSlug: string;
issueId: string;
relationKey: TIssueRelationTypes;
relationIssueId: string;
disabled: boolean;
handleIssueCrudState: (
key: "update" | "delete" | "removeRelation",
issueId: string,
issue?: TIssue | null,
relationKey?: TIssueRelationTypes | null,
relationIssueId?: string | null
) => void;
issueServiceType?: TIssueServiceType;
};
export const RelationIssueListItem = observer(function RelationIssueListItem(props: Props) {
const {
workspaceSlug,
issueId,
relationKey,
relationIssueId,
disabled = false,
handleIssueCrudState,
issueServiceType = EIssueServiceType.ISSUES,
} = props;
const { t } = useTranslation();
// store hooks
const {
issue: { getIssueById },
removeRelation,
toggleCreateIssueModal,
toggleDeleteIssueModal,
} = useIssueDetail(issueServiceType);
const project = useProject();
const { isMobile } = usePlatformOS();
// derived values
const issue = getIssueById(relationIssueId);
const { handleRedirection } = useIssuePeekOverviewRedirection(!!issue?.is_epic);
const issueOperations = useRelationOperations(issue?.is_epic ? EIssueServiceType.EPICS : EIssueServiceType.ISSUES);
const projectDetail = (issue && issue.project_id && project.getProjectById(issue.project_id)) || undefined;
const projectId = issue?.project_id;
if (!issue || !projectId) return <></>;
const workItemLink = generateWorkItemLink({
workspaceSlug: workspaceSlug.toString(),
projectId: issue?.project_id,
issueId: issue?.id,
projectIdentifier: projectDetail?.identifier,
sequenceId: issue?.sequence_id,
isEpic: issue?.is_epic,
});
// handlers
const handleIssuePeekOverview = (issue: TIssue) => {
if (issue.is_epic) {
// open epics in new tab
window.open(workItemLink, "_blank");
return;
}
handleRedirection(workspaceSlug, issue, isMobile);
};
const handleEditIssue = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
e.preventDefault();
handleIssueCrudState("update", relationIssueId, { ...issue });
toggleCreateIssueModal(true);
};
const handleDeleteIssue = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
e.preventDefault();
handleIssueCrudState("delete", relationIssueId, issue);
toggleDeleteIssueModal(relationIssueId);
handleIssueCrudState("removeRelation", issueId, issue, relationKey, relationIssueId);
};
const handleCopyIssueLink = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.stopPropagation();
e.preventDefault();
issueOperations.copyLink(workItemLink);
};
const handleRemoveRelation = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e.preventDefault();
e.stopPropagation();
removeRelation(workspaceSlug, projectId, issueId, relationKey, relationIssueId);
};
return (
<div key={relationIssueId}>
<ControlLink
id={`issue-${issue.id}`}
href={workItemLink}
onClick={() => handleIssuePeekOverview(issue)}
className="w-full cursor-pointer"
>
{issue && (
<div className="group relative flex h-full min-h-11 w-full items-center px-1.5 py-1 transition-all hover:bg-surface-2">
<span className="size-5 flex-shrink-0" />
<div className="flex min-w-0 flex-1 cursor-pointer items-center gap-3">
<div className="flex-shrink-0">
{projectDetail && (
<IssueIdentifier
projectId={projectDetail.id}
issueTypeId={issue.type_id}
projectIdentifier={projectDetail.identifier}
issueSequenceId={issue.sequence_id}
size="xs"
variant="secondary"
/>
)}
</div>
<Tooltip label={issue.name} layout="stacked" disabled={isMobile}>
<span className="w-0 flex-1 truncate text-13 text-primary">{issue.name}</span>
</Tooltip>
</div>
<div
className="flex-shrink-0 text-13"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<RelationIssueProperty
workspaceSlug={workspaceSlug}
issueId={relationIssueId}
disabled={disabled}
issueOperations={issueOperations}
issueServiceType={issueServiceType}
/>
</div>
<div className="flex-shrink-0 pl-2 text-13">
<CustomMenu placement="bottom-end" ellipsis>
{!disabled && (
<CustomMenu.MenuItem onClick={handleEditIssue}>
<div className="flex items-center gap-2">
<EditOutline className="h-3.5 w-3.5" />
<span>{t("common.actions.edit")}</span>
</div>
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem onClick={handleCopyIssueLink}>
<div className="flex items-center gap-2">
<LinkOutline className="h-3.5 w-3.5" />
<span>{t("common.actions.copy_link")}</span>
</div>
</CustomMenu.MenuItem>
{!disabled && (
<CustomMenu.MenuItem onClick={handleRemoveRelation}>
<div className="flex items-center gap-2">
<CloseOutline className="h-3.5 w-3.5" />
<span>{t("common.actions.remove_relation")}</span>
</div>
</CustomMenu.MenuItem>
)}
{!disabled && (
<CustomMenu.MenuItem onClick={handleDeleteIssue}>
<div className="flex items-center gap-2">
<DeleteOutline className="h-3.5 w-3.5" />
<span>{t("common.actions.delete")}</span>
</div>
</CustomMenu.MenuItem>
)}
</CustomMenu>
</div>
</div>
)}
</ControlLink>
</div>
);
});

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
import { observer } from "mobx-react";
// plane imports
import type { TIssue, TIssueServiceType } from "@plane/types";
import { EIssueServiceType } from "@plane/types";
import type { TIssueRelationTypes } from "@plane/types";
// local imports
import { RelationIssueListItem } from "./issue-list-item";
type Props = {
workspaceSlug: string;
issueId: string;
issueIds: string[];
relationKey: TIssueRelationTypes;
handleIssueCrudState: (
key: "update" | "delete" | "removeRelation",
issueId: string,
issue?: TIssue | null,
relationKey?: TIssueRelationTypes | null,
relationIssueId?: string | null
) => void;
disabled?: boolean;
issueServiceType?: TIssueServiceType;
};
export const RelationIssueList = observer(function RelationIssueList(props: Props) {
const {
workspaceSlug,
issueId,
issueIds,
relationKey,
disabled = false,
handleIssueCrudState,
issueServiceType = EIssueServiceType.ISSUES,
} = props;
return (
<div className="relative">
{issueIds &&
issueIds.length > 0 &&
issueIds.map((relationIssueId) => (
<RelationIssueListItem
key={relationIssueId}
workspaceSlug={workspaceSlug}
issueId={issueId}
relationKey={relationKey}
relationIssueId={relationIssueId}
disabled={disabled}
handleIssueCrudState={handleIssueCrudState}
issueServiceType={issueServiceType}
/>
))}
</div>
);
});

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
*/
import React from "react";
import { observer } from "mobx-react";
// components
import type { TIssuePriorities, TIssueServiceType } from "@plane/types";
import { EIssueServiceType } from "@plane/types";
import { MemberDropdown } from "@/components/dropdowns/member/dropdown";
import { PriorityDropdown } from "@/components/dropdowns/priority";
import { StateDropdown } from "@/components/dropdowns/state/dropdown";
// hooks
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
// types
import type { TRelationIssueOperations } from "../issue-detail-widgets/relations/helper";
type Props = {
workspaceSlug: string;
issueId: string;
disabled: boolean;
issueOperations: TRelationIssueOperations;
issueServiceType?: TIssueServiceType;
};
export const RelationIssueProperty = observer(function RelationIssueProperty(props: Props) {
const { workspaceSlug, issueId, disabled, issueOperations, issueServiceType = EIssueServiceType.ISSUES } = props;
// hooks
const {
issue: { getIssueById },
} = useIssueDetail(issueServiceType);
// derived value
const issue = getIssueById(issueId);
// if issue is not found, return empty
if (!issue) return <></>;
// handlers
const handleStateChange = (val: string) =>
issue.project_id &&
issueOperations.update(workspaceSlug, issue.project_id, issueId, {
state_id: val,
});
const handlePriorityChange = (val: TIssuePriorities) =>
issue.project_id &&
issueOperations.update(workspaceSlug, issue.project_id, issueId, {
priority: val,
});
const handleAssigneeChange = (val: string[]) =>
issue.project_id &&
issueOperations.update(workspaceSlug, issue.project_id, issueId, {
assignee_ids: val,
});
return (
<div className="relative flex items-center gap-2">
<div className="h-5 flex-shrink-0">
<StateDropdown
value={issue.state_id}
projectId={issue.project_id ?? undefined}
onChange={handleStateChange}
disabled={disabled}
buttonVariant="border-with-text"
/>
</div>
<div className="h-5 flex-shrink-0">
<PriorityDropdown
value={issue.priority}
onChange={handlePriorityChange}
disabled={disabled}
buttonVariant="border-without-text"
buttonClassName="border"
/>
</div>
<div className="h-5 flex-shrink-0">
<MemberDropdown
value={issue.assignee_ids}
projectId={issue.project_id ?? undefined}
onChange={handleAssigneeChange}
disabled={disabled}
multiple
buttonVariant={(issue?.assignee_ids || []).length > 0 ? "transparent-without-text" : "border-without-text"}
buttonClassName={(issue?.assignee_ids || []).length > 0 ? "hover:bg-transparent px-0" : ""}
/>
</div>
</div>
);
});