forked from github/plane
fix: merge conflicts
This commit is contained in:
commit
47866fb511
@ -33,14 +33,9 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
- space/**
|
- space/**
|
||||||
|
|
||||||
- name: Setup .npmrc for repository
|
|
||||||
run: |
|
|
||||||
echo -e "@tiptap-pro:registry=https://registry.tiptap.dev/\n//registry.tiptap.dev/:_authToken=${{ secrets.TIPTAP_TOKEN }}" > .npmrc
|
|
||||||
|
|
||||||
- name: Build Plane's Main App
|
- name: Build Plane's Main App
|
||||||
if: steps.changed-files.outputs.web_any_changed == 'true'
|
if: steps.changed-files.outputs.web_any_changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
mv ./.npmrc ./web
|
|
||||||
cd web
|
cd web
|
||||||
yarn
|
yarn
|
||||||
yarn build
|
yarn build
|
||||||
|
4
.github/workflows/Update_Docker_Images.yml
vendored
4
.github/workflows/Update_Docker_Images.yml
vendored
@ -22,10 +22,6 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Setup .npmrc for repository
|
|
||||||
run: |
|
|
||||||
echo -e "@tiptap-pro:registry=https://registry.tiptap.dev/\n//registry.tiptap.dev/:_authToken=${{ secrets.TIPTAP_TOKEN }}" > .npmrc
|
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker (Docker Hub) from Github Release
|
- name: Extract metadata (tags, labels) for Docker (Docker Hub) from Github Release
|
||||||
id: metaFrontend
|
id: metaFrontend
|
||||||
uses: docker/metadata-action@v4.3.0
|
uses: docker/metadata-action@v4.3.0
|
||||||
|
11
README.md
11
README.md
@ -59,17 +59,6 @@ chmod +x setup.sh
|
|||||||
|
|
||||||
> If running in a cloud env replace localhost with public facing IP address of the VM
|
> If running in a cloud env replace localhost with public facing IP address of the VM
|
||||||
|
|
||||||
- Setup Tiptap Pro
|
|
||||||
|
|
||||||
Visit [Tiptap Pro](https://collab.tiptap.dev/pro-extensions) and signup (it is free).
|
|
||||||
|
|
||||||
Create a **`.npmrc`** file, copy the following and replace your registry token generated from Tiptap Pro.
|
|
||||||
|
|
||||||
```
|
|
||||||
@tiptap-pro:registry=https://registry.tiptap.dev/
|
|
||||||
//registry.tiptap.dev/:_authToken=YOUR_REGISTRY_TOKEN
|
|
||||||
```
|
|
||||||
|
|
||||||
- Run Docker compose up
|
- Run Docker compose up
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
11
setup.sh
11
setup.sh
@ -11,14 +11,3 @@ cp ./apiserver/.env.example ./apiserver/.env
|
|||||||
|
|
||||||
# Generate the SECRET_KEY that will be used by django
|
# Generate the SECRET_KEY that will be used by django
|
||||||
echo -e "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env
|
echo -e "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env
|
||||||
|
|
||||||
# Generate Prompt for taking tiptap auth key
|
|
||||||
echo -e "\n\e[1;38m Instructions for generating TipTap Pro Extensions Auth Token \e[0m \n"
|
|
||||||
|
|
||||||
echo -e "\e[1;38m 1. Head over to TipTap cloud's Pro Extensions Page, https://collab.tiptap.dev/pro-extensions \e[0m"
|
|
||||||
echo -e "\e[1;38m 2. Copy the token given to you under the first paragraph, after 'Here it is' \e[0m \n"
|
|
||||||
|
|
||||||
read -p $'\e[1;32m Please Enter Your TipTap Pro Extensions Authentication Token: \e[0m \e[1;36m' authToken
|
|
||||||
|
|
||||||
echo "@tiptap-pro:registry=https://registry.tiptap.dev/
|
|
||||||
//registry.tiptap.dev/:_authToken=${authToken}" > .npmrc
|
|
@ -18,7 +18,6 @@ import Gapcursor from "@tiptap/extension-gapcursor";
|
|||||||
import ts from "highlight.js/lib/languages/typescript";
|
import ts from "highlight.js/lib/languages/typescript";
|
||||||
|
|
||||||
import "highlight.js/styles/github-dark.css";
|
import "highlight.js/styles/github-dark.css";
|
||||||
import UniqueID from "@tiptap-pro/extension-unique-id";
|
|
||||||
import UpdatedImage from "./updated-image";
|
import UpdatedImage from "./updated-image";
|
||||||
import isValidHttpUrl from "../bubble-menu/utils/link-validator";
|
import isValidHttpUrl from "../bubble-menu/utils/link-validator";
|
||||||
import { CustomTableCell } from "./table/table-cell";
|
import { CustomTableCell } from "./table/table-cell";
|
||||||
@ -121,9 +120,6 @@ export const TiptapExtensions = (
|
|||||||
},
|
},
|
||||||
includeChildren: true,
|
includeChildren: true,
|
||||||
}),
|
}),
|
||||||
UniqueID.configure({
|
|
||||||
types: ["image"],
|
|
||||||
}),
|
|
||||||
SlashCommand(workspaceSlug, setIsSubmitting),
|
SlashCommand(workspaceSlug, setIsSubmitting),
|
||||||
TiptapUnderline,
|
TiptapUnderline,
|
||||||
TextStyle,
|
TextStyle,
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
"@heroicons/react": "^2.0.12",
|
"@heroicons/react": "^2.0.12",
|
||||||
"@mui/icons-material": "^5.14.1",
|
"@mui/icons-material": "^5.14.1",
|
||||||
"@mui/material": "^5.14.1",
|
"@mui/material": "^5.14.1",
|
||||||
"@tiptap-pro/extension-unique-id": "^2.1.0",
|
|
||||||
"@tiptap/extension-code-block-lowlight": "^2.0.4",
|
"@tiptap/extension-code-block-lowlight": "^2.0.4",
|
||||||
"@tiptap/extension-color": "^2.0.4",
|
"@tiptap/extension-color": "^2.0.4",
|
||||||
"@tiptap/extension-gapcursor": "^2.1.7",
|
"@tiptap/extension-gapcursor": "^2.1.7",
|
||||||
|
@ -41,7 +41,7 @@ export const CommandPalette: React.FC = observer(() => {
|
|||||||
const [isCreateUpdatePageModalOpen, setIsCreateUpdatePageModalOpen] = useState(false);
|
const [isCreateUpdatePageModalOpen, setIsCreateUpdatePageModalOpen] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId, inboxId } = router.query;
|
const { workspaceSlug, projectId, issueId, inboxId, cycleId, moduleId } = router.query;
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
@ -183,6 +183,13 @@ export const CommandPalette: React.FC = observer(() => {
|
|||||||
isOpen={isIssueModalOpen}
|
isOpen={isIssueModalOpen}
|
||||||
handleClose={() => setIsIssueModalOpen(false)}
|
handleClose={() => setIsIssueModalOpen(false)}
|
||||||
fieldsToShow={inboxId ? ["name", "description", "priority"] : ["all"]}
|
fieldsToShow={inboxId ? ["name", "description", "priority"] : ["all"]}
|
||||||
|
prePopulateData={
|
||||||
|
cycleId
|
||||||
|
? { cycle: cycleId.toString() }
|
||||||
|
: moduleId
|
||||||
|
? { module: moduleId.toString() }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<BulkDeleteIssuesModal
|
<BulkDeleteIssuesModal
|
||||||
isOpen={isBulkDeleteIssuesModalOpen}
|
isOpen={isBulkDeleteIssuesModalOpen}
|
||||||
|
@ -2,8 +2,9 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
// icons
|
// icons
|
||||||
import { Icon, Tooltip } from "components/ui";
|
import { Icon, Tooltip } from "components/ui";
|
||||||
|
import { CopyPlus } from "lucide-react";
|
||||||
import { Squares2X2Icon } from "@heroicons/react/24/outline";
|
import { Squares2X2Icon } from "@heroicons/react/24/outline";
|
||||||
import { BlockedIcon, BlockerIcon } from "components/icons";
|
import { BlockedIcon, BlockerIcon, RelatedIcon } from "components/icons";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||||
import { capitalizeFirstLetter } from "helpers/string.helper";
|
import { capitalizeFirstLetter } from "helpers/string.helper";
|
||||||
@ -157,7 +158,7 @@ const activityDetails: {
|
|||||||
},
|
},
|
||||||
icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
|
icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
|
||||||
},
|
},
|
||||||
blocks: {
|
blocked_by: {
|
||||||
message: (activity) => {
|
message: (activity) => {
|
||||||
if (activity.old_value === "")
|
if (activity.old_value === "")
|
||||||
return (
|
return (
|
||||||
@ -176,6 +177,44 @@ const activityDetails: {
|
|||||||
},
|
},
|
||||||
icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
|
icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
|
||||||
},
|
},
|
||||||
|
duplicate: {
|
||||||
|
message: (activity) => {
|
||||||
|
if (activity.old_value === "")
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
marked this issue as duplicate of{" "}
|
||||||
|
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
removed this issue as a duplicate of{" "}
|
||||||
|
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: <CopyPlus size={12} color="#6b7280" />,
|
||||||
|
},
|
||||||
|
relates_to: {
|
||||||
|
message: (activity) => {
|
||||||
|
if (activity.old_value === "")
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
marked that this issue relates to{" "}
|
||||||
|
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
else
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
removed the relation from{" "}
|
||||||
|
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: <RelatedIcon height="12" width="12" color="#6b7280" />,
|
||||||
|
},
|
||||||
cycles: {
|
cycles: {
|
||||||
message: (activity, showIssue, workspaceSlug) => {
|
message: (activity, showIssue, workspaceSlug) => {
|
||||||
if (activity.verb === "created")
|
if (activity.verb === "created")
|
||||||
|
@ -23,7 +23,6 @@ import {
|
|||||||
CreateUpdateIssueModal,
|
CreateUpdateIssueModal,
|
||||||
DeleteIssueModal,
|
DeleteIssueModal,
|
||||||
DeleteDraftIssueModal,
|
DeleteDraftIssueModal,
|
||||||
IssuePeekOverview,
|
|
||||||
CreateUpdateDraftIssueModal,
|
CreateUpdateDraftIssueModal,
|
||||||
} from "components/issues";
|
} from "components/issues";
|
||||||
import { CreateUpdateViewModal } from "components/views";
|
import { CreateUpdateViewModal } from "components/views";
|
||||||
@ -484,15 +483,7 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
fieldsToShow={[
|
fieldsToShow={["all"]}
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"label",
|
|
||||||
"assignee",
|
|
||||||
"priority",
|
|
||||||
"dueDate",
|
|
||||||
"priority",
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
<CreateUpdateIssueModal
|
<CreateUpdateIssueModal
|
||||||
isOpen={editIssueModal && issueToEdit?.actionType !== "delete"}
|
isOpen={editIssueModal && issueToEdit?.actionType !== "delete"}
|
||||||
|
@ -50,12 +50,12 @@ export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeletion = async () => {
|
const handleDeletion = async () => {
|
||||||
if (!workspaceSlug || !data) return;
|
if (!workspaceSlug || !data || !user) return;
|
||||||
|
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
await issueServices
|
await issueServices
|
||||||
.deleteDraftIssue(workspaceSlug as string, data.project, data.id)
|
.deleteDraftIssue(workspaceSlug as string, data.project, data.id, user)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
handleClose();
|
handleClose();
|
||||||
|
@ -14,6 +14,7 @@ import useIssuesView from "hooks/use-issues-view";
|
|||||||
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
||||||
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
import useMyIssues from "hooks/my-issues/use-my-issues";
|
import useMyIssues from "hooks/my-issues/use-my-issues";
|
||||||
// components
|
// components
|
||||||
@ -79,6 +80,8 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { projects } = useProjects();
|
const { projects } = useProjects();
|
||||||
|
|
||||||
|
const { clearValue: clearDraftIssueLocalStorage } = useLocalStorage("draftedIssue", {});
|
||||||
|
|
||||||
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
|
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
@ -111,11 +114,14 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prePopulateData && prePopulateData.project)
|
||||||
|
return setActiveProject(prePopulateData.project);
|
||||||
|
|
||||||
// if data is not present, set active project to the project
|
// if data is not present, set active project to the project
|
||||||
// in the url. This has the least priority.
|
// in the url. This has the least priority.
|
||||||
if (projects && projects.length > 0 && !activeProject)
|
if (projects && projects.length > 0 && !activeProject)
|
||||||
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
||||||
}, [activeProject, data, projectId, projects, isOpen]);
|
}, [activeProject, data, projectId, projects, isOpen, prePopulateData]);
|
||||||
|
|
||||||
const calendarFetchKey = cycleId
|
const calendarFetchKey = cycleId
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams)
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams)
|
||||||
@ -228,6 +234,8 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
if (!data) await createIssue(payload);
|
if (!data) await createIssue(payload);
|
||||||
else await updateIssue(payload);
|
else await updateIssue(payload);
|
||||||
|
|
||||||
|
clearDraftIssueLocalStorage();
|
||||||
|
|
||||||
if (onSubmit) await onSubmit(payload);
|
if (onSubmit) await onSubmit(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
import aiService from "services/ai.service";
|
import aiService from "services/ai.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useLocalStorage from "hooks/use-local-storage";
|
|
||||||
// components
|
// components
|
||||||
import { GptAssistantModal } from "components/core";
|
import { GptAssistantModal } from "components/core";
|
||||||
import { ParentIssuesListModal, TIssueFormAttributes } from "components/issues";
|
import { ParentIssuesListModal, TIssueFormAttributes } from "components/issues";
|
||||||
@ -59,11 +58,9 @@ export interface IssueFormProps {
|
|||||||
setActiveProject: React.Dispatch<React.SetStateAction<string | null>>;
|
setActiveProject: React.Dispatch<React.SetStateAction<string | null>>;
|
||||||
createMore: boolean;
|
createMore: boolean;
|
||||||
setCreateMore: React.Dispatch<React.SetStateAction<boolean>>;
|
setCreateMore: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
handleClose: () => void;
|
|
||||||
handleDiscardClose: () => void;
|
handleDiscardClose: () => void;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
user: ICurrentUserResponse | undefined;
|
user: ICurrentUserResponse | undefined;
|
||||||
setIsConfirmDiscardOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
handleFormDirty: (payload: Partial<IIssue> | null) => void;
|
handleFormDirty: (payload: Partial<IIssue> | null) => void;
|
||||||
customAttributesList: { [key: string]: string[] };
|
customAttributesList: { [key: string]: string[] };
|
||||||
handleCustomAttributesChange: (attributeId: string, val: string | string[] | undefined) => void;
|
handleCustomAttributesChange: (attributeId: string, val: string | string[] | undefined) => void;
|
||||||
@ -95,8 +92,6 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||||||
const [gptAssistantModal, setGptAssistantModal] = useState(false);
|
const [gptAssistantModal, setGptAssistantModal] = useState(false);
|
||||||
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
|
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
|
||||||
|
|
||||||
const { setValue: setValueInLocalStorage } = useLocalStorage<any>("draftedIssue", null);
|
|
||||||
|
|
||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -127,9 +122,11 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||||||
state: getValues("state"),
|
state: getValues("state"),
|
||||||
priority: getValues("priority"),
|
priority: getValues("priority"),
|
||||||
assignees: getValues("assignees"),
|
assignees: getValues("assignees"),
|
||||||
target_date: getValues("target_date"),
|
|
||||||
labels: getValues("labels"),
|
labels: getValues("labels"),
|
||||||
|
start_date: getValues("start_date"),
|
||||||
|
target_date: getValues("target_date"),
|
||||||
project: getValues("project"),
|
project: getValues("project"),
|
||||||
|
parent: getValues("parent"),
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -582,8 +579,6 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const data = JSON.stringify(getValues());
|
|
||||||
setValueInLocalStorage(data);
|
|
||||||
handleDiscardClose();
|
handleDiscardClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -22,6 +22,7 @@ import useInboxView from "hooks/use-inbox-view";
|
|||||||
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
import useMyIssues from "hooks/my-issues/use-my-issues";
|
import useMyIssues from "hooks/my-issues/use-my-issues";
|
||||||
|
import useLocalStorage from "hooks/use-local-storage";
|
||||||
// components
|
// components
|
||||||
import { IssueForm, ConfirmIssueDiscard } from "components/issues";
|
import { IssueForm, ConfirmIssueDiscard } from "components/issues";
|
||||||
// types
|
// types
|
||||||
@ -105,10 +106,11 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
|
|
||||||
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
|
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
|
||||||
|
|
||||||
|
const { setValue: setValueInLocalStorage, clearValue: clearLocalStorageValue } =
|
||||||
|
useLocalStorage<any>("draftedIssue", {});
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string };
|
|
||||||
if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string };
|
|
||||||
if (router.asPath.includes("my-issues") || router.asPath.includes("assigned"))
|
if (router.asPath.includes("my-issues") || router.asPath.includes("assigned"))
|
||||||
prePopulateData = {
|
prePopulateData = {
|
||||||
...prePopulateData,
|
...prePopulateData,
|
||||||
@ -116,21 +118,21 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
|
if (!showConfirmDiscard) handleClose();
|
||||||
|
if (formDirtyState === null) return setActiveProject(null);
|
||||||
|
const data = JSON.stringify(formDirtyState);
|
||||||
|
setValueInLocalStorage(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDiscardClose = () => {
|
||||||
if (formDirtyState !== null) {
|
if (formDirtyState !== null) {
|
||||||
setShowConfirmDiscard(true);
|
setShowConfirmDiscard(true);
|
||||||
} else {
|
} else {
|
||||||
handleClose();
|
handleClose();
|
||||||
setActiveProject(null);
|
setActiveProject(null);
|
||||||
setCustomAttributesList({});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDiscardClose = () => {
|
|
||||||
handleClose();
|
|
||||||
setActiveProject(null);
|
|
||||||
setCustomAttributesList({});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFormDirty = (data: any) => {
|
const handleFormDirty = (data: any) => {
|
||||||
setFormDirtyState(data);
|
setFormDirtyState(data);
|
||||||
};
|
};
|
||||||
@ -241,6 +243,27 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// if modal is closed, reset active project to null
|
||||||
|
// and return to avoid activeProject being set to some other project
|
||||||
|
if (!isOpen) {
|
||||||
|
setActiveProject(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if data is present, set active project to the project of the
|
||||||
|
// issue. This has more priority than the project in the url.
|
||||||
|
if (data && data.project) {
|
||||||
|
setActiveProject(data.project);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if data is not present, set active project to the project
|
||||||
|
// in the url. This has the least priority.
|
||||||
|
if (projects && projects.length > 0 && !activeProject)
|
||||||
|
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
||||||
|
}, [activeProject, data, projectId, projects, isOpen]);
|
||||||
|
|
||||||
const calendarFetchKey = cycleId
|
const calendarFetchKey = cycleId
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams)
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams)
|
||||||
: moduleId
|
: moduleId
|
||||||
@ -466,9 +489,9 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
setActiveProject(null);
|
setActiveProject(null);
|
||||||
setFormDirtyState(null);
|
setFormDirtyState(null);
|
||||||
setShowConfirmDiscard(false);
|
setShowConfirmDiscard(false);
|
||||||
|
clearLocalStorageValue();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||||
<Dialog as="div" className="relative z-20" onClose={onClose}>
|
<Dialog as="div" className="relative z-20" onClose={onClose}>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
@ -500,16 +523,13 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer(
|
|||||||
initialData={data ?? prePopulateData}
|
initialData={data ?? prePopulateData}
|
||||||
createMore={createMore}
|
createMore={createMore}
|
||||||
setCreateMore={setCreateMore}
|
setCreateMore={setCreateMore}
|
||||||
handleClose={onClose}
|
|
||||||
handleDiscardClose={onDiscardClose}
|
handleDiscardClose={onDiscardClose}
|
||||||
setIsConfirmDiscardOpen={setShowConfirmDiscard}
|
|
||||||
projectId={activeProject ?? ""}
|
projectId={activeProject ?? ""}
|
||||||
setActiveProject={setActiveProject}
|
setActiveProject={setActiveProject}
|
||||||
status={data ? true : false}
|
status={data ? true : false}
|
||||||
user={user}
|
user={user}
|
||||||
customAttributesList={customAttributesList}
|
|
||||||
handleCustomAttributesChange={handleCustomAttributesChange}
|
|
||||||
fieldsToShow={fieldsToShow}
|
fieldsToShow={fieldsToShow}
|
||||||
|
handleCustomAttributesChange={handleCustomAttributesChange}
|
||||||
handleFormDirty={handleFormDirty}
|
handleFormDirty={handleFormDirty}
|
||||||
/>
|
/>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
|
@ -73,7 +73,7 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
|
|||||||
...selectedIssues.map((issue) => ({
|
...selectedIssues.map((issue) => ({
|
||||||
issue: issueId as string,
|
issue: issueId as string,
|
||||||
relation_type: "blocked_by" as const,
|
relation_type: "blocked_by" as const,
|
||||||
related_issue_detail: issue.blocked_issue_detail,
|
issue_detail: issue.blocked_issue_detail,
|
||||||
related_issue: issue.blocked_issue_detail.id,
|
related_issue: issue.blocked_issue_detail.id,
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
@ -111,17 +111,17 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
|
|||||||
{blockedByIssue && blockedByIssue.length > 0
|
{blockedByIssue && blockedByIssue.length > 0
|
||||||
? blockedByIssue.map((relation) => (
|
? blockedByIssue.map((relation) => (
|
||||||
<div
|
<div
|
||||||
key={relation.related_issue_detail?.id}
|
key={relation?.id}
|
||||||
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-red-500 duration-300 hover:border-red-500/20 hover:bg-red-500/20"
|
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-red-500 duration-300 hover:border-red-500/20 hover:bg-red-500/20"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={`/${workspaceSlug}/projects/${relation.related_issue_detail?.project_detail.id}/issues/${relation.related_issue_detail?.id}`}
|
href={`/${workspaceSlug}/projects/${relation.issue_detail?.project_detail.id}/issues/${relation.issue_detail?.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<BlockedIcon height={10} width={10} />
|
<BlockedIcon height={10} width={10} />
|
||||||
{`${relation.related_issue_detail?.project_detail.identifier}-${relation.related_issue_detail?.sequence_id}`}
|
{`${relation.issue_detail?.project_detail.identifier}-${relation.issue_detail?.sequence_id}`}
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -81,6 +81,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
|
|||||||
related_issue_detail: issue.blocker_issue_detail,
|
related_issue_detail: issue.blocker_issue_detail,
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
|
relation: "blocking",
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
submitChanges({
|
submitChanges({
|
||||||
@ -89,7 +90,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
|
|||||||
...(response ?? []).map((i: any) => ({
|
...(response ?? []).map((i: any) => ({
|
||||||
id: i.id,
|
id: i.id,
|
||||||
relation_type: i.relation_type,
|
relation_type: i.relation_type,
|
||||||
issue_detail: i.related_issue_detail,
|
issue_detail: i.issue_detail,
|
||||||
issue: i.related_issue,
|
issue: i.related_issue,
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
@ -118,7 +119,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
|
|||||||
{blockerIssue && blockerIssue.length > 0
|
{blockerIssue && blockerIssue.length > 0
|
||||||
? blockerIssue.map((relation) => (
|
? blockerIssue.map((relation) => (
|
||||||
<div
|
<div
|
||||||
key={relation.issue_detail?.id}
|
key={relation.id}
|
||||||
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20"
|
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@ -134,9 +135,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
className="opacity-0 duration-300 group-hover:opacity-100"
|
className="opacity-0 duration-300 group-hover:opacity-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const updatedBlockers = blockerIssue.filter(
|
const updatedBlockers = blockerIssue.filter((i) => i.id !== relation.id);
|
||||||
(i) => i.issue_detail?.id !== relation.issue_detail?.id
|
|
||||||
);
|
|
||||||
|
|
||||||
submitChanges({
|
submitChanges({
|
||||||
issue_relations: updatedBlockers,
|
issue_relations: updatedBlockers,
|
||||||
|
@ -18,7 +18,7 @@ import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issueId?: string;
|
issueId?: string;
|
||||||
submitChanges: (formData: Partial<IIssue>) => void;
|
submitChanges: (formData?: Partial<IIssue>) => void;
|
||||||
watch: UseFormWatch<IIssue>;
|
watch: UseFormWatch<IIssue>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
@ -69,7 +69,7 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
|
|||||||
related_list: [
|
related_list: [
|
||||||
...selectedIssues.map((issue) => ({
|
...selectedIssues.map((issue) => ({
|
||||||
issue: issueId as string,
|
issue: issueId as string,
|
||||||
related_issue_detail: issue.blocker_issue_detail,
|
issue_detail: issue.blocker_issue_detail,
|
||||||
related_issue: issue.blocker_issue_detail.id,
|
related_issue: issue.blocker_issue_detail.id,
|
||||||
relation_type: "duplicate" as const,
|
relation_type: "duplicate" as const,
|
||||||
})),
|
})),
|
||||||
@ -90,7 +90,7 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
|
|||||||
?.filter((i) => i.relation_type === "duplicate")
|
?.filter((i) => i.relation_type === "duplicate")
|
||||||
.map((i) => ({
|
.map((i) => ({
|
||||||
...i,
|
...i,
|
||||||
related_issue_detail: i.issue_detail,
|
issue_detail: i.issue_detail,
|
||||||
related_issue: i.issue_detail?.id,
|
related_issue: i.issue_detail?.id,
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
@ -114,39 +114,35 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
|
|||||||
{duplicateIssuesRelation && duplicateIssuesRelation.length > 0
|
{duplicateIssuesRelation && duplicateIssuesRelation.length > 0
|
||||||
? duplicateIssuesRelation.map((relation) => (
|
? duplicateIssuesRelation.map((relation) => (
|
||||||
<div
|
<div
|
||||||
key={relation.related_issue_detail?.id}
|
key={relation.issue_detail?.id}
|
||||||
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20"
|
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={`/${workspaceSlug}/projects/${relation.related_issue_detail?.project_detail.id}/issues/${relation.related_issue_detail?.id}`}
|
href={`/${workspaceSlug}/projects/${relation.issue_detail?.project_detail.id}/issues/${relation.issue_detail?.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<BlockerIcon height={10} width={10} />
|
<BlockerIcon height={10} width={10} />
|
||||||
{`${relation.related_issue_detail?.project_detail.identifier}-${relation.related_issue_detail?.sequence_id}`}
|
{`${relation.issue_detail?.project_detail.identifier}-${relation.issue_detail?.sequence_id}`}
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="opacity-0 duration-300 group-hover:opacity-100"
|
className="opacity-0 duration-300 group-hover:opacity-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const updatedBlockers = duplicateIssuesRelation.filter(
|
|
||||||
(i) => i.related_issue_detail?.id !== relation.related_issue_detail?.id
|
|
||||||
);
|
|
||||||
|
|
||||||
submitChanges({
|
|
||||||
related_issues: updatedBlockers,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
issuesService.deleteIssueRelation(
|
issuesService
|
||||||
workspaceSlug as string,
|
.deleteIssueRelation(
|
||||||
projectId as string,
|
workspaceSlug as string,
|
||||||
issueId as string,
|
projectId as string,
|
||||||
relation.id,
|
issueId as string,
|
||||||
user
|
relation.id,
|
||||||
);
|
user
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
submitChanges();
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<X className="h-2 w-2" />
|
<X className="h-2 w-2" />
|
||||||
|
@ -18,7 +18,7 @@ import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issueId?: string;
|
issueId?: string;
|
||||||
submitChanges: (formData: Partial<IIssue>) => void;
|
submitChanges: (formData?: Partial<IIssue>) => void;
|
||||||
watch: UseFormWatch<IIssue>;
|
watch: UseFormWatch<IIssue>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
@ -69,7 +69,7 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
|
|||||||
related_list: [
|
related_list: [
|
||||||
...selectedIssues.map((issue) => ({
|
...selectedIssues.map((issue) => ({
|
||||||
issue: issueId as string,
|
issue: issueId as string,
|
||||||
related_issue_detail: issue.blocker_issue_detail,
|
issue_detail: issue.blocker_issue_detail,
|
||||||
related_issue: issue.blocker_issue_detail.id,
|
related_issue: issue.blocker_issue_detail.id,
|
||||||
relation_type: "relates_to" as const,
|
relation_type: "relates_to" as const,
|
||||||
})),
|
})),
|
||||||
@ -90,7 +90,7 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
|
|||||||
?.filter((i) => i.relation_type === "relates_to")
|
?.filter((i) => i.relation_type === "relates_to")
|
||||||
.map((i) => ({
|
.map((i) => ({
|
||||||
...i,
|
...i,
|
||||||
related_issue_detail: i.issue_detail,
|
issue_detail: i.issue_detail,
|
||||||
related_issue: i.issue_detail?.id,
|
related_issue: i.issue_detail?.id,
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
@ -114,39 +114,35 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
|
|||||||
{relatedToIssueRelation && relatedToIssueRelation.length > 0
|
{relatedToIssueRelation && relatedToIssueRelation.length > 0
|
||||||
? relatedToIssueRelation.map((relation) => (
|
? relatedToIssueRelation.map((relation) => (
|
||||||
<div
|
<div
|
||||||
key={relation.related_issue_detail?.id}
|
key={relation.issue_detail?.id}
|
||||||
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20"
|
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={`/${workspaceSlug}/projects/${relation.related_issue_detail?.project_detail.id}/issues/${relation.related_issue_detail?.id}`}
|
href={`/${workspaceSlug}/projects/${relation.issue_detail?.project_detail.id}/issues/${relation.issue_detail?.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<BlockerIcon height={10} width={10} />
|
<BlockerIcon height={10} width={10} />
|
||||||
{`${relation.related_issue_detail?.project_detail.identifier}-${relation.related_issue_detail?.sequence_id}`}
|
{`${relation.issue_detail?.project_detail.identifier}-${relation.issue_detail?.sequence_id}`}
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="opacity-0 duration-300 group-hover:opacity-100"
|
className="opacity-0 duration-300 group-hover:opacity-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const updatedBlockers = relatedToIssueRelation.filter(
|
|
||||||
(i) => i.related_issue_detail?.id !== relation.related_issue_detail?.id
|
|
||||||
);
|
|
||||||
|
|
||||||
submitChanges({
|
|
||||||
related_issues: updatedBlockers,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
issuesService.deleteIssueRelation(
|
issuesService
|
||||||
workspaceSlug as string,
|
.deleteIssueRelation(
|
||||||
projectId as string,
|
workspaceSlug as string,
|
||||||
issueId as string,
|
projectId as string,
|
||||||
relation.id,
|
issueId as string,
|
||||||
user
|
relation.id,
|
||||||
);
|
user
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
submitChanges();
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<X className="h-2 w-2" />
|
<X className="h-2 w-2" />
|
||||||
|
@ -514,17 +514,14 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarDuplicateSelect
|
<SidebarDuplicateSelect
|
||||||
issueId={issueId as string}
|
issueId={issueId as string}
|
||||||
submitChanges={(data: any) => {
|
submitChanges={(data: any) => {
|
||||||
mutate<IIssue>(
|
if (!data) return mutate(ISSUE_DETAILS(issueId as string));
|
||||||
ISSUE_DETAILS(issueId as string),
|
mutate<IIssue>(ISSUE_DETAILS(issueId as string), (prevData) => {
|
||||||
(prevData) => {
|
if (!prevData) return prevData;
|
||||||
if (!prevData) return prevData;
|
return {
|
||||||
return {
|
...prevData,
|
||||||
...prevData,
|
...data,
|
||||||
...data,
|
};
|
||||||
};
|
});
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
||||||
@ -534,17 +531,14 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
<SidebarRelatesSelect
|
<SidebarRelatesSelect
|
||||||
issueId={issueId as string}
|
issueId={issueId as string}
|
||||||
submitChanges={(data: any) => {
|
submitChanges={(data: any) => {
|
||||||
mutate<IIssue>(
|
if (!data) return mutate(ISSUE_DETAILS(issueId as string));
|
||||||
ISSUE_DETAILS(issueId as string),
|
mutate<IIssue>(ISSUE_DETAILS(issueId as string), (prevData) => {
|
||||||
(prevData) => {
|
if (!prevData) return prevData;
|
||||||
if (!prevData) return prevData;
|
return {
|
||||||
return {
|
...prevData,
|
||||||
...prevData,
|
...data,
|
||||||
...data,
|
};
|
||||||
};
|
});
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
watch={watchIssue}
|
watch={watchIssue}
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
||||||
|
@ -18,7 +18,6 @@ import Gapcursor from "@tiptap/extension-gapcursor";
|
|||||||
import ts from "highlight.js/lib/languages/typescript";
|
import ts from "highlight.js/lib/languages/typescript";
|
||||||
|
|
||||||
import "highlight.js/styles/github-dark.css";
|
import "highlight.js/styles/github-dark.css";
|
||||||
import UniqueID from "@tiptap-pro/extension-unique-id";
|
|
||||||
import UpdatedImage from "./updated-image";
|
import UpdatedImage from "./updated-image";
|
||||||
import isValidHttpUrl from "../bubble-menu/utils/link-validator";
|
import isValidHttpUrl from "../bubble-menu/utils/link-validator";
|
||||||
import { CustomTableCell } from "./table/table-cell";
|
import { CustomTableCell } from "./table/table-cell";
|
||||||
@ -121,9 +120,6 @@ export const TiptapExtensions = (
|
|||||||
},
|
},
|
||||||
includeChildren: true,
|
includeChildren: true,
|
||||||
}),
|
}),
|
||||||
UniqueID.configure({
|
|
||||||
types: ["image"],
|
|
||||||
}),
|
|
||||||
SlashCommand(workspaceSlug, setIsSubmitting),
|
SlashCommand(workspaceSlug, setIsSubmitting),
|
||||||
TiptapUnderline,
|
TiptapUnderline,
|
||||||
TextStyle,
|
TextStyle,
|
||||||
|
2
web/components/ui/buttons/type.d.ts
vendored
2
web/components/ui/buttons/type.d.ts
vendored
@ -1,7 +1,7 @@
|
|||||||
export type ButtonProps = {
|
export type ButtonProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: () => void;
|
onClick?: (e: any) => void;
|
||||||
type?: "button" | "submit" | "reset";
|
type?: "button" | "submit" | "reset";
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
|
import { CopyPlus } from "lucide-react";
|
||||||
import { Icon, Tooltip } from "components/ui";
|
import { Icon, Tooltip } from "components/ui";
|
||||||
import { Squares2X2Icon } from "@heroicons/react/24/outline";
|
import { Squares2X2Icon } from "@heroicons/react/24/outline";
|
||||||
import { BlockedIcon, BlockerIcon } from "components/icons";
|
import { BlockedIcon, BlockerIcon, RelatedIcon } from "components/icons";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||||
import { capitalizeFirstLetter } from "helpers/string.helper";
|
import { capitalizeFirstLetter } from "helpers/string.helper";
|
||||||
@ -117,7 +118,7 @@ const activityDetails: {
|
|||||||
icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
|
icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
|
||||||
},
|
},
|
||||||
|
|
||||||
blocks: {
|
blocked_by: {
|
||||||
message: (activity) => (
|
message: (activity) => (
|
||||||
<>
|
<>
|
||||||
{activity.old_value === ""
|
{activity.old_value === ""
|
||||||
@ -132,6 +133,36 @@ const activityDetails: {
|
|||||||
icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
|
icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
duplicate: {
|
||||||
|
message: (activity) => (
|
||||||
|
<>
|
||||||
|
{activity.old_value === ""
|
||||||
|
? "marked this issue as duplicate of "
|
||||||
|
: "removed this issue as a duplicate of "}
|
||||||
|
<span className="font-medium text-custom-text-100">
|
||||||
|
{activity.verb === "created" ? activity.new_value : activity.old_value}
|
||||||
|
</span>
|
||||||
|
.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
icon: <CopyPlus size={12} color="#6b7280" />,
|
||||||
|
},
|
||||||
|
|
||||||
|
relates_to: {
|
||||||
|
message: (activity) => (
|
||||||
|
<>
|
||||||
|
{activity.old_value === ""
|
||||||
|
? "marked that this issue relates to "
|
||||||
|
: "removed the relation from "}
|
||||||
|
<span className="font-medium text-custom-text-100">
|
||||||
|
{activity.old_value === "" ? activity.new_value : activity.old_value}
|
||||||
|
</span>
|
||||||
|
.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
icon: <RelatedIcon height="12" width="12" color="#6b7280" />,
|
||||||
|
},
|
||||||
|
|
||||||
cycles: {
|
cycles: {
|
||||||
message: (activity) => (
|
message: (activity) => (
|
||||||
<>
|
<>
|
||||||
|
@ -351,25 +351,23 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
|
|||||||
{blockedIssue &&
|
{blockedIssue &&
|
||||||
blockedIssue.map((issue) => (
|
blockedIssue.map((issue) => (
|
||||||
<div
|
<div
|
||||||
key={issue.related_issue_detail?.id}
|
key={issue.issue_detail?.id}
|
||||||
className="group inline-flex mr-1 cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-red-500 duration-300 hover:border-red-500/20 hover:bg-red-500/20"
|
className="group inline-flex mr-1 cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-red-500 duration-300 hover:border-red-500/20 hover:bg-red-500/20"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={`/${workspaceSlug}/projects/${issue.related_issue_detail?.project_detail.id}/issues/${issue.related_issue_detail?.id}`}
|
href={`/${workspaceSlug}/projects/${issue.issue_detail?.project_detail.id}/issues/${issue.issue_detail?.id}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<BlockedIcon height={10} width={10} />
|
<BlockedIcon height={10} width={10} />
|
||||||
{`${issue?.related_issue_detail?.project_detail?.identifier}-${issue?.related_issue_detail?.sequence_id}`}
|
{`${issue?.issue_detail?.project_detail?.identifier}-${issue?.issue_detail?.sequence_id}`}
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="duration-300"
|
className="duration-300"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const updatedBlocked = blockedIssue.filter(
|
const updatedBlocked = blockedIssue.filter((i) => i?.id !== issue?.id);
|
||||||
(i) => i.related_issue_detail?.id !== issue.related_issue_detail?.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
|
@ -17,7 +17,10 @@ export const WorkspaceSidebarQuickAction = () => {
|
|||||||
|
|
||||||
const [isDraftIssueModalOpen, setIsDraftIssueModalOpen] = useState(false);
|
const [isDraftIssueModalOpen, setIsDraftIssueModalOpen] = useState(false);
|
||||||
|
|
||||||
const { storedValue, clearValue } = useLocalStorage<any>("draftedIssue", null);
|
const { storedValue, clearValue } = useLocalStorage<any>(
|
||||||
|
"draftedIssue",
|
||||||
|
JSON.stringify(undefined)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -30,18 +33,7 @@ export const WorkspaceSidebarQuickAction = () => {
|
|||||||
clearValue();
|
clearValue();
|
||||||
setIsDraftIssueModalOpen(false);
|
setIsDraftIssueModalOpen(false);
|
||||||
}}
|
}}
|
||||||
fieldsToShow={[
|
fieldsToShow={["all"]}
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"label",
|
|
||||||
"assignee",
|
|
||||||
"priority",
|
|
||||||
"dueDate",
|
|
||||||
"priority",
|
|
||||||
"state",
|
|
||||||
"startDate",
|
|
||||||
"project",
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -50,7 +42,7 @@ export const WorkspaceSidebarQuickAction = () => {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex items-center justify-between w-full rounded cursor-pointer px-2 gap-1 ${
|
className={`flex items-center justify-between w-full rounded cursor-pointer px-2 gap-1 group ${
|
||||||
store?.theme?.sidebarCollapsed
|
store?.theme?.sidebarCollapsed
|
||||||
? "px-2 hover:bg-custom-sidebar-background-80"
|
? "px-2 hover:bg-custom-sidebar-background-80"
|
||||||
: "px-3 shadow border-[0.5px] border-custom-border-300"
|
: "px-3 shadow border-[0.5px] border-custom-border-300"
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
"@nivo/pie": "0.80.0",
|
"@nivo/pie": "0.80.0",
|
||||||
"@nivo/scatterplot": "0.80.0",
|
"@nivo/scatterplot": "0.80.0",
|
||||||
"@sentry/nextjs": "^7.36.0",
|
"@sentry/nextjs": "^7.36.0",
|
||||||
"@tiptap-pro/extension-unique-id": "^2.1.0",
|
|
||||||
"@tiptap/extension-code-block-lowlight": "^2.0.4",
|
"@tiptap/extension-code-block-lowlight": "^2.0.4",
|
||||||
"@tiptap/extension-color": "^2.0.4",
|
"@tiptap/extension-color": "^2.0.4",
|
||||||
"@tiptap/extension-gapcursor": "^2.1.7",
|
"@tiptap/extension-gapcursor": "^2.1.7",
|
||||||
|
@ -154,6 +154,7 @@ class ProjectIssuesServices extends APIService {
|
|||||||
relation_type: "duplicate" | "relates_to" | "blocked_by";
|
relation_type: "duplicate" | "relates_to" | "blocked_by";
|
||||||
related_issue: string;
|
related_issue: string;
|
||||||
}>;
|
}>;
|
||||||
|
relation?: "blocking" | null;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
return this.post(
|
return this.post(
|
||||||
@ -658,7 +659,12 @@ class ProjectIssuesServices extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteDraftIssue(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
async deleteDraftIssue(
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string,
|
||||||
|
user: ICurrentUserResponse
|
||||||
|
): Promise<any> {
|
||||||
return this.delete(
|
return this.delete(
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`
|
`/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-drafts/${issueId}/`
|
||||||
)
|
)
|
||||||
|
189
web/store/draft-issue.ts
Normal file
189
web/store/draft-issue.ts
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
// mobx
|
||||||
|
import { action, observable, runInAction, makeAutoObservable } from "mobx";
|
||||||
|
// services
|
||||||
|
import issueService from "services/issues.service";
|
||||||
|
// types
|
||||||
|
import type { ICurrentUserResponse, IIssue } from "types";
|
||||||
|
|
||||||
|
class DraftIssuesStore {
|
||||||
|
issues: { [key: string]: IIssue } = {};
|
||||||
|
isIssuesLoading: boolean = false;
|
||||||
|
rootStore: any | null = null;
|
||||||
|
|
||||||
|
constructor(_rootStore: any | null = null) {
|
||||||
|
makeAutoObservable(this, {
|
||||||
|
issues: observable.ref,
|
||||||
|
isIssuesLoading: observable.ref,
|
||||||
|
rootStore: observable.ref,
|
||||||
|
loadDraftIssues: action,
|
||||||
|
getIssueById: action,
|
||||||
|
createDraftIssue: action,
|
||||||
|
updateDraftIssue: action,
|
||||||
|
deleteDraftIssue: action,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rootStore = _rootStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Fetch all draft issues of a project and hydrate issues field
|
||||||
|
*/
|
||||||
|
|
||||||
|
loadDraftIssues = async (workspaceSlug: string, projectId: string, params?: any) => {
|
||||||
|
this.isIssuesLoading = true;
|
||||||
|
try {
|
||||||
|
const issuesResponse = await issueService.getDraftIssues(workspaceSlug, projectId, params);
|
||||||
|
|
||||||
|
const issues = Array.isArray(issuesResponse) ? { allIssues: issuesResponse } : issuesResponse;
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = issues;
|
||||||
|
this.isIssuesLoading = false;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.isIssuesLoading = false;
|
||||||
|
console.error("Fetching issues error", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Fetch a single draft issue by id and hydrate issues field
|
||||||
|
* @param workspaceSlug
|
||||||
|
* @param projectId
|
||||||
|
* @param issueId
|
||||||
|
* @returns {IIssue}
|
||||||
|
*/
|
||||||
|
|
||||||
|
getIssueById = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string
|
||||||
|
): Promise<IIssue> => {
|
||||||
|
if (this.issues[issueId]) return this.issues[issueId];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const issueResponse: IIssue = await issueService.getDraftIssueById(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueId
|
||||||
|
);
|
||||||
|
|
||||||
|
const issues = {
|
||||||
|
...this.issues,
|
||||||
|
[issueId]: { ...issueResponse },
|
||||||
|
};
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = issues;
|
||||||
|
});
|
||||||
|
|
||||||
|
return issueResponse;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Create a new draft issue and hydrate issues field
|
||||||
|
* @param workspaceSlug
|
||||||
|
* @param projectId
|
||||||
|
* @param issueForm
|
||||||
|
* @param user
|
||||||
|
* @returns {IIssue}
|
||||||
|
*/
|
||||||
|
|
||||||
|
createDraftIssue = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueForm: IIssue,
|
||||||
|
user: ICurrentUserResponse
|
||||||
|
): Promise<IIssue> => {
|
||||||
|
try {
|
||||||
|
const issueResponse = await issueService.createDraftIssue(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueForm,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
|
||||||
|
const issues = {
|
||||||
|
...this.issues,
|
||||||
|
[issueResponse.id]: { ...issueResponse },
|
||||||
|
};
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = issues;
|
||||||
|
});
|
||||||
|
return issueResponse;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Creating issue error", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDraftIssue = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string,
|
||||||
|
issueForm: Partial<IIssue>,
|
||||||
|
user: ICurrentUserResponse
|
||||||
|
) => {
|
||||||
|
// keep a copy of the issue in the store
|
||||||
|
const originalIssue = { ...this.issues[issueId] };
|
||||||
|
|
||||||
|
// immediately update the issue in the store
|
||||||
|
const updatedIssue = { ...this.issues[issueId], ...issueForm };
|
||||||
|
if (updatedIssue.assignees_list) updatedIssue.assignees = updatedIssue.assignees_list;
|
||||||
|
|
||||||
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues[issueId] = { ...updatedIssue };
|
||||||
|
});
|
||||||
|
|
||||||
|
// make a patch request to update the issue
|
||||||
|
const issueResponse: IIssue = await issueService.updateDraftIssue(
|
||||||
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueId,
|
||||||
|
issueForm,
|
||||||
|
user
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedIssues = { ...this.issues };
|
||||||
|
updatedIssues[issueId] = { ...issueResponse };
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = updatedIssues;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// if there is an error, revert the changes
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues[issueId] = originalIssue;
|
||||||
|
});
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteDraftIssue = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string,
|
||||||
|
user: ICurrentUserResponse
|
||||||
|
) => {
|
||||||
|
const issues = { ...this.issues };
|
||||||
|
delete issues[issueId];
|
||||||
|
|
||||||
|
try {
|
||||||
|
runInAction(() => {
|
||||||
|
this.issues = issues;
|
||||||
|
});
|
||||||
|
|
||||||
|
issueService.deleteDraftIssue(workspaceSlug, projectId, issueId, user);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Deleting issue error", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DraftIssuesStore;
|
@ -8,6 +8,7 @@ import ProjectPublishStore, { IProjectPublishStore } from "./project-publish";
|
|||||||
import IssuesStore from "./issues";
|
import IssuesStore from "./issues";
|
||||||
import CustomAttributesStore from "./custom-attributes";
|
import CustomAttributesStore from "./custom-attributes";
|
||||||
import CustomAttributeValuesStore from "./custom-attribute-values";
|
import CustomAttributeValuesStore from "./custom-attribute-values";
|
||||||
|
import DraftIssuesStore from "./draft-issue";
|
||||||
|
|
||||||
enableStaticRendering(typeof window === "undefined");
|
enableStaticRendering(typeof window === "undefined");
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ export class RootStore {
|
|||||||
issues: IssuesStore;
|
issues: IssuesStore;
|
||||||
customAttributes: CustomAttributesStore;
|
customAttributes: CustomAttributesStore;
|
||||||
customAttributeValues: CustomAttributeValuesStore;
|
customAttributeValues: CustomAttributeValuesStore;
|
||||||
|
draftIssuesStore: DraftIssuesStore;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.user = new UserStore(this);
|
this.user = new UserStore(this);
|
||||||
@ -28,5 +30,6 @@ export class RootStore {
|
|||||||
this.issues = new IssuesStore(this);
|
this.issues = new IssuesStore(this);
|
||||||
this.customAttributes = new CustomAttributesStore(this);
|
this.customAttributes = new CustomAttributesStore(this);
|
||||||
this.customAttributeValues = new CustomAttributeValuesStore(this);
|
this.customAttributeValues = new CustomAttributeValuesStore(this);
|
||||||
|
this.draftIssuesStore = new DraftIssuesStore(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
web/types/issues.d.ts
vendored
6
web/types/issues.d.ts
vendored
@ -70,8 +70,10 @@ export type IssueRelationType = "duplicate" | "relates_to" | "blocked_by";
|
|||||||
export interface IssueRelation {
|
export interface IssueRelation {
|
||||||
id: string;
|
id: string;
|
||||||
issue: string;
|
issue: string;
|
||||||
related_issue: string;
|
issue_detail: BlockeIssueDetail;
|
||||||
relation_type: IssueRelationType;
|
relation_type: IssueRelationType;
|
||||||
|
related_issue: string;
|
||||||
|
relation: "blocking" | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIssue {
|
export interface IIssue {
|
||||||
@ -96,6 +98,8 @@ export interface IIssue {
|
|||||||
relation_type: IssueRelationType;
|
relation_type: IssueRelationType;
|
||||||
related_issue: string;
|
related_issue: string;
|
||||||
}[];
|
}[];
|
||||||
|
issue_relations: IssueRelation[];
|
||||||
|
related_issues: IssueRelation[];
|
||||||
bridge_id?: string | null;
|
bridge_id?: string | null;
|
||||||
completed_at: Date;
|
completed_at: Date;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
|
12
yarn.lock
12
yarn.lock
@ -2174,13 +2174,6 @@
|
|||||||
lodash.merge "^4.6.2"
|
lodash.merge "^4.6.2"
|
||||||
postcss-selector-parser "6.0.10"
|
postcss-selector-parser "6.0.10"
|
||||||
|
|
||||||
"@tiptap-pro/extension-unique-id@^2.1.0":
|
|
||||||
version "2.2.3"
|
|
||||||
resolved "https://registry.tiptap.dev/@tiptap-pro%2fextension-unique-id/-/extension-unique-id-2.2.3.tgz#151a570ef8363bf460bf5b08dc0581fb182ebabc"
|
|
||||||
integrity sha512-Y1jM+6hebNltFZ+0fbC+NcOCU647KjRtqJ7jEZVFoP12ZMocZNqTTabsSIys0UXOkSaJy3/H6Z/ybygAHY/dBg==
|
|
||||||
dependencies:
|
|
||||||
uuid "^8.3.2"
|
|
||||||
|
|
||||||
"@tiptap/core@^2.1.8":
|
"@tiptap/core@^2.1.8":
|
||||||
version "2.1.8"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.8.tgz#4555dc7d86580dee790d4aded1ce7fb79319da70"
|
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.8.tgz#4555dc7d86580dee790d4aded1ce7fb79319da70"
|
||||||
@ -8188,11 +8181,6 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||||
|
|
||||||
uuid@^8.3.2:
|
|
||||||
version "8.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
|
||||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
|
||||||
|
|
||||||
uuid@^9.0.0:
|
uuid@^9.0.0:
|
||||||
version "9.0.1"
|
version "9.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
|
||||||
|
Loading…
Reference in New Issue
Block a user