From 85a8af5125012df46767128bafd1871b9fd05b0f Mon Sep 17 00:00:00 2001
From: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
Date: Fri, 16 Feb 2024 18:20:44 +0530
Subject: [PATCH 01/11] fix: spreadsheet views sorting (#3683)
* fix sorting in spreadsheet all issues
* removing focus border since it is being handled globally
---
.../issues/issue-layouts/roots/all-issue-layout-root.tsx | 2 +-
.../issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx | 2 +-
.../issues/issue-layouts/spreadsheet/issue-column.tsx | 2 +-
.../issue-layouts/spreadsheet/spreadsheet-header-column.tsx | 2 +-
web/store/issue/helpers/issue-helper.store.ts | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx
index 59cf5b9af1..1a77ed5faa 100644
--- a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx
+++ b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx
@@ -159,7 +159,7 @@ export const AllIssueLayoutRoot: React.FC = observer(() => {
globalViewId.toString()
);
},
- [updateFilters, workspaceSlug]
+ [updateFilters, workspaceSlug, globalViewId]
);
const renderQuickActions = useCallback(
diff --git a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx
index e4efc51374..a94455a0b0 100644
--- a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx
+++ b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx
@@ -88,7 +88,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
viewId
);
},
- [issueFiltersStore, projectId, workspaceSlug, viewId]
+ [issueFiltersStore?.updateFilters, projectId, workspaceSlug, viewId]
);
const renderQuickActions = useCallback(
diff --git a/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx b/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx
index 5d2e62fa55..20dd946dfc 100644
--- a/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx
+++ b/web/components/issues/issue-layouts/spreadsheet/issue-column.tsx
@@ -38,7 +38,7 @@ export const IssueColumn = observer((props: Props) => {
>
{
shouldRenderProperty={shouldRenderProperty}
>
diff --git a/web/store/issue/helpers/issue-helper.store.ts b/web/store/issue/helpers/issue-helper.store.ts
index ff5dba9dd2..3fca8aae8b 100644
--- a/web/store/issue/helpers/issue-helper.store.ts
+++ b/web/store/issue/helpers/issue-helper.store.ts
@@ -202,7 +202,7 @@ export class IssueHelperStore implements TIssueHelperStore {
if (!memberMap) break;
for (const dataId of dataIdsArray) {
const member = memberMap[dataId];
- if (memberMap && member.first_name) dataValues.push(member.first_name.toLocaleLowerCase());
+ if (member && member.first_name) dataValues.push(member.first_name.toLocaleLowerCase());
}
break;
}
From 665a07f15ab98b33291dcc3b57b21a76ec43de7a Mon Sep 17 00:00:00 2001
From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Date: Fri, 16 Feb 2024 20:01:58 +0530
Subject: [PATCH 02/11] chore: dropdown and peek overview improvement (#3682)
* chore: dropdown focus state improvement
* fix: peek overview dropdown placement fix
---
web/components/dropdowns/cycle.tsx | 19 ++++++++++---------
web/components/dropdowns/estimate.tsx | 13 ++++++++++---
.../dropdowns/member/project-member.tsx | 13 ++++++++++---
.../dropdowns/member/workspace-member.tsx | 17 ++++++++++-------
web/components/dropdowns/module.tsx | 19 ++++++++++---------
web/components/dropdowns/priority.tsx | 17 ++++++++++-------
web/components/dropdowns/project.tsx | 17 ++++++++++-------
web/components/dropdowns/state.tsx | 13 ++++++++++---
web/components/issues/peek-overview/view.tsx | 14 +++-----------
web/components/issues/select/label.tsx | 11 ++++++++++-
10 files changed, 93 insertions(+), 60 deletions(-)
diff --git a/web/components/dropdowns/cycle.tsx b/web/components/dropdowns/cycle.tsx
index e3aa6df11d..5086d2d26b 100644
--- a/web/components/dropdowns/cycle.tsx
+++ b/web/components/dropdowns/cycle.tsx
@@ -61,6 +61,7 @@ export const CycleDropdown: React.FC = observer((props) => {
const [isOpen, setIsOpen] = useState(false);
// refs
const dropdownRef = useRef(null);
+ const inputRef = useRef(null);
// popper-js refs
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
@@ -111,23 +112,15 @@ export const CycleDropdown: React.FC = observer((props) => {
const filteredOptions =
query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
- // fetch cycles of the project if not already present in the store
- useEffect(() => {
- if (!workspaceSlug) return;
-
- if (!cycleIds) fetchAllCycles(workspaceSlug, projectId);
- }, [cycleIds, fetchAllCycles, projectId, workspaceSlug]);
-
const selectedCycle = value ? getCycleById(value) : null;
const onOpen = () => {
- if (referenceElement) referenceElement.focus();
+ if (workspaceSlug && !cycleIds) fetchAllCycles(workspaceSlug, projectId);
};
const handleClose = () => {
if (!isOpen) return;
setIsOpen(false);
- if (referenceElement) referenceElement.blur();
onClose && onClose();
};
@@ -151,6 +144,12 @@ export const CycleDropdown: React.FC = observer((props) => {
useOutsideClickDetector(dropdownRef, handleClose);
+ useEffect(() => {
+ if (isOpen && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isOpen]);
+
return (
= observer((props) => {
setQuery(e.target.value)}
diff --git a/web/components/dropdowns/estimate.tsx b/web/components/dropdowns/estimate.tsx
index 48558a683f..2674fa902d 100644
--- a/web/components/dropdowns/estimate.tsx
+++ b/web/components/dropdowns/estimate.tsx
@@ -1,4 +1,4 @@
-import { Fragment, ReactNode, useRef, useState } from "react";
+import { Fragment, ReactNode, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import { Combobox } from "@headlessui/react";
import { usePopper } from "react-popper";
@@ -60,6 +60,7 @@ export const EstimateDropdown: React.FC = observer((props) => {
const [isOpen, setIsOpen] = useState(false);
// refs
const dropdownRef = useRef(null);
+ const inputRef = useRef(null);
// popper-js refs
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
@@ -110,13 +111,11 @@ export const EstimateDropdown: React.FC = observer((props) => {
const onOpen = () => {
if (!activeEstimate && workspaceSlug) fetchProjectEstimates(workspaceSlug, projectId);
- if (referenceElement) referenceElement.focus();
};
const handleClose = () => {
if (!isOpen) return;
setIsOpen(false);
- if (referenceElement) referenceElement.blur();
onClose && onClose();
};
@@ -140,6 +139,12 @@ export const EstimateDropdown: React.FC = observer((props) => {
useOutsideClickDetector(dropdownRef, handleClose);
+ useEffect(() => {
+ if (isOpen && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isOpen]);
+
return (
= observer((props) => {
setQuery(e.target.value)}
diff --git a/web/components/dropdowns/member/project-member.tsx b/web/components/dropdowns/member/project-member.tsx
index 44cd3a7016..d1f285aa55 100644
--- a/web/components/dropdowns/member/project-member.tsx
+++ b/web/components/dropdowns/member/project-member.tsx
@@ -1,4 +1,4 @@
-import { Fragment, useRef, useState } from "react";
+import { Fragment, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import { Combobox } from "@headlessui/react";
import { usePopper } from "react-popper";
@@ -50,6 +50,7 @@ export const ProjectMemberDropdown: React.FC = observer((props) => {
const [isOpen, setIsOpen] = useState(false);
// refs
const dropdownRef = useRef(null);
+ const inputRef = useRef(null);
// popper-js refs
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
@@ -103,13 +104,11 @@ export const ProjectMemberDropdown: React.FC = observer((props) => {
const onOpen = () => {
if (!projectMemberIds && workspaceSlug) fetchProjectMembers(workspaceSlug, projectId);
- if (referenceElement) referenceElement.focus();
};
const handleClose = () => {
if (!isOpen) return;
setIsOpen(false);
- if (referenceElement) referenceElement.blur();
onClose && onClose();
};
@@ -133,6 +132,12 @@ export const ProjectMemberDropdown: React.FC = observer((props) => {
useOutsideClickDetector(dropdownRef, handleClose);
+ useEffect(() => {
+ if (isOpen && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isOpen]);
+
return (
= observer((props) => {
setQuery(e.target.value)}
diff --git a/web/components/dropdowns/member/workspace-member.tsx b/web/components/dropdowns/member/workspace-member.tsx
index d126b60f72..7a2628ccaf 100644
--- a/web/components/dropdowns/member/workspace-member.tsx
+++ b/web/components/dropdowns/member/workspace-member.tsx
@@ -1,4 +1,4 @@
-import { Fragment, useRef, useState } from "react";
+import { Fragment, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import { Combobox } from "@headlessui/react";
import { usePopper } from "react-popper";
@@ -44,6 +44,7 @@ export const WorkspaceMemberDropdown: React.FC = observer((
const [isOpen, setIsOpen] = useState(false);
// refs
const dropdownRef = useRef(null);
+ const inputRef = useRef(null);
// popper-js refs
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
@@ -91,19 +92,13 @@ export const WorkspaceMemberDropdown: React.FC = observer((
};
if (multiple) comboboxProps.multiple = true;
- const onOpen = () => {
- if (referenceElement) referenceElement.focus();
- };
-
const handleClose = () => {
if (!isOpen) return;
setIsOpen(false);
- if (referenceElement) referenceElement.blur();
onClose && onClose();
};
const toggleDropdown = () => {
- if (!isOpen) onOpen();
setIsOpen((prevIsOpen) => !prevIsOpen);
};
@@ -122,6 +117,12 @@ export const WorkspaceMemberDropdown: React.FC = observer((
useOutsideClickDetector(dropdownRef, handleClose);
+ useEffect(() => {
+ if (isOpen && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isOpen]);
+
return (
= observer((
setQuery(e.target.value)}
diff --git a/web/components/dropdowns/module.tsx b/web/components/dropdowns/module.tsx
index a9b64c1f1f..c05eeb97e6 100644
--- a/web/components/dropdowns/module.tsx
+++ b/web/components/dropdowns/module.tsx
@@ -166,6 +166,7 @@ export const ModuleDropdown: React.FC = observer((props) => {
const [isOpen, setIsOpen] = useState(false);
// refs
const dropdownRef = useRef(null);
+ const inputRef = useRef(null);
// popper-js refs
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
@@ -216,21 +217,13 @@ export const ModuleDropdown: React.FC = observer((props) => {
const filteredOptions =
query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
- // fetch modules of the project if not already present in the store
- useEffect(() => {
- if (!workspaceSlug) return;
-
- if (!moduleIds) fetchModules(workspaceSlug, projectId);
- }, [moduleIds, fetchModules, projectId, workspaceSlug]);
-
const onOpen = () => {
- if (referenceElement) referenceElement.focus();
+ if (!moduleIds && workspaceSlug) fetchModules(workspaceSlug, projectId);
};
const handleClose = () => {
if (!isOpen) return;
setIsOpen(false);
- if (referenceElement) referenceElement.blur();
onClose && onClose();
};
@@ -261,6 +254,12 @@ export const ModuleDropdown: React.FC = observer((props) => {
};
if (multiple) comboboxProps.multiple = true;
+ useEffect(() => {
+ if (isOpen && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isOpen]);
+
return (
= observer((props) => {
setQuery(e.target.value)}
diff --git a/web/components/dropdowns/priority.tsx b/web/components/dropdowns/priority.tsx
index 1bab9a21e9..d519ad9f18 100644
--- a/web/components/dropdowns/priority.tsx
+++ b/web/components/dropdowns/priority.tsx
@@ -1,4 +1,4 @@
-import { Fragment, ReactNode, useRef, useState } from "react";
+import { Fragment, ReactNode, useEffect, useRef, useState } from "react";
import { Combobox } from "@headlessui/react";
import { usePopper } from "react-popper";
import { Check, ChevronDown, Search } from "lucide-react";
@@ -272,6 +272,7 @@ export const PriorityDropdown: React.FC = (props) => {
const [isOpen, setIsOpen] = useState(false);
// refs
const dropdownRef = useRef(null);
+ const inputRef = useRef(null);
// popper-js refs
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
@@ -305,19 +306,13 @@ export const PriorityDropdown: React.FC = (props) => {
const filteredOptions =
query === "" ? options : options.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
- const onOpen = () => {
- if (referenceElement) referenceElement.focus();
- };
-
const handleClose = () => {
if (!isOpen) return;
setIsOpen(false);
- if (referenceElement) referenceElement.blur();
onClose && onClose();
};
const toggleDropdown = () => {
- if (!isOpen) onOpen();
setIsOpen((prevIsOpen) => !prevIsOpen);
};
@@ -342,6 +337,12 @@ export const PriorityDropdown: React.FC = (props) => {
? BackgroundButton
: TransparentButton;
+ useEffect(() => {
+ if (isOpen && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isOpen]);
+
return (
= (props) => {
setQuery(e.target.value)}
diff --git a/web/components/dropdowns/project.tsx b/web/components/dropdowns/project.tsx
index 7991c44024..f6fb9205e8 100644
--- a/web/components/dropdowns/project.tsx
+++ b/web/components/dropdowns/project.tsx
@@ -1,4 +1,4 @@
-import { Fragment, ReactNode, useRef, useState } from "react";
+import { Fragment, ReactNode, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import { Combobox } from "@headlessui/react";
import { usePopper } from "react-popper";
@@ -50,6 +50,7 @@ export const ProjectDropdown: React.FC = observer((props) => {
const [isOpen, setIsOpen] = useState(false);
// refs
const dropdownRef = useRef(null);
+ const inputRef = useRef(null);
// popper-js refs
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
@@ -94,19 +95,13 @@ export const ProjectDropdown: React.FC = observer((props) => {
const selectedProject = value ? getProjectById(value) : null;
- const onOpen = () => {
- if (referenceElement) referenceElement.focus();
- };
-
const handleClose = () => {
if (!isOpen) return;
setIsOpen(false);
onClose && onClose();
- if (referenceElement) referenceElement.blur();
};
const toggleDropdown = () => {
- if (!isOpen) onOpen();
setIsOpen((prevIsOpen) => !prevIsOpen);
};
@@ -125,6 +120,12 @@ export const ProjectDropdown: React.FC = observer((props) => {
useOutsideClickDetector(dropdownRef, handleClose);
+ useEffect(() => {
+ if (isOpen && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isOpen]);
+
return (
= observer((props) => {
setQuery(e.target.value)}
diff --git a/web/components/dropdowns/state.tsx b/web/components/dropdowns/state.tsx
index a7f54adfbe..fa068fdd01 100644
--- a/web/components/dropdowns/state.tsx
+++ b/web/components/dropdowns/state.tsx
@@ -1,4 +1,4 @@
-import { Fragment, ReactNode, useRef, useState } from "react";
+import { Fragment, ReactNode, useEffect, useRef, useState } from "react";
import { observer } from "mobx-react-lite";
import { Combobox } from "@headlessui/react";
import { usePopper } from "react-popper";
@@ -52,6 +52,7 @@ export const StateDropdown: React.FC = observer((props) => {
const [isOpen, setIsOpen] = useState(false);
// refs
const dropdownRef = useRef(null);
+ const inputRef = useRef(null);
// popper-js refs
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
@@ -92,14 +93,12 @@ export const StateDropdown: React.FC = observer((props) => {
const onOpen = () => {
if (!statesList && workspaceSlug) fetchProjectStates(workspaceSlug, projectId);
- if (referenceElement) referenceElement.focus();
};
const handleClose = () => {
if (!isOpen) return;
setIsOpen(false);
onClose && onClose();
- if (referenceElement) referenceElement.blur();
};
const toggleDropdown = () => {
@@ -122,6 +121,12 @@ export const StateDropdown: React.FC = observer((props) => {
useOutsideClickDetector(dropdownRef, handleClose);
+ useEffect(() => {
+ if (isOpen && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isOpen]);
+
return (
= observer((props) => {
setQuery(e.target.value)}
diff --git a/web/components/issues/peek-overview/view.tsx b/web/components/issues/peek-overview/view.tsx
index 82bda41d5f..7b6c851ffd 100644
--- a/web/components/issues/peek-overview/view.tsx
+++ b/web/components/issues/peek-overview/view.tsx
@@ -126,7 +126,7 @@ export const IssueView: FC = observer((props) => {
/>
)}
-
+
{issueId && (
= observer((props) => {
disabled={disabled}
/>
-
+
) : (
@@ -250,11 +246,7 @@ export const IssueView: FC = observer((props) => {
setIsSubmitting={(value) => setIsSubmitting(value)}
/>
-
+
= observer((props) => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
// refs
const dropdownRef = useRef (null);
+ const inputRef = useRef(null);
// popper
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: "bottom-start",
@@ -76,6 +77,12 @@ export const IssueLabelSelect: React.FC = observer((props) => {
useOutsideClickDetector(dropdownRef, handleClose);
+ useEffect(() => {
+ if (isDropdownOpen && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isDropdownOpen]);
+
return (
= observer((props) => {
setQuery(event.target.value)}
placeholder="Search"
From a94c60703195379ef91f37d3c87b5c4343adc0bc Mon Sep 17 00:00:00 2001
From: guru_sainath
Date: Fri, 16 Feb 2024 20:07:04 +0530
Subject: [PATCH 03/11] fix: updated issue title and description components
(#3687)
* fix: issue description fixes
* chore: description html in the archive issue
* chore: changed retrieve viewset
* chore: implemented new issue title description components in inbox, issue-detail and fixed issue in archived store
* chore: removed consoles and empty description update in issue detail
* fix: draft issue empty state image
---------
Co-authored-by: sriram veeraghanta
Co-authored-by: NarayanBavisetti
Co-authored-by: Anmol Singh Bhatia
---
apiserver/plane/app/views/issue.py | 12 +--
web/components/issues/description-input.tsx | 95 +++++++++++++++++++
.../issue-detail/inbox/main-content.tsx | 23 +++--
.../comments/comment-create.tsx | 1 -
.../issues/issue-detail/main-content.tsx | 23 +++--
.../empty-states/draft-issues.tsx | 2 +-
.../issues/issue-layouts/list/block.tsx | 5 +-
.../roots/archived-issue-layout-root.tsx | 2 +-
.../issues/peek-overview/issue-detail.tsx | 42 +++++---
web/components/issues/peek-overview/root.tsx | 22 +++--
web/components/issues/title-input.tsx | 70 ++++++++++++++
.../pages/create-update-page-modal.tsx | 1 -
web/components/project/form.tsx | 2 +-
.../profile/preferences/layout.tsx | 77 +++++++--------
14 files changed, 289 insertions(+), 88 deletions(-)
create mode 100644 web/components/issues/description-input.tsx
create mode 100644 web/components/issues/title-input.tsx
diff --git a/apiserver/plane/app/views/issue.py b/apiserver/plane/app/views/issue.py
index c8845150a5..edefade16e 100644
--- a/apiserver/plane/app/views/issue.py
+++ b/apiserver/plane/app/views/issue.py
@@ -1209,13 +1209,13 @@ class IssueArchiveViewSet(BaseViewSet):
return Response(issues, status=status.HTTP_200_OK)
def retrieve(self, request, slug, project_id, pk=None):
- issue = Issue.objects.get(
- workspace__slug=slug,
- project_id=project_id,
- archived_at__isnull=False,
- pk=pk,
+ issue = self.get_queryset().filter(pk=pk).first()
+ return Response(
+ IssueDetailSerializer(
+ issue, fields=self.fields, expand=self.expand
+ ).data,
+ status=status.HTTP_200_OK,
)
- return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
def unarchive(self, request, slug, project_id, pk=None):
issue = Issue.objects.get(
diff --git a/web/components/issues/description-input.tsx b/web/components/issues/description-input.tsx
new file mode 100644
index 0000000000..8f3dc86448
--- /dev/null
+++ b/web/components/issues/description-input.tsx
@@ -0,0 +1,95 @@
+import { FC, useState, useEffect } from "react";
+import { observer } from "mobx-react";
+// components
+import { Loader } from "@plane/ui";
+import { RichReadOnlyEditor, RichTextEditor } from "@plane/rich-text-editor";
+// store hooks
+import { useMention, useWorkspace } from "hooks/store";
+// services
+import { FileService } from "services/file.service";
+const fileService = new FileService();
+// types
+import { TIssueOperations } from "./issue-detail";
+// hooks
+import useDebounce from "hooks/use-debounce";
+import useReloadConfirmations from "hooks/use-reload-confirmation";
+
+export type IssueDescriptionInputProps = {
+ disabled?: boolean;
+ value: string | undefined | null;
+ workspaceSlug: string;
+ setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
+ issueOperations: TIssueOperations;
+ projectId: string;
+ issueId: string;
+};
+
+export const IssueDescriptionInput: FC = observer((props) => {
+ const { disabled, value, workspaceSlug, setIsSubmitting, issueId, issueOperations, projectId } = props;
+ // states
+ const [descriptionHTML, setDescriptionHTML] = useState(value);
+ // store hooks
+ const { mentionHighlights, mentionSuggestions } = useMention();
+ const workspaceStore = useWorkspace();
+ // hooks
+ const { setShowAlert } = useReloadConfirmations();
+ const debouncedValue = useDebounce(descriptionHTML, 1500);
+ // computed values
+ const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug)?.id as string;
+
+ useEffect(() => {
+ setDescriptionHTML(value);
+ }, [value]);
+
+ useEffect(() => {
+ if (debouncedValue || debouncedValue === "") {
+ issueOperations
+ .update(workspaceSlug, projectId, issueId, { description_html: debouncedValue }, false)
+ .finally(() => {
+ setIsSubmitting("saved");
+ });
+ }
+ // DO NOT Add more dependencies here. It will cause multiple requests to be sent.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [debouncedValue]);
+
+ if (!descriptionHTML && descriptionHTML !== "") {
+ return (
+
+
+
+ );
+ }
+
+ if (disabled) {
+ return (
+
+ );
+ }
+
+ return (
+ {
+ setShowAlert(true);
+ setIsSubmitting("submitting");
+ setDescriptionHTML(description_html);
+ }}
+ mentionSuggestions={mentionSuggestions}
+ mentionHighlights={mentionHighlights}
+ />
+ );
+});
diff --git a/web/components/issues/issue-detail/inbox/main-content.tsx b/web/components/issues/issue-detail/inbox/main-content.tsx
index 4a1f79bee5..d25fe92606 100644
--- a/web/components/issues/issue-detail/inbox/main-content.tsx
+++ b/web/components/issues/issue-detail/inbox/main-content.tsx
@@ -3,7 +3,9 @@ import { observer } from "mobx-react-lite";
// hooks
import { useIssueDetail, useProjectState, useUser } from "hooks/store";
// components
-import { IssueDescriptionForm, IssueUpdateStatus, TIssueOperations } from "components/issues";
+import { IssueUpdateStatus, TIssueOperations } from "components/issues";
+import { IssueTitleInput } from "../../title-input";
+import { IssueDescriptionInput } from "../../description-input";
import { IssueReaction } from "../reactions";
import { IssueActivity } from "../issue-activity";
import { InboxIssueStatus } from "../../../inbox/inbox-issue-status";
@@ -57,15 +59,24 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
- setIsSubmitting(value)}
- isSubmitting={isSubmitting}
- issue={issue}
issueOperations={issueOperations}
disabled={!is_editable}
+ value={issue.name}
+ />
+
+ setIsSubmitting(value)}
+ issueOperations={issueOperations}
+ disabled={!is_editable}
+ value={issue.description_html}
/>
{currentUser && (
diff --git a/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx b/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx
index bb79c98176..bf5b15266f 100644
--- a/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx
+++ b/web/components/issues/issue-detail/issue-activity/comments/comment-create.tsx
@@ -81,7 +81,6 @@ export const IssueCommentCreate: FC = (props) => {
render={({ field: { value, onChange } }) => (
{
- console.log("yo");
handleSubmit(onSubmit)(e);
}}
cancelUploadImage={fileService.cancelUpload}
diff --git a/web/components/issues/issue-detail/main-content.tsx b/web/components/issues/issue-detail/main-content.tsx
index 0755258017..14860a0cf6 100644
--- a/web/components/issues/issue-detail/main-content.tsx
+++ b/web/components/issues/issue-detail/main-content.tsx
@@ -3,7 +3,9 @@ import { observer } from "mobx-react-lite";
// hooks
import { useIssueDetail, useProjectState, useUser } from "hooks/store";
// components
-import { IssueDescriptionForm, IssueAttachmentRoot, IssueUpdateStatus } from "components/issues";
+import { IssueAttachmentRoot, IssueUpdateStatus } from "components/issues";
+import { IssueTitleInput } from "../title-input";
+import { IssueDescriptionInput } from "../description-input";
import { IssueParentDetail } from "./parent";
import { IssueReaction } from "./reactions";
import { SubIssuesRoot } from "../sub-issues";
@@ -61,15 +63,24 @@ export const IssueMainContent: React.FC = observer((props) => {
- setIsSubmitting(value)}
- isSubmitting={isSubmitting}
- issue={issue}
issueOperations={issueOperations}
disabled={!is_editable}
+ value={issue.name}
+ />
+
+ setIsSubmitting(value)}
+ issueOperations={issueOperations}
+ disabled={!is_editable}
+ value={issue.description_html}
/>
{currentUser && (
diff --git a/web/components/issues/issue-layouts/empty-states/draft-issues.tsx b/web/components/issues/issue-layouts/empty-states/draft-issues.tsx
index 347778d8f2..c496cc5fe2 100644
--- a/web/components/issues/issue-layouts/empty-states/draft-issues.tsx
+++ b/web/components/issues/issue-layouts/empty-states/draft-issues.tsx
@@ -42,7 +42,7 @@ export const ProjectDraftEmptyState: React.FC = observer(() => {
const isLightMode = resolvedTheme ? resolvedTheme === "light" : currentUser?.theme.theme === "light";
const currentLayoutEmptyStateImagePath = getEmptyStateImagePath("empty-filters", activeLayout ?? "list", isLightMode);
- const EmptyStateImagePath = getEmptyStateImagePath("draft", "empty-issues", isLightMode);
+ const EmptyStateImagePath = getEmptyStateImagePath("draft", "draft-issues-empty", isLightMode);
const issueFilterCount = size(
Object.fromEntries(
diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx
index ceec7b219c..2e48e1f1c8 100644
--- a/web/components/issues/issue-layouts/list/block.tsx
+++ b/web/components/issues/issue-layouts/list/block.tsx
@@ -50,9 +50,8 @@ export const IssueBlock: React.FC = observer((props: IssueBlock
return (
{displayProperties && displayProperties?.key && (
diff --git a/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx
index 5f049d4c39..2ae7ae510a 100644
--- a/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx
+++ b/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx
@@ -54,7 +54,7 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
-
+
)}
diff --git a/web/components/issues/peek-overview/issue-detail.tsx b/web/components/issues/peek-overview/issue-detail.tsx
index 8c51019384..7bc1b1b03b 100644
--- a/web/components/issues/peek-overview/issue-detail.tsx
+++ b/web/components/issues/peek-overview/issue-detail.tsx
@@ -1,10 +1,15 @@
-import { FC } from "react";
-// hooks
-import { useIssueDetail, useProject, useUser } from "hooks/store";
-// components
-import { IssueDescriptionForm, TIssueOperations } from "components/issues";
-import { IssueReaction } from "../issue-detail/reactions";
+import { FC, useCallback, useEffect, useState } from "react";
import { observer } from "mobx-react";
+// store hooks
+import { useIssueDetail, useProject, useUser } from "hooks/store";
+// hooks
+import useReloadConfirmations from "hooks/use-reload-confirmation";
+// components
+import { TIssueOperations } from "components/issues";
+import { IssueReaction } from "../issue-detail/reactions";
+import { IssueTitleInput } from "../title-input";
+import { IssueDescriptionInput } from "../description-input";
+import { debounce } from "lodash";
interface IPeekOverviewIssueDetails {
workspaceSlug: string;
@@ -17,17 +22,18 @@ interface IPeekOverviewIssueDetails {
}
export const PeekOverviewIssueDetails: FC = observer((props) => {
- const { workspaceSlug, projectId, issueId, issueOperations, disabled, isSubmitting, setIsSubmitting } = props;
+ const { workspaceSlug, issueId, issueOperations, disabled, setIsSubmitting } = props;
// store hooks
const { getProjectById } = useProject();
const { currentUser } = useUser();
const {
issue: { getIssueById },
} = useIssueDetail();
-
// derived values
const issue = getIssueById(issueId);
+
if (!issue) return <>>;
+
const projectDetails = getProjectById(issue?.project_id);
return (
@@ -35,20 +41,28 @@ export const PeekOverviewIssueDetails: FC = observer(
{projectDetails?.identifier}-{issue?.sequence_id}
- setIsSubmitting(value)}
- isSubmitting={isSubmitting}
- issue={issue}
issueOperations={issueOperations}
disabled={disabled}
+ value={issue.name}
+ />
+ setIsSubmitting(value)}
+ issueOperations={issueOperations}
+ disabled={disabled}
+ value={issue.description_html}
/>
{currentUser && (
diff --git a/web/components/issues/peek-overview/root.tsx b/web/components/issues/peek-overview/root.tsx
index b491ebe363..c49c0a5033 100644
--- a/web/components/issues/peek-overview/root.tsx
+++ b/web/components/issues/peek-overview/root.tsx
@@ -69,20 +69,11 @@ export const IssuePeekOverview: FC = observer((props) => {
// state
const [loader, setLoader] = useState(false);
- useEffect(() => {
- if (peekIssue) {
- setLoader(true);
- fetchIssue(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId).finally(() => {
- setLoader(false);
- });
- }
- }, [peekIssue, fetchIssue]);
-
const issueOperations: TIssuePeekOperations = useMemo(
() => ({
fetch: async (workspaceSlug: string, projectId: string, issueId: string) => {
try {
- await fetchIssue(workspaceSlug, projectId, issueId);
+ await fetchIssue(workspaceSlug, projectId, issueId, is_archived);
} catch (error) {
console.error("Error fetching the parent issue");
}
@@ -324,9 +315,20 @@ export const IssuePeekOverview: FC = observer((props) => {
removeModulesFromIssue,
setToastAlert,
onIssueUpdate,
+ captureIssueEvent,
+ router.asPath,
]
);
+ useEffect(() => {
+ if (peekIssue) {
+ setLoader(true);
+ issueOperations.fetch(peekIssue.workspaceSlug, peekIssue.projectId, peekIssue.issueId).finally(() => {
+ setLoader(false);
+ });
+ }
+ }, [peekIssue, issueOperations]);
+
if (!peekIssue?.workspaceSlug || !peekIssue?.projectId || !peekIssue?.issueId) return <>>;
const issue = getIssueById(peekIssue.issueId) || undefined;
diff --git a/web/components/issues/title-input.tsx b/web/components/issues/title-input.tsx
new file mode 100644
index 0000000000..2cd031b4f8
--- /dev/null
+++ b/web/components/issues/title-input.tsx
@@ -0,0 +1,70 @@
+import { FC, useState, useEffect, useCallback } from "react";
+import { observer } from "mobx-react";
+// components
+import { TextArea } from "@plane/ui";
+// types
+import { TIssueOperations } from "./issue-detail";
+// hooks
+import useDebounce from "hooks/use-debounce";
+import useReloadConfirmations from "hooks/use-reload-confirmation";
+
+export type IssueTitleInputProps = {
+ disabled?: boolean;
+ value: string | undefined | null;
+ workspaceSlug: string;
+ setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
+ issueOperations: TIssueOperations;
+ projectId: string;
+ issueId: string;
+};
+
+export const IssueTitleInput: FC = observer((props) => {
+ const { disabled, value, workspaceSlug, setIsSubmitting, issueId, issueOperations, projectId } = props;
+ // states
+ const [title, setTitle] = useState("");
+ // hooks
+ const { setShowAlert } = useReloadConfirmations();
+ const debouncedValue = useDebounce(title, 1500);
+
+ useEffect(() => {
+ if (value) setTitle(value);
+ }, [value]);
+
+ useEffect(() => {
+ if (debouncedValue) {
+ issueOperations.update(workspaceSlug, projectId, issueId, { name: debouncedValue }, false).finally(() => {
+ setIsSubmitting("saved");
+ });
+ }
+ // DO NOT Add more dependencies here. It will cause multiple requests to be sent.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [debouncedValue]);
+
+ const handleTitleChange = useCallback(
+ (e: React.ChangeEvent) => {
+ setShowAlert(true);
+ setIsSubmitting("submitting");
+ setTitle(e.target.value);
+ },
+ [setIsSubmitting, setShowAlert]
+ );
+
+ return (
+
+
+
+ 255 ? "text-red-500" : ""}`}>{title.length}
+ /255
+
+
+ );
+});
diff --git a/web/components/pages/create-update-page-modal.tsx b/web/components/pages/create-update-page-modal.tsx
index e81a0040b7..eea7e9d7fd 100644
--- a/web/components/pages/create-update-page-modal.tsx
+++ b/web/components/pages/create-update-page-modal.tsx
@@ -68,7 +68,6 @@ export const CreateUpdatePageModal: FC = (props) => {
state: "SUCCESS",
},
});
- console.log("Page updated successfully", pageStore);
} else {
await createProjectPage(formData);
}
diff --git a/web/components/project/form.tsx b/web/components/project/form.tsx
index f46dee6b92..7fd741cb95 100644
--- a/web/components/project/form.tsx
+++ b/web/components/project/form.tsx
@@ -79,7 +79,7 @@ export const ProjectDetailsForm: FC = (props) => {
return updateProject(workspaceSlug.toString(), project.id, payload)
.then((res) => {
const changed_properties = Object.keys(dirtyFields);
- console.log(dirtyFields);
+
captureProjectEvent({
eventName: PROJECT_UPDATED,
payload: {
diff --git a/web/layouts/settings-layout/profile/preferences/layout.tsx b/web/layouts/settings-layout/profile/preferences/layout.tsx
index 3154123377..0e1d315876 100644
--- a/web/layouts/settings-layout/profile/preferences/layout.tsx
+++ b/web/layouts/settings-layout/profile/preferences/layout.tsx
@@ -20,54 +20,55 @@ export const ProfilePreferenceSettingsLayout: FC {
- const item = router.asPath.split('/');
+ const item = router.asPath.split("/");
let splittedItem = item[item.length - 1];
splittedItem = splittedItem.replace(splittedItem[0], splittedItem[0].toUpperCase());
- console.log(splittedItem);
return splittedItem;
- }
+ };
const profilePreferenceLinks: Array<{
label: string;
href: string;
}> = [
- {
- label: "Theme",
- href: `/profile/preferences/theme`,
- },
- {
- label: "Email",
- href: `/profile/preferences/email`,
- },
- ];
+ {
+ label: "Theme",
+ href: `/profile/preferences/theme`,
+ },
+ {
+ label: "Email",
+ href: `/profile/preferences/email`,
+ },
+ ];
return (
-
- themeStore.toggleSidebar()} />
-
- {showMenuItem()}
-
-
- }
- customButtonClassName="flex flex-grow justify-start text-custom-text-200 text-sm"
- >
- <>>
- {profilePreferenceLinks.map((link) => (
-
- {link.label}
-
- ))}
-
-
- }>
+
+ themeStore.toggleSidebar()} />
+
+ {showMenuItem()}
+
+
+ }
+ customButtonClassName="flex flex-grow justify-start text-custom-text-200 text-sm"
+ >
+ <>>
+ {profilePreferenceLinks.map((link) => (
+
+
+ {link.label}
+
+
+ ))}
+
+
+ }
+ >
From 41e812a811873ec7a14b787e923981dafb5323d5 Mon Sep 17 00:00:00 2001
From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Date: Fri, 16 Feb 2024 20:07:38 +0530
Subject: [PATCH 04/11] fix: quick action copy link action (#3686)
---
.../issue-layouts/list/list-view-types.d.ts | 2 ++
.../quick-action-dropdowns/all-issue.tsx | 21 +++++++++-------
.../quick-action-dropdowns/archived-issue.tsx | 6 ++---
.../quick-action-dropdowns/cycle-issue.tsx | 25 +++++++++++--------
.../quick-action-dropdowns/module-issue.tsx | 25 +++++++++++--------
.../quick-action-dropdowns/project-issue.tsx | 15 ++++++-----
6 files changed, 54 insertions(+), 40 deletions(-)
diff --git a/web/components/issues/issue-layouts/list/list-view-types.d.ts b/web/components/issues/issue-layouts/list/list-view-types.d.ts
index 1838316cb9..e369410af7 100644
--- a/web/components/issues/issue-layouts/list/list-view-types.d.ts
+++ b/web/components/issues/issue-layouts/list/list-view-types.d.ts
@@ -1,3 +1,5 @@
+import { TIssue } from "@plane/types";
+
export interface IQuickActionProps {
issue: TIssue;
handleDelete: () => Promise;
diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx
index f97f1ea86c..bc6518911a 100644
--- a/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx
+++ b/web/components/issues/issue-layouts/quick-action-dropdowns/all-issue.tsx
@@ -2,6 +2,7 @@ import { useState } from "react";
import { useRouter } from "next/router";
import { CustomMenu } from "@plane/ui";
import { Copy, Link, Pencil, Trash2 } from "lucide-react";
+import omit from "lodash/omit";
// hooks
import useToast from "hooks/use-toast";
import { useEventTracker } from "hooks/store";
@@ -30,7 +31,7 @@ export const AllIssueQuickActions: React.FC = (props) => {
const { setToastAlert } = useToast();
const handleCopyIssueLink = () => {
- copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() =>
+ copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`).then(() =>
setToastAlert({
type: "success",
title: "Link copied",
@@ -39,11 +40,13 @@ export const AllIssueQuickActions: React.FC = (props) => {
);
};
- const duplicateIssuePayload = {
- ...issue,
- name: `${issue.name} (copy)`,
- };
- delete duplicateIssuePayload.id;
+ const duplicateIssuePayload = omit(
+ {
+ ...issue,
+ name: `${issue.name} (copy)`,
+ },
+ ["id"]
+ );
return (
<>
@@ -87,7 +90,7 @@ export const AllIssueQuickActions: React.FC = (props) => {
{
setTrackElement("Global issues");
- setIssueToEdit(issue);
+ setIssueToEdit(issue);
setCreateUpdateIssueModal(true);
}}
>
@@ -99,7 +102,7 @@ export const AllIssueQuickActions: React.FC = (props) => {
{
setTrackElement("Global issues");
- setCreateUpdateIssueModal(true);
+ setCreateUpdateIssueModal(true);
}}
>
@@ -110,7 +113,7 @@ export const AllIssueQuickActions: React.FC = (props) => {
{
setTrackElement("Global issues");
- setDeleteIssueModal(true);
+ setDeleteIssueModal(true);
}}
>
diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx
index f962701769..e331d71827 100644
--- a/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx
+++ b/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx
@@ -4,7 +4,7 @@ import { CustomMenu } from "@plane/ui";
import { Link, Trash2 } from "lucide-react";
// hooks
import useToast from "hooks/use-toast";
-import { useEventTracker, useIssues ,useUser} from "hooks/store";
+import { useEventTracker, useIssues, useUser } from "hooks/store";
// components
import { DeleteArchivedIssueModal } from "components/issues";
// helpers
@@ -37,7 +37,7 @@ export const ArchivedIssueQuickActions: React.FC = (props) =>
const activeLayout = `${issuesFilter.issueFilters?.displayFilters?.layout} layout`;
const handleCopyIssueLink = () => {
- copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/archived-issues/${issue.id}`).then(() =>
+ copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project_id}/archived-issues/${issue.id}`).then(() =>
setToastAlert({
type: "success",
title: "Link copied",
@@ -75,7 +75,7 @@ export const ArchivedIssueQuickActions: React.FC = (props) =>
{
setTrackElement(activeLayout);
- setDeleteIssueModal(true);
+ setDeleteIssueModal(true);
}}
>
diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx
index 8c3beb3d2c..4699b1c81e 100644
--- a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx
+++ b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx
@@ -2,9 +2,10 @@ import { useState } from "react";
import { useRouter } from "next/router";
import { CustomMenu } from "@plane/ui";
import { Copy, Link, Pencil, Trash2, XCircle } from "lucide-react";
+import omit from "lodash/omit";
// hooks
import useToast from "hooks/use-toast";
-import { useEventTracker, useIssues,useUser } from "hooks/store";
+import { useEventTracker, useIssues, useUser } from "hooks/store";
// components
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// helpers
@@ -49,7 +50,7 @@ export const CycleIssueQuickActions: React.FC = (props) => {
const activeLayout = `${issuesFilter.issueFilters?.displayFilters?.layout} layout`;
const handleCopyIssueLink = () => {
- copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() =>
+ copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`).then(() =>
setToastAlert({
type: "success",
title: "Link copied",
@@ -58,11 +59,13 @@ export const CycleIssueQuickActions: React.FC = (props) => {
);
};
- const duplicateIssuePayload = {
- ...issue,
- name: `${issue.name} (copy)`,
- };
- delete duplicateIssuePayload.id;
+ const duplicateIssuePayload = omit(
+ {
+ ...issue,
+ name: `${issue.name} (copy)`,
+ },
+ ["id"]
+ );
return (
<>
@@ -107,10 +110,10 @@ export const CycleIssueQuickActions: React.FC = (props) => {
onClick={() => {
setIssueToEdit({
...issue,
- cycle: cycleId?.toString() ?? null,
+ cycle_id: cycleId?.toString() ?? null,
});
setTrackElement(activeLayout);
- setCreateUpdateIssueModal(true);
+ setCreateUpdateIssueModal(true);
}}
>
@@ -131,7 +134,7 @@ export const CycleIssueQuickActions: React.FC = (props) => {
{
setTrackElement(activeLayout);
- setCreateUpdateIssueModal(true);
+ setCreateUpdateIssueModal(true);
}}
>
@@ -142,7 +145,7 @@ export const CycleIssueQuickActions: React.FC = (props) => {
{
setTrackElement(activeLayout);
- setDeleteIssueModal(true);
+ setDeleteIssueModal(true);
}}
>
diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx
index a3ed73ec0c..6eabfda59f 100644
--- a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx
+++ b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx
@@ -2,9 +2,10 @@ import { useState } from "react";
import { useRouter } from "next/router";
import { CustomMenu } from "@plane/ui";
import { Copy, Link, Pencil, Trash2, XCircle } from "lucide-react";
+import omit from "lodash/omit";
// hooks
import useToast from "hooks/use-toast";
-import { useIssues, useEventTracker ,useUser } from "hooks/store";
+import { useIssues, useEventTracker, useUser } from "hooks/store";
// components
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
// helpers
@@ -49,7 +50,7 @@ export const ModuleIssueQuickActions: React.FC = (props) => {
const activeLayout = `${issuesFilter.issueFilters?.displayFilters?.layout} layout`;
const handleCopyIssueLink = () => {
- copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() =>
+ copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`).then(() =>
setToastAlert({
type: "success",
title: "Link copied",
@@ -58,11 +59,13 @@ export const ModuleIssueQuickActions: React.FC = (props) => {
);
};
- const duplicateIssuePayload = {
- ...issue,
- name: `${issue.name} (copy)`,
- };
- delete duplicateIssuePayload.id;
+ const duplicateIssuePayload = omit(
+ {
+ ...issue,
+ name: `${issue.name} (copy)`,
+ },
+ ["id"]
+ );
return (
<>
@@ -105,9 +108,9 @@ export const ModuleIssueQuickActions: React.FC = (props) => {
<>
{
- setIssueToEdit({ ...issue, module: moduleId?.toString() ?? null });
+ setIssueToEdit({ ...issue, module_ids: moduleId ? [moduleId.toString()] : [] });
setTrackElement(activeLayout);
- setCreateUpdateIssueModal(true);
+ setCreateUpdateIssueModal(true);
}}
>
@@ -128,7 +131,7 @@ export const ModuleIssueQuickActions: React.FC = (props) => {
{
setTrackElement(activeLayout);
- setCreateUpdateIssueModal(true);
+ setCreateUpdateIssueModal(true);
}}
>
@@ -141,7 +144,7 @@ export const ModuleIssueQuickActions: React.FC = (props) => {
e.preventDefault();
e.stopPropagation();
setTrackElement(activeLayout);
- setDeleteIssueModal(true);
+ setDeleteIssueModal(true);
}}
>
diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx
index 65adc85427..1d6d88f257 100644
--- a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx
+++ b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx
@@ -2,6 +2,7 @@ import { useState } from "react";
import { useRouter } from "next/router";
import { CustomMenu } from "@plane/ui";
import { Copy, Link, Pencil, Trash2 } from "lucide-react";
+import omit from "lodash/omit";
// hooks
import { useEventTracker, useIssues, useUser } from "hooks/store";
import useToast from "hooks/use-toast";
@@ -39,7 +40,7 @@ export const ProjectIssueQuickActions: React.FC = (props) =>
const { setToastAlert } = useToast();
const handleCopyIssueLink = () => {
- copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() =>
+ copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project_id}/issues/${issue.id}`).then(() =>
setToastAlert({
type: "success",
title: "Link copied",
@@ -48,11 +49,13 @@ export const ProjectIssueQuickActions: React.FC = (props) =>
);
};
- const duplicateIssuePayload = {
- ...issue,
- name: `${issue.name} (copy)`,
- };
- delete duplicateIssuePayload.id;
+ const duplicateIssuePayload = omit(
+ {
+ ...issue,
+ name: `${issue.name} (copy)`,
+ },
+ ["id"]
+ );
const isDraftIssue = router?.asPath?.includes("draft-issues") || false;
From eba5ed24adac43350eae1af8ff2f5359bcda3d66 Mon Sep 17 00:00:00 2001
From: sriram veeraghanta
Date: Sun, 18 Feb 2024 15:26:50 +0530
Subject: [PATCH 05/11] fix: color pick background color on change (#3691)
---
.../core/theme/custom-theme-selector.tsx | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/web/components/core/theme/custom-theme-selector.tsx b/web/components/core/theme/custom-theme-selector.tsx
index bd6f435692..fdb7a64834 100644
--- a/web/components/core/theme/custom-theme-selector.tsx
+++ b/web/components/core/theme/custom-theme-selector.tsx
@@ -66,7 +66,6 @@ export const CustomThemeSelector: React.FC = observer(() => {
const handleValueChange = (val: string | undefined, onChange: any) => {
let hex = val;
-
// prepend a hashtag if it doesn't exist
if (val && val[0] !== "#") hex = `#${val}`;
@@ -94,7 +93,7 @@ export const CustomThemeSelector: React.FC = observer(() => {
placeholder="#0d101b"
className="w-full"
style={{
- backgroundColor: value,
+ backgroundColor: watch("background"),
color: watch("text"),
}}
hasError={Boolean(errors?.background)}
@@ -120,8 +119,8 @@ export const CustomThemeSelector: React.FC = observer(() => {
placeholder="#c5c5c5"
className="w-full"
style={{
- backgroundColor: watch("background"),
- color: value,
+ backgroundColor: watch("text"),
+ color: watch("background"),
}}
hasError={Boolean(errors?.text)}
/>
@@ -146,7 +145,7 @@ export const CustomThemeSelector: React.FC = observer(() => {
placeholder="#3f76ff"
className="w-full"
style={{
- backgroundColor: value,
+ backgroundColor: watch("primary"),
color: watch("text"),
}}
hasError={Boolean(errors?.primary)}
@@ -172,7 +171,7 @@ export const CustomThemeSelector: React.FC = observer(() => {
placeholder="#0d101b"
className="w-full"
style={{
- backgroundColor: value,
+ backgroundColor: watch("sidebarBackground"),
color: watch("sidebarText"),
}}
hasError={Boolean(errors?.sidebarBackground)}
@@ -200,8 +199,8 @@ export const CustomThemeSelector: React.FC = observer(() => {
placeholder="#c5c5c5"
className="w-full"
style={{
- backgroundColor: watch("sidebarBackground"),
- color: value,
+ backgroundColor: watch("sidebarText"),
+ color: watch("sidebarBackground"),
}}
hasError={Boolean(errors?.sidebarText)}
/>
From 10057377dca40c0dc8d088dc3161821b67813635 Mon Sep 17 00:00:00 2001
From: guru_sainath
Date: Sun, 18 Feb 2024 15:28:37 +0530
Subject: [PATCH 06/11] fix: improved issue description editor focus and state
management (#3690)
* chore: issue input and editor reload alert issue resolved
* chore: issue description mutation issue in inbox
* fix: reload confirmation alert and stay focused after saving
* chore: updated the renderOnPropChange prop in the description-input
---------
Co-authored-by: sriram veeraghanta
---
web/components/issues/description-input.tsx | 30 +++-
.../issue-detail/inbox/main-content.tsx | 2 +
.../issues/issue-detail/main-content.tsx | 2 +
.../roots/archived-issue-layout-root.tsx | 2 +-
.../issues/peek-overview/header.tsx | 153 ++++++++++++++++++
web/components/issues/peek-overview/index.ts | 1 +
.../issues/peek-overview/issue-detail.tsx | 20 ++-
web/components/issues/peek-overview/view.tsx | 145 +++--------------
web/components/issues/title-input.tsx | 7 +-
9 files changed, 227 insertions(+), 135 deletions(-)
create mode 100644 web/components/issues/peek-overview/header.tsx
diff --git a/web/components/issues/description-input.tsx b/web/components/issues/description-input.tsx
index 8f3dc86448..f82627fed1 100644
--- a/web/components/issues/description-input.tsx
+++ b/web/components/issues/description-input.tsx
@@ -12,12 +12,12 @@ const fileService = new FileService();
import { TIssueOperations } from "./issue-detail";
// hooks
import useDebounce from "hooks/use-debounce";
-import useReloadConfirmations from "hooks/use-reload-confirmation";
export type IssueDescriptionInputProps = {
disabled?: boolean;
value: string | undefined | null;
workspaceSlug: string;
+ isSubmitting: "submitting" | "submitted" | "saved";
setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
issueOperations: TIssueOperations;
projectId: string;
@@ -28,21 +28,34 @@ export const IssueDescriptionInput: FC = observer((p
const { disabled, value, workspaceSlug, setIsSubmitting, issueId, issueOperations, projectId } = props;
// states
const [descriptionHTML, setDescriptionHTML] = useState(value);
+ const [localIssueDescription, setLocalIssueDescription] = useState({
+ id: issueId,
+ description_html: typeof value === "string" && value != "" ? value : "",
+ });
// store hooks
const { mentionHighlights, mentionSuggestions } = useMention();
- const workspaceStore = useWorkspace();
+ const { getWorkspaceBySlug } = useWorkspace();
// hooks
- const { setShowAlert } = useReloadConfirmations();
const debouncedValue = useDebounce(descriptionHTML, 1500);
// computed values
- const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug)?.id as string;
+ const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id as string;
useEffect(() => {
- setDescriptionHTML(value);
+ if (value) setDescriptionHTML(value);
}, [value]);
+ useEffect(() => {
+ if (issueId && value)
+ setLocalIssueDescription({
+ id: issueId,
+ description_html: typeof value === "string" && value != "" ? value : "",
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [issueId, value]);
+
useEffect(() => {
if (debouncedValue || debouncedValue === "") {
+ setIsSubmitting("submitted");
issueOperations
.update(workspaceSlug, projectId, issueId, { description_html: debouncedValue }, false)
.finally(() => {
@@ -79,12 +92,13 @@ export const IssueDescriptionInput: FC = observer((p
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
value={descriptionHTML}
- setShouldShowAlert={setShowAlert}
- setIsSubmitting={setIsSubmitting}
+ rerenderOnPropsChange={localIssueDescription}
+ // setShouldShowAlert={setShowAlert}
+ // setIsSubmitting={setIsSubmitting}
dragDropEnabled
customClassName="min-h-[150px] shadow-sm"
onChange={(description: Object, description_html: string) => {
- setShowAlert(true);
+ // setShowAlert(true);
setIsSubmitting("submitting");
setDescriptionHTML(description_html);
}}
diff --git a/web/components/issues/issue-detail/inbox/main-content.tsx b/web/components/issues/issue-detail/inbox/main-content.tsx
index d25fe92606..b49c0286f9 100644
--- a/web/components/issues/issue-detail/inbox/main-content.tsx
+++ b/web/components/issues/issue-detail/inbox/main-content.tsx
@@ -63,6 +63,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
issueId={issue.id}
+ isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}
issueOperations={issueOperations}
disabled={!is_editable}
@@ -73,6 +74,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
issueId={issue.id}
+ isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}
issueOperations={issueOperations}
disabled={!is_editable}
diff --git a/web/components/issues/issue-detail/main-content.tsx b/web/components/issues/issue-detail/main-content.tsx
index 14860a0cf6..968b9faa58 100644
--- a/web/components/issues/issue-detail/main-content.tsx
+++ b/web/components/issues/issue-detail/main-content.tsx
@@ -67,6 +67,7 @@ export const IssueMainContent: React.FC = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
issueId={issue.id}
+ isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}
issueOperations={issueOperations}
disabled={!is_editable}
@@ -77,6 +78,7 @@ export const IssueMainContent: React.FC = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
issueId={issue.id}
+ isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}
issueOperations={issueOperations}
disabled={!is_editable}
diff --git a/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx
index 2ae7ae510a..5f049d4c39 100644
--- a/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx
+++ b/web/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx
@@ -54,7 +54,7 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
-
+
)}
diff --git a/web/components/issues/peek-overview/header.tsx b/web/components/issues/peek-overview/header.tsx
new file mode 100644
index 0000000000..8b51c977e5
--- /dev/null
+++ b/web/components/issues/peek-overview/header.tsx
@@ -0,0 +1,153 @@
+import { FC } from "react";
+import { useRouter } from "next/router";
+import { observer } from "mobx-react";
+import { MoveRight, MoveDiagonal, Link2, Trash2 } from "lucide-react";
+// ui
+import { CenterPanelIcon, CustomSelect, FullScreenPanelIcon, SidePanelIcon } from "@plane/ui";
+// helpers
+import { copyUrlToClipboard } from "helpers/string.helper";
+// hooks
+import useToast from "hooks/use-toast";
+// store hooks
+import { useUser } from "hooks/store";
+// components
+import { IssueSubscription, IssueUpdateStatus } from "components/issues";
+
+export type TPeekModes = "side-peek" | "modal" | "full-screen";
+
+const PEEK_OPTIONS: { key: TPeekModes; icon: any; title: string }[] = [
+ {
+ key: "side-peek",
+ icon: SidePanelIcon,
+ title: "Side Peek",
+ },
+ {
+ key: "modal",
+ icon: CenterPanelIcon,
+ title: "Modal",
+ },
+ {
+ key: "full-screen",
+ icon: FullScreenPanelIcon,
+ title: "Full Screen",
+ },
+];
+
+export type PeekOverviewHeaderProps = {
+ peekMode: TPeekModes;
+ setPeekMode: (value: TPeekModes) => void;
+ removeRoutePeekId: () => void;
+ workspaceSlug: string;
+ projectId: string;
+ issueId: string;
+ isArchived: boolean;
+ disabled: boolean;
+ toggleDeleteIssueModal: (value: boolean) => void;
+ isSubmitting: "submitting" | "submitted" | "saved";
+};
+
+export const IssuePeekOverviewHeader: FC = observer((props) => {
+ const {
+ peekMode,
+ setPeekMode,
+ workspaceSlug,
+ projectId,
+ issueId,
+ isArchived,
+ disabled,
+ removeRoutePeekId,
+ toggleDeleteIssueModal,
+ isSubmitting,
+ } = props;
+ // router
+ const router = useRouter();
+ // store hooks
+ const { currentUser } = useUser();
+ // hooks
+ const { setToastAlert } = useToast();
+ // derived values
+ const currentMode = PEEK_OPTIONS.find((m) => m.key === peekMode);
+
+ const handleCopyText = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ copyUrlToClipboard(
+ `${workspaceSlug}/projects/${projectId}/${isArchived ? "archived-issues" : "issues"}/${issueId}`
+ ).then(() => {
+ setToastAlert({
+ type: "success",
+ title: "Link Copied!",
+ message: "Issue link copied to clipboard.",
+ });
+ });
+ };
+
+ const redirectToIssueDetail = () => {
+ router.push({
+ pathname: `/${workspaceSlug}/projects/${projectId}/${isArchived ? "archived-issues" : "issues"}/${issueId}`,
+ });
+ removeRoutePeekId();
+ };
+
+ return (
+
+
+
+
+
+ {currentMode && (
+
+ setPeekMode(val)}
+ customButton={
+
+ }
+ >
+ {PEEK_OPTIONS.map((mode) => (
+
+
+
+ {mode.title}
+
+
+ ))}
+
+
+ )}
+
+
+
+
+ {currentUser && !isArchived && (
+
+ )}
+
+ {!disabled && (
+
+ )}
+
+
+
+ );
+});
diff --git a/web/components/issues/peek-overview/index.ts b/web/components/issues/peek-overview/index.ts
index 6d602e45b8..aa341b939f 100644
--- a/web/components/issues/peek-overview/index.ts
+++ b/web/components/issues/peek-overview/index.ts
@@ -2,3 +2,4 @@ export * from "./issue-detail";
export * from "./properties";
export * from "./root";
export * from "./view";
+export * from "./header";
diff --git a/web/components/issues/peek-overview/issue-detail.tsx b/web/components/issues/peek-overview/issue-detail.tsx
index 7bc1b1b03b..0134d35eea 100644
--- a/web/components/issues/peek-overview/issue-detail.tsx
+++ b/web/components/issues/peek-overview/issue-detail.tsx
@@ -1,4 +1,4 @@
-import { FC, useCallback, useEffect, useState } from "react";
+import { FC, useEffect } from "react";
import { observer } from "mobx-react";
// store hooks
import { useIssueDetail, useProject, useUser } from "hooks/store";
@@ -9,7 +9,6 @@ import { TIssueOperations } from "components/issues";
import { IssueReaction } from "../issue-detail/reactions";
import { IssueTitleInput } from "../title-input";
import { IssueDescriptionInput } from "../description-input";
-import { debounce } from "lodash";
interface IPeekOverviewIssueDetails {
workspaceSlug: string;
@@ -22,13 +21,15 @@ interface IPeekOverviewIssueDetails {
}
export const PeekOverviewIssueDetails: FC = observer((props) => {
- const { workspaceSlug, issueId, issueOperations, disabled, setIsSubmitting } = props;
+ const { workspaceSlug, issueId, issueOperations, disabled, isSubmitting, setIsSubmitting } = props;
// store hooks
const { getProjectById } = useProject();
const { currentUser } = useUser();
const {
issue: { getIssueById },
} = useIssueDetail();
+ // hooks
+ const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
// derived values
const issue = getIssueById(issueId);
@@ -36,6 +37,17 @@ export const PeekOverviewIssueDetails: FC = observer(
const projectDetails = getProjectById(issue?.project_id);
+ useEffect(() => {
+ if (isSubmitting === "submitted") {
+ setShowAlert(false);
+ setTimeout(async () => {
+ setIsSubmitting("saved");
+ }, 2000);
+ } else if (isSubmitting === "submitting") {
+ setShowAlert(true);
+ }
+ }, [isSubmitting, setShowAlert, setIsSubmitting]);
+
return (
<>
@@ -45,6 +57,7 @@ export const PeekOverviewIssueDetails: FC = observer(
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
issueId={issue.id}
+ isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}
issueOperations={issueOperations}
disabled={disabled}
@@ -54,6 +67,7 @@ export const PeekOverviewIssueDetails: FC = observer(
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
issueId={issue.id}
+ isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}
issueOperations={issueOperations}
disabled={disabled}
diff --git a/web/components/issues/peek-overview/view.tsx b/web/components/issues/peek-overview/view.tsx
index 7b6c851ffd..4e80c49385 100644
--- a/web/components/issues/peek-overview/view.tsx
+++ b/web/components/issues/peek-overview/view.tsx
@@ -1,28 +1,25 @@
import { FC, useRef, useState } from "react";
-import { useRouter } from "next/router";
+
import { observer } from "mobx-react-lite";
-import { MoveRight, MoveDiagonal, Link2, Trash2 } from "lucide-react";
+
// hooks
import useOutsideClickDetector from "hooks/use-outside-click-detector";
import useKeypress from "hooks/use-keypress";
// store hooks
-import { useIssueDetail, useUser } from "hooks/store";
-import useToast from "hooks/use-toast";
+import { useIssueDetail } from "hooks/store";
// components
import {
DeleteArchivedIssueModal,
DeleteIssueModal,
- IssueSubscription,
- IssueUpdateStatus,
+ IssuePeekOverviewHeader,
+ TPeekModes,
PeekOverviewIssueDetails,
PeekOverviewProperties,
TIssueOperations,
} from "components/issues";
import { IssueActivity } from "../issue-detail/issue-activity";
// ui
-import { CenterPanelIcon, CustomSelect, FullScreenPanelIcon, SidePanelIcon, Spinner } from "@plane/ui";
-// helpers
-import { copyUrlToClipboard } from "helpers/string.helper";
+import { Spinner } from "@plane/ui";
interface IIssueView {
workspaceSlug: string;
@@ -34,72 +31,28 @@ interface IIssueView {
issueOperations: TIssueOperations;
}
-type TPeekModes = "side-peek" | "modal" | "full-screen";
-
-const PEEK_OPTIONS: { key: TPeekModes; icon: any; title: string }[] = [
- {
- key: "side-peek",
- icon: SidePanelIcon,
- title: "Side Peek",
- },
- {
- key: "modal",
- icon: CenterPanelIcon,
- title: "Modal",
- },
- {
- key: "full-screen",
- icon: FullScreenPanelIcon,
- title: "Full Screen",
- },
-];
-
export const IssueView: FC = observer((props) => {
const { workspaceSlug, projectId, issueId, isLoading, is_archived, disabled = false, issueOperations } = props;
- // router
- const router = useRouter();
// states
const [peekMode, setPeekMode] = useState("side-peek");
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
// ref
const issuePeekOverviewRef = useRef(null);
// store hooks
- const { setPeekIssue, isAnyModalOpen, isDeleteIssueModalOpen, toggleDeleteIssueModal } = useIssueDetail();
- const { currentUser } = useUser();
const {
+ setPeekIssue,
+ isAnyModalOpen,
+ isDeleteIssueModalOpen,
+ toggleDeleteIssueModal,
issue: { getIssueById },
} = useIssueDetail();
- const { setToastAlert } = useToast();
- // derived values
- const currentMode = PEEK_OPTIONS.find((m) => m.key === peekMode);
const issue = getIssueById(issueId);
-
+ // remove peek id
const removeRoutePeekId = () => {
setPeekIssue(undefined);
};
+ // hooks
useOutsideClickDetector(issuePeekOverviewRef, () => !isAnyModalOpen && removeRoutePeekId());
-
- const redirectToIssueDetail = () => {
- router.push({
- pathname: `/${workspaceSlug}/projects/${projectId}/${is_archived ? "archived-issues" : "issues"}/${issueId}`,
- });
- removeRoutePeekId();
- };
-
- const handleCopyText = (e: React.MouseEvent) => {
- e.stopPropagation();
- e.preventDefault();
- copyUrlToClipboard(
- `${workspaceSlug}/projects/${projectId}/${is_archived ? "archived-issues" : "issues"}/${issueId}`
- ).then(() => {
- setToastAlert({
- type: "success",
- title: "Link Copied!",
- message: "Issue link copied to clipboard.",
- });
- });
- };
-
const handleKeyDown = () => !isAnyModalOpen && removeRoutePeekId();
useKeypress("Escape", handleKeyDown);
@@ -141,66 +94,20 @@ export const IssueView: FC = observer((props) => {
}}
>
{/* header */}
-
-
-
-
-
- {currentMode && (
-
- setPeekMode(val)}
- customButton={
-
- }
- >
- {PEEK_OPTIONS.map((mode) => (
-
-
-
- {mode.title}
-
-
- ))}
-
-
- )}
-
-
-
-
- {currentUser && !is_archived && (
-
- )}
-
- {!disabled && (
-
- )}
-
-
-
-
+ {
+ setPeekMode(value);
+ }}
+ removeRoutePeekId={removeRoutePeekId}
+ toggleDeleteIssueModal={toggleDeleteIssueModal}
+ isArchived={is_archived}
+ issueId={issueId}
+ workspaceSlug={workspaceSlug}
+ projectId={projectId}
+ isSubmitting={isSubmitting}
+ disabled={disabled}
+ />
{/* content */}
{isLoading && !issue ? (
diff --git a/web/components/issues/title-input.tsx b/web/components/issues/title-input.tsx
index 2cd031b4f8..e189741904 100644
--- a/web/components/issues/title-input.tsx
+++ b/web/components/issues/title-input.tsx
@@ -6,12 +6,12 @@ import { TextArea } from "@plane/ui";
import { TIssueOperations } from "./issue-detail";
// hooks
import useDebounce from "hooks/use-debounce";
-import useReloadConfirmations from "hooks/use-reload-confirmation";
export type IssueTitleInputProps = {
disabled?: boolean;
value: string | undefined | null;
workspaceSlug: string;
+ isSubmitting: "submitting" | "submitted" | "saved";
setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
issueOperations: TIssueOperations;
projectId: string;
@@ -23,7 +23,7 @@ export const IssueTitleInput: FC = observer((props) => {
// states
const [title, setTitle] = useState("");
// hooks
- const { setShowAlert } = useReloadConfirmations();
+
const debouncedValue = useDebounce(title, 1500);
useEffect(() => {
@@ -42,11 +42,10 @@ export const IssueTitleInput: FC = observer((props) => {
const handleTitleChange = useCallback(
(e: React.ChangeEvent) => {
- setShowAlert(true);
setIsSubmitting("submitting");
setTitle(e.target.value);
},
- [setIsSubmitting, setShowAlert]
+ [setIsSubmitting]
);
return (
From 261013b794b39e0b39b45b36da4cb16f878c50eb Mon Sep 17 00:00:00 2001
From: sriram veeraghanta
Date: Mon, 19 Feb 2024 00:17:31 +0530
Subject: [PATCH 07/11] fix: inbox issue initial data load (#3693)
* fix: inbox issue initial data load
* chore: removed unnecessary comments
---
web/components/issues/description-input.tsx | 22 ++++---------------
.../issue-detail/inbox/main-content.tsx | 1 +
web/components/issues/title-input.tsx | 2 +-
3 files changed, 6 insertions(+), 19 deletions(-)
diff --git a/web/components/issues/description-input.tsx b/web/components/issues/description-input.tsx
index f82627fed1..d18fad6e5a 100644
--- a/web/components/issues/description-input.tsx
+++ b/web/components/issues/description-input.tsx
@@ -22,16 +22,13 @@ export type IssueDescriptionInputProps = {
issueOperations: TIssueOperations;
projectId: string;
issueId: string;
+ initialValue?: string;
};
export const IssueDescriptionInput: FC = observer((props) => {
- const { disabled, value, workspaceSlug, setIsSubmitting, issueId, issueOperations, projectId } = props;
+ const { disabled, value, workspaceSlug, setIsSubmitting, issueId, issueOperations, projectId, initialValue } = props;
// states
const [descriptionHTML, setDescriptionHTML] = useState(value);
- const [localIssueDescription, setLocalIssueDescription] = useState({
- id: issueId,
- description_html: typeof value === "string" && value != "" ? value : "",
- });
// store hooks
const { mentionHighlights, mentionSuggestions } = useMention();
const { getWorkspaceBySlug } = useWorkspace();
@@ -45,16 +42,7 @@ export const IssueDescriptionInput: FC = observer((p
}, [value]);
useEffect(() => {
- if (issueId && value)
- setLocalIssueDescription({
- id: issueId,
- description_html: typeof value === "string" && value != "" ? value : "",
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [issueId, value]);
-
- useEffect(() => {
- if (debouncedValue || debouncedValue === "") {
+ if (debouncedValue && debouncedValue !== value) {
setIsSubmitting("submitted");
issueOperations
.update(workspaceSlug, projectId, issueId, { description_html: debouncedValue }, false)
@@ -92,9 +80,7 @@ export const IssueDescriptionInput: FC = observer((p
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
value={descriptionHTML}
- rerenderOnPropsChange={localIssueDescription}
- // setShouldShowAlert={setShowAlert}
- // setIsSubmitting={setIsSubmitting}
+ initialValue={initialValue}
dragDropEnabled
customClassName="min-h-[150px] shadow-sm"
onChange={(description: Object, description_html: string) => {
diff --git a/web/components/issues/issue-detail/inbox/main-content.tsx b/web/components/issues/issue-detail/inbox/main-content.tsx
index b49c0286f9..a576f60345 100644
--- a/web/components/issues/issue-detail/inbox/main-content.tsx
+++ b/web/components/issues/issue-detail/inbox/main-content.tsx
@@ -79,6 +79,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
issueOperations={issueOperations}
disabled={!is_editable}
value={issue.description_html}
+ initialValue={issue.description_html}
/>
{currentUser && (
diff --git a/web/components/issues/title-input.tsx b/web/components/issues/title-input.tsx
index e189741904..55dd80b87b 100644
--- a/web/components/issues/title-input.tsx
+++ b/web/components/issues/title-input.tsx
@@ -31,7 +31,7 @@ export const IssueTitleInput: FC = observer((props) => {
}, [value]);
useEffect(() => {
- if (debouncedValue) {
+ if (debouncedValue && debouncedValue !== value) {
issueOperations.update(workspaceSlug, projectId, issueId, { name: debouncedValue }, false).finally(() => {
setIsSubmitting("saved");
});
From 7381a818a9e1ec52c8e14dac6859895f80fc4be6 Mon Sep 17 00:00:00 2001
From: sriram veeraghanta
Date: Mon, 19 Feb 2024 00:34:54 +0530
Subject: [PATCH 08/11] fix: description editor fixes
---
web/components/issues/description-input.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/web/components/issues/description-input.tsx b/web/components/issues/description-input.tsx
index d18fad6e5a..8e0e47ae93 100644
--- a/web/components/issues/description-input.tsx
+++ b/web/components/issues/description-input.tsx
@@ -84,7 +84,6 @@ export const IssueDescriptionInput: FC = observer((p
dragDropEnabled
customClassName="min-h-[150px] shadow-sm"
onChange={(description: Object, description_html: string) => {
- // setShowAlert(true);
setIsSubmitting("submitting");
setDescriptionHTML(description_html);
}}
From 170f30c7dde872758d7ec1fc033d7a34ccca0269 Mon Sep 17 00:00:00 2001
From: sriram veeraghanta
Date: Mon, 19 Feb 2024 00:35:21 +0530
Subject: [PATCH 09/11] fix: editor fixes
---
packages/editor/rich-text-editor/src/ui/index.tsx | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/packages/editor/rich-text-editor/src/ui/index.tsx b/packages/editor/rich-text-editor/src/ui/index.tsx
index 43c3f8f343..fe5752849e 100644
--- a/packages/editor/rich-text-editor/src/ui/index.tsx
+++ b/packages/editor/rich-text-editor/src/ui/index.tsx
@@ -15,6 +15,7 @@ import { EditorBubbleMenu } from "src/ui/menus/bubble-menu";
export type IRichTextEditor = {
value: string;
+ initialValue?: string;
dragDropEnabled?: boolean;
uploadFile: UploadImage;
restoreFile: RestoreImage;
@@ -54,6 +55,7 @@ const RichTextEditor = ({
setShouldShowAlert,
editorContentCustomClassNames,
value,
+ initialValue,
uploadFile,
deleteFile,
noBorder,
@@ -97,6 +99,10 @@ const RichTextEditor = ({
customClassName,
});
+ React.useEffect(() => {
+ if (editor && initialValue) editor.commands.setContent(initialValue);
+ }, [editor, initialValue]);
+
if (!editor) return null;
return (
From 17e5663e81a88606b27bd9731c65574b09d45d7e Mon Sep 17 00:00:00 2001
From: sriram veeraghanta
Date: Mon, 19 Feb 2024 12:54:45 +0530
Subject: [PATCH 10/11] fix: description autosave fixes
---
web/components/issues/description-input.tsx | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/web/components/issues/description-input.tsx b/web/components/issues/description-input.tsx
index 8e0e47ae93..86ef898c0a 100644
--- a/web/components/issues/description-input.tsx
+++ b/web/components/issues/description-input.tsx
@@ -38,23 +38,22 @@ export const IssueDescriptionInput: FC = observer((p
const workspaceId = getWorkspaceBySlug(workspaceSlug)?.id as string;
useEffect(() => {
- if (value) setDescriptionHTML(value);
+ setDescriptionHTML(value);
}, [value]);
useEffect(() => {
if (debouncedValue && debouncedValue !== value) {
- setIsSubmitting("submitted");
issueOperations
.update(workspaceSlug, projectId, issueId, { description_html: debouncedValue }, false)
.finally(() => {
- setIsSubmitting("saved");
+ setIsSubmitting("submitted");
});
}
// DO NOT Add more dependencies here. It will cause multiple requests to be sent.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedValue]);
- if (!descriptionHTML && descriptionHTML !== "") {
+ if (!descriptionHTML) {
return (
@@ -79,13 +78,13 @@ export const IssueDescriptionInput: FC = observer((p
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
- value={descriptionHTML}
+ value={descriptionHTML === "" ? "" : descriptionHTML}
initialValue={initialValue}
dragDropEnabled
customClassName="min-h-[150px] shadow-sm"
onChange={(description: Object, description_html: string) => {
setIsSubmitting("submitting");
- setDescriptionHTML(description_html);
+ setDescriptionHTML(description_html === "" ? "" : description_html);
}}
mentionSuggestions={mentionSuggestions}
mentionHighlights={mentionHighlights}
From bbbd7047d33d9f6f223ff7d5c3a1f79892afcbb1 Mon Sep 17 00:00:00 2001
From: guru_sainath
Date: Mon, 19 Feb 2024 15:43:57 +0530
Subject: [PATCH 11/11] fix: issue description empty state initial load in
inbox and issue detail page (#3696)
* fix: updated description init loading and added loading confirmation alert in inbox issues, issue peek overview, and issue detail
* fix: updated the space issue in the editor and removed unwanted props in the description-input for issues
---
.../editor/rich-text-editor/src/ui/index.tsx | 2 +-
web/components/issues/description-input.tsx | 20 +++++------
.../issue-detail/inbox/main-content.tsx | 33 +++++++++++++++----
.../issues/issue-detail/main-content.tsx | 32 ++++++++++++++----
.../issues/peek-overview/issue-detail.tsx | 28 ++++++++++------
5 files changed, 80 insertions(+), 35 deletions(-)
diff --git a/packages/editor/rich-text-editor/src/ui/index.tsx b/packages/editor/rich-text-editor/src/ui/index.tsx
index fe5752849e..4bcb340fd8 100644
--- a/packages/editor/rich-text-editor/src/ui/index.tsx
+++ b/packages/editor/rich-text-editor/src/ui/index.tsx
@@ -100,7 +100,7 @@ const RichTextEditor = ({
});
React.useEffect(() => {
- if (editor && initialValue) editor.commands.setContent(initialValue);
+ if (editor && initialValue && editor.getHTML() != initialValue) editor.commands.setContent(initialValue);
}, [editor, initialValue]);
if (!editor) return null;
diff --git a/web/components/issues/description-input.tsx b/web/components/issues/description-input.tsx
index 86ef898c0a..79634fa84a 100644
--- a/web/components/issues/description-input.tsx
+++ b/web/components/issues/description-input.tsx
@@ -1,5 +1,4 @@
import { FC, useState, useEffect } from "react";
-import { observer } from "mobx-react";
// components
import { Loader } from "@plane/ui";
import { RichReadOnlyEditor, RichTextEditor } from "@plane/rich-text-editor";
@@ -14,19 +13,18 @@ import { TIssueOperations } from "./issue-detail";
import useDebounce from "hooks/use-debounce";
export type IssueDescriptionInputProps = {
- disabled?: boolean;
- value: string | undefined | null;
workspaceSlug: string;
- isSubmitting: "submitting" | "submitted" | "saved";
- setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
- issueOperations: TIssueOperations;
projectId: string;
issueId: string;
- initialValue?: string;
+ value: string | undefined;
+ initialValue: string | undefined;
+ disabled?: boolean;
+ issueOperations: TIssueOperations;
+ setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
};
-export const IssueDescriptionInput: FC = observer((props) => {
- const { disabled, value, workspaceSlug, setIsSubmitting, issueId, issueOperations, projectId, initialValue } = props;
+export const IssueDescriptionInput: FC = (props) => {
+ const { workspaceSlug, projectId, issueId, value, initialValue, disabled, issueOperations, setIsSubmitting } = props;
// states
const [descriptionHTML, setDescriptionHTML] = useState(value);
// store hooks
@@ -78,7 +76,7 @@ export const IssueDescriptionInput: FC = observer((p
uploadFile={fileService.getUploadFileFunction(workspaceSlug)}
deleteFile={fileService.getDeleteImageFunction(workspaceId)}
restoreFile={fileService.getRestoreImageFunction(workspaceId)}
- value={descriptionHTML === "" ? "" : descriptionHTML}
+ value={descriptionHTML}
initialValue={initialValue}
dragDropEnabled
customClassName="min-h-[150px] shadow-sm"
@@ -90,4 +88,4 @@ export const IssueDescriptionInput: FC = observer((p
mentionHighlights={mentionHighlights}
/>
);
-});
+};
diff --git a/web/components/issues/issue-detail/inbox/main-content.tsx b/web/components/issues/issue-detail/inbox/main-content.tsx
index a576f60345..d753be02fe 100644
--- a/web/components/issues/issue-detail/inbox/main-content.tsx
+++ b/web/components/issues/issue-detail/inbox/main-content.tsx
@@ -1,7 +1,8 @@
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useIssueDetail, useProjectState, useUser } from "hooks/store";
+import useReloadConfirmations from "hooks/use-reload-confirmation";
// components
import { IssueUpdateStatus, TIssueOperations } from "components/issues";
import { IssueTitleInput } from "../../title-input";
@@ -31,12 +32,31 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
const {
issue: { getIssueById },
} = useIssueDetail();
+ const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
- const issue = getIssueById(issueId);
+ useEffect(() => {
+ if (isSubmitting === "submitted") {
+ setShowAlert(false);
+ setTimeout(async () => {
+ setIsSubmitting("saved");
+ }, 3000);
+ } else if (isSubmitting === "submitting") {
+ setShowAlert(true);
+ }
+ }, [isSubmitting, setShowAlert, setIsSubmitting]);
+
+ const issue = issueId ? getIssueById(issueId) : undefined;
if (!issue) return <>>;
const currentIssueState = projectStates?.find((s) => s.id === issue.state_id);
+ const issueDescription =
+ issue.description_html !== undefined || issue.description_html !== null
+ ? issue.description_html != ""
+ ? issue.description_html
+ : ""
+ : undefined;
+
return (
<>
@@ -74,12 +94,11 @@ export const InboxIssueMainContent: React.FC = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
issueId={issue.id}
- isSubmitting={isSubmitting}
- setIsSubmitting={(value) => setIsSubmitting(value)}
- issueOperations={issueOperations}
+ value={issueDescription}
+ initialValue={issueDescription}
disabled={!is_editable}
- value={issue.description_html}
- initialValue={issue.description_html}
+ issueOperations={issueOperations}
+ setIsSubmitting={(value) => setIsSubmitting(value)}
/>
{currentUser && (
diff --git a/web/components/issues/issue-detail/main-content.tsx b/web/components/issues/issue-detail/main-content.tsx
index 968b9faa58..719129d98f 100644
--- a/web/components/issues/issue-detail/main-content.tsx
+++ b/web/components/issues/issue-detail/main-content.tsx
@@ -1,7 +1,8 @@
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { observer } from "mobx-react-lite";
// hooks
import { useIssueDetail, useProjectState, useUser } from "hooks/store";
+import useReloadConfirmations from "hooks/use-reload-confirmation";
// components
import { IssueAttachmentRoot, IssueUpdateStatus } from "components/issues";
import { IssueTitleInput } from "../title-input";
@@ -33,12 +34,31 @@ export const IssueMainContent: React.FC = observer((props) => {
const {
issue: { getIssueById },
} = useIssueDetail();
+ const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
- const issue = getIssueById(issueId);
+ useEffect(() => {
+ if (isSubmitting === "submitted") {
+ setShowAlert(false);
+ setTimeout(async () => {
+ setIsSubmitting("saved");
+ }, 2000);
+ } else if (isSubmitting === "submitting") {
+ setShowAlert(true);
+ }
+ }, [isSubmitting, setShowAlert, setIsSubmitting]);
+
+ const issue = issueId ? getIssueById(issueId) : undefined;
if (!issue) return <>>;
const currentIssueState = projectStates?.find((s) => s.id === issue.state_id);
+ const issueDescription =
+ issue.description_html !== undefined || issue.description_html !== null
+ ? issue.description_html != ""
+ ? issue.description_html
+ : ""
+ : undefined;
+
return (
<>
@@ -78,11 +98,11 @@ export const IssueMainContent: React.FC = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={issue.project_id}
issueId={issue.id}
- isSubmitting={isSubmitting}
- setIsSubmitting={(value) => setIsSubmitting(value)}
- issueOperations={issueOperations}
+ value={issueDescription}
+ initialValue={issueDescription}
disabled={!is_editable}
- value={issue.description_html}
+ issueOperations={issueOperations}
+ setIsSubmitting={(value) => setIsSubmitting(value)}
/>
{currentUser && (
diff --git a/web/components/issues/peek-overview/issue-detail.tsx b/web/components/issues/peek-overview/issue-detail.tsx
index 0134d35eea..7f540874c7 100644
--- a/web/components/issues/peek-overview/issue-detail.tsx
+++ b/web/components/issues/peek-overview/issue-detail.tsx
@@ -30,12 +30,6 @@ export const PeekOverviewIssueDetails: FC = observer(
} = useIssueDetail();
// hooks
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
- // derived values
- const issue = getIssueById(issueId);
-
- if (!issue) return <>>;
-
- const projectDetails = getProjectById(issue?.project_id);
useEffect(() => {
if (isSubmitting === "submitted") {
@@ -48,6 +42,18 @@ export const PeekOverviewIssueDetails: FC = observer(
}
}, [isSubmitting, setShowAlert, setIsSubmitting]);
+ const issue = issueId ? getIssueById(issueId) : undefined;
+ if (!issue) return <>>;
+
+ const projectDetails = getProjectById(issue?.project_id);
+
+ const issueDescription =
+ issue.description_html !== undefined || issue.description_html !== null
+ ? issue.description_html != ""
+ ? issue.description_html
+ : ""
+ : undefined;
+
return (
<>
@@ -63,16 +69,18 @@ export const PeekOverviewIssueDetails: FC = observer(
disabled={disabled}
value={issue.name}
/>
+
setIsSubmitting(value)}
- issueOperations={issueOperations}
+ value={issueDescription}
+ initialValue={issueDescription}
disabled={disabled}
- value={issue.description_html}
+ issueOperations={issueOperations}
+ setIsSubmitting={(value) => setIsSubmitting(value)}
/>
+
{currentUser && (
| |