Compare commits

...

2 Commits

Author SHA1 Message Date
Aaryan Khandelwal f32458eb7b chore: add reload logic 2026-01-29 14:38:05 +05:30
Aaryan Khandelwal 5a31c6449d feat: comments draft 2026-01-29 13:29:56 +05:30
@@ -1,18 +1,25 @@
/**
* Copyright (c) 2023-present Plane Software, Inc. and contributors
* SPDX-License-Identifier: AGPL-3.0-only
* See the LICENSE file for details.
* SPDX-FileCopyrightText: 2023-present Plane Software, Inc.
* SPDX-License-Identifier: LicenseRef-Plane-Commercial
*
* Licensed under the Plane Commercial License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://plane.so/legals/eula
*
* DO NOT remove or modify this notice.
* NOTICE: Proprietary and confidential. Unauthorized use or distribution is prohibited.
*/
import type { FC } from "react";
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { observer } from "mobx-react";
import { useForm, Controller } from "react-hook-form";
// plane imports
import { EIssueCommentAccessSpecifier } from "@plane/constants";
import type { EditorRefApi } from "@plane/editor";
import { useLocalStorage } from "@plane/hooks";
import type { TIssueComment, TCommentsOperations } from "@plane/types";
import { cn, isCommentEmpty } from "@plane/utils";
import { isCommentEmpty } from "@plane/utils";
// components
import { LiteTextEditor } from "@/components/editor/lite-text";
// hooks
@@ -51,16 +58,21 @@ export const CommentCreate = observer(function CommentCreate(props: TCommentCrea
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug)?.id as string;
// form info
const {
getValues,
handleSubmit,
control,
watch,
formState: { isSubmitting },
reset,
} = useForm<Partial<TIssueComment>>({
defaultValues: {
comment_html: "<p></p>",
},
});
watch,
} = useForm<Partial<TIssueComment>>();
// local storage
const commentId = "add_comment_" + entityId;
const { storedValue: storedCommentDescription, setValue: setStoredCommentDescription } = useLocalStorage<
string | undefined
>(commentId, undefined);
// derived form values
const commentHTML = watch("comment_html");
const isEmpty = isCommentEmpty(commentHTML);
const onSubmit = async (formData: Partial<TIssueComment>) => {
try {
@@ -78,22 +90,40 @@ export const CommentCreate = observer(function CommentCreate(props: TCommentCrea
}
setUploadedAssetIds([]);
}
setStoredCommentDescription(undefined);
} catch (error) {
console.error(error);
} finally {
reset({
comment_html: "<p></p>",
comment_html: "",
});
editorRef.current?.clearEditor();
}
};
const commentHTML = watch("comment_html");
const isEmpty = isCommentEmpty(commentHTML ?? undefined);
// auto save comment description to local storage on unmount and page reload/close
useEffect(() => {
const saveCommentToLocalStorage = () => {
const latestDescription = getValues("comment_html");
const isLatestDescriptionEmpty = isCommentEmpty(latestDescription);
if (latestDescription && !isLatestDescriptionEmpty) {
setStoredCommentDescription(latestDescription);
}
};
window.addEventListener("beforeunload", saveCommentToLocalStorage);
return () => {
saveCommentToLocalStorage();
window.removeEventListener("beforeunload", saveCommentToLocalStorage);
};
// react-hook-form methods should not be included in the dependency array
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setStoredCommentDescription]);
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className={cn("sticky bottom-0 z-[4] bg-surface-1 sm:static")}
className="sticky sm:static bottom-0 z-4 bg-surface-1"
onKeyDown={(e) => {
if (
e.key === "Enter" &&
@@ -104,7 +134,7 @@ export const CommentCreate = observer(function CommentCreate(props: TCommentCrea
!isSubmitting &&
editorRef.current?.isEditorReadyToDiscard()
)
handleSubmit(onSubmit)(e);
void handleSubmit(onSubmit)(e);
}}
>
<Controller
@@ -114,23 +144,22 @@ export const CommentCreate = observer(function CommentCreate(props: TCommentCrea
<Controller
name="comment_html"
control={control}
render={({ field: { value, onChange } }) => (
render={({ field: { onChange } }) => (
<LiteTextEditor
editable
workspaceId={workspaceId}
id={"add_comment_" + entityId}
value={"<p></p>"}
id={commentId}
workspaceSlug={workspaceSlug}
projectId={projectId}
onEnterKeyPress={(e) => {
if (!isEmpty && !isSubmitting) {
handleSubmit(onSubmit)(e);
void handleSubmit(onSubmit)(e);
}
}}
ref={editorRef}
initialValue={value ?? "<p></p>"}
initialValue={storedCommentDescription || "<p></p>"}
containerClassName="min-h-min"
onChange={(comment_json, comment_html) => onChange(comment_html)}
onChange={(_comment_json, comment_html) => onChange(comment_html)}
accessSpecifier={accessValue ?? EIssueCommentAccessSpecifier.INTERNAL}
handleAccessChange={onAccessChange}
isSubmitting={isSubmitting}