feat: version history for issues

This commit is contained in:
Aaryan Khandelwal
2024-11-14 20:42:42 +05:30
parent acd68595d5
commit 2756c383ba
6 changed files with 49 additions and 24 deletions

View File

@@ -61,8 +61,7 @@ export const IssueVersionEditor: React.FC<TVersionEditorProps> = observer((props
<RichTextReadOnlyEditor
id={activeVersion ?? ""}
initialValue={description ?? "<p></p>"}
containerClassName="p-0 pb-64 border-none"
editorClassName="pl-10"
containerClassName="!px-5 pb-64 border-none"
workspaceSlug={workspaceSlug?.toString() ?? ""}
projectId={projectId?.toString() ?? ""}
/>

View File

@@ -4,13 +4,14 @@ import { FC, useCallback, useRef } from "react";
import debounce from "lodash/debounce";
import { observer } from "mobx-react";
// plane editor
import { convertBinaryDataToBase64String, EditorRefApi } from "@plane/editor";
import { convertBinaryDataToBase64String, EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor";
// types
import { EFileAssetType } from "@plane/types/src/enums";
// plane ui
import { Loader } from "@plane/ui";
// components
import { CollaborativeRichTextEditor, CollaborativeRichTextReadOnlyEditor } from "@/components/editor";
import { IssueVersionHistory } from "@/components/issues";
// helpers
import { getDescriptionPlaceholder } from "@/helpers/issue.helper";
// hooks
@@ -49,6 +50,7 @@ export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = observer((p
} = props;
// refs
const editorRef = useRef<EditorRefApi>(null);
const readOnlyEditorRef = useRef<EditorReadOnlyRefApi>(null);
// store hooks
const { getWorkspaceBySlug } = useWorkspace();
// derived values
@@ -95,7 +97,7 @@ export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = observer((p
);
return (
<>
<div>
{!disabled ? (
<CollaborativeRichTextEditor
key={issueId}
@@ -132,6 +134,7 @@ export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = observer((p
/>
) : (
<CollaborativeRichTextReadOnlyEditor
ref={readOnlyEditorRef}
containerClassName={containerClassName}
descriptionBinary={savedDescriptionBinary}
descriptionHTML={descriptionHTML}
@@ -140,6 +143,13 @@ export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = observer((p
workspaceSlug={workspaceSlug}
/>
)}
</>
{/* version history overlay */}
<IssueVersionHistory
disabled={!!disabled}
editorRef={editorRef.current}
issueId={issueId}
readOnlyEditorRef={readOnlyEditorRef.current}
/>
</div>
);
});

View File

@@ -3,8 +3,9 @@
import React, { FC, useState } from "react";
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
import { ArchiveIcon, ArchiveRestoreIcon, LinkIcon, Trash2 } from "lucide-react";
import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
import { ArchiveIcon, ArchiveRestoreIcon, History, LinkIcon, Trash2 } from "lucide-react";
// plane ui
import { CustomMenu, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
// components
import { ArchiveIssueModal, DeleteIssueModal, IssueSubscription } from "@/components/issues";
// constants
@@ -25,6 +26,8 @@ import {
} from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { usePlatformOS } from "@/hooks/use-platform-os";
import { useQueryParams } from "@/hooks/use-query-params";
// plane web constants
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
type Props = {
@@ -35,15 +38,12 @@ type Props = {
export const IssueDetailQuickActions: FC<Props> = observer((props) => {
const { workspaceSlug, projectId, issueId } = props;
// states
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [archiveIssueModal, setArchiveIssueModal] = useState(false);
const [isRestoring, setIsRestoring] = useState(false);
// router
const router = useAppRouter();
// hooks
const { data: currentUser } = useUser();
const { allowPermissions } = useUserPermissions();
@@ -62,13 +62,11 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
} = useIssues(EIssuesStoreType.ARCHIVED);
const { captureIssueEvent } = useEventTracker();
const pathname = usePathname();
const { updateQueryParams } = useQueryParams();
// derived values
const issue = getIssueById(issueId);
if (!issue) return <></>;
const stateDetails = getStateById(issue.state_id);
// handlers
const handleCopyText = () => {
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
@@ -80,7 +78,6 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
});
});
};
const handleDeleteIssue = async () => {
try {
const deleteIssue = issue?.archived_at ? removeArchivedIssue : removeIssue;
@@ -109,7 +106,6 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
});
}
};
const handleArchiveIssue = async () => {
try {
await archiveIssue(workspaceSlug, projectId, issueId).then(() => {
@@ -128,7 +124,6 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
});
}
};
const handleRestore = async () => {
if (!workspaceSlug || !projectId || !issueId) return;
@@ -153,6 +148,21 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
.finally(() => setIsRestoring(false));
};
const MENU_ITEMS = [
{
key: "version-history",
action: () => {
// add query param, version=current to the route
const updatedRoute = updateQueryParams({
paramsToAdd: { version: "current" },
});
router.push(updatedRoute);
},
label: "Version history",
icon: History,
shouldRender: true,
},
];
// auth
const isEditable = allowPermissions([EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.PROJECT);
const canRestoreIssue = allowPermissions(
@@ -239,7 +249,6 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
)}
</>
)}
{isEditable && (
<Tooltip tooltipContent="Delete" isMobile={isMobile}>
<button
@@ -251,6 +260,17 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
</button>
</Tooltip>
)}
<CustomMenu maxHeight="lg" placement="bottom-start" verticalEllipsis closeOnSelect>
{MENU_ITEMS.map((item) => {
if (!item.shouldRender) return null;
return (
<CustomMenu.MenuItem key={item.key} onClick={item.action} className="flex items-center gap-2">
<item.icon className="h-3 w-3" />
{item.label}
</CustomMenu.MenuItem>
);
})}
</CustomMenu>
</div>
</div>
</div>

View File

@@ -9,7 +9,7 @@ import { TIssue } from "@plane/types";
import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
// components
import { EmptyState } from "@/components/common";
import { IssuePeekOverview, IssueVersionHistory } from "@/components/issues";
import { IssuePeekOverview } from "@/components/issues";
// constants
import { ISSUE_UPDATED, ISSUE_DELETED, ISSUE_ARCHIVED } from "@/constants/event-tracker";
import { EIssuesStoreType } from "@/constants/issue";
@@ -344,7 +344,6 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
pathname,
]
);
// issue details
const issue = getIssueById(issueId);
// checking if issue is editable, based on user role
@@ -390,8 +389,6 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
)}
{/* peek overview */}
<IssuePeekOverview />
{/* version history overlay */}
<IssueVersionHistory />
</>
);
});

View File

@@ -14,9 +14,9 @@ const issueVersionService = new IssueVersionService();
type Props = {
disabled: boolean;
editorRef: EditorRefApi;
editorRef: EditorRefApi | null;
issueId: string;
readOnlyEditorRef: EditorReadOnlyRefApi;
readOnlyEditorRef: EditorReadOnlyRefApi | null;
};
export const IssueVersionHistory: React.FC<Props> = (props) => {

View File

@@ -4,6 +4,5 @@ export * from "./header";
export * from "./list";
export * from "./loaders";
export * from "./modals";
export * from "./version";
export * from "./pages-list-main-content";
export * from "./pages-list-view";