Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions space/core/components/editor/lite-text-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { EditorRefApi, ILiteTextEditor, LiteTextEditorWithRef } from "@plane/edi
import { IssueCommentToolbar } from "@/components/editor";
// helpers
import { cn } from "@/helpers/common.helper";
import { isEmptyHtmlString } from "@/helpers/string.helper";
import { isCommentEmpty } from "@/helpers/string.helper";
// hooks
import { useMention } from "@/hooks/use-mention";
// services
Expand Down Expand Up @@ -33,10 +33,7 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
function isMutableRefObject<T>(ref: React.ForwardedRef<T>): ref is React.MutableRefObject<T | null> {
return !!ref && typeof ref === "object" && "current" in ref;
}
const isEmpty =
props.initialValue?.trim() === "" ||
props.initialValue === "<p></p>" ||
(isEmptyHtmlString(props.initialValue ?? "") && !props.initialValue?.includes("mention-component"));
const isEmpty = isCommentEmpty(props.initialValue);

return (
<div className="border border-custom-border-200 rounded p-3 space-y-3">
Expand Down
22 changes: 19 additions & 3 deletions space/helpers/string.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,29 @@ export const checkEmailValidity = (email: string): boolean => {
return isEmailValid;
};

export const isEmptyHtmlString = (htmlString: string) => {
export const isEmptyHtmlString = (htmlString: string, allowedHTMLTags: string[] = []) => {
// Remove HTML tags using regex
const cleanText = DOMPurify.sanitize(htmlString, { ALLOWED_TAGS: ["img"] });
const cleanText = DOMPurify.sanitize(htmlString, { ALLOWED_TAGS: allowedHTMLTags });
// Trim the string and check if it's empty
return cleanText.trim() === "";
};

/**
* @description this function returns whether a comment is empty or not by checking for the following conditions-
* 1. If comment is undefined
* 2. If comment is an empty string
* 3. If comment is "<p></p>"
* @param {string | undefined} comment
* @returns {boolean}
*/
export const isCommentEmpty = (comment: string | undefined): boolean => {
// return true if comment is undefined
if (!comment) return true;
return (
comment?.trim() === "" || comment === "<p></p>" || isEmptyHtmlString(comment ?? "", ["img", "mention-component"])
);
};

export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " ");

export const capitalizeFirstLetter = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
export const capitalizeFirstLetter = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IssueCommentToolbar } from "@/components/editor";
import { EIssueCommentAccessSpecifier } from "@/constants/issue";
// helpers
import { cn } from "@/helpers/common.helper";
import { isEmptyHtmlString } from "@/helpers/string.helper";
import { isCommentEmpty } from "@/helpers/string.helper";
// hooks
import { useMember, useMention, useUser } from "@/hooks/store";
// services
Expand Down Expand Up @@ -59,10 +59,7 @@ export const LiteTextEditor = React.forwardRef<EditorRefApi, LiteTextEditorWrapp
user: currentUser ?? undefined,
});

const isEmpty =
props.initialValue?.trim() === "" ||
props.initialValue === "<p></p>" ||
(isEmptyHtmlString(props.initialValue ?? "") && !props.initialValue?.includes("mention-component"));
const isEmpty = isCommentEmpty(props.initialValue);

function isMutableRefObject<T>(ref: React.ForwardedRef<T>): ref is React.MutableRefObject<T | null> {
return !!ref && typeof ref === "object" && "current" in ref;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { LiteTextEditor, LiteTextReadOnlyEditor } from "@/components/editor";
// constants
import { EIssueCommentAccessSpecifier } from "@/constants/issue";
// helpers
import { isEmptyHtmlString } from "@/helpers/string.helper";
import { isCommentEmpty } from "@/helpers/string.helper";
// hooks
import { useIssueDetail, useUser, useWorkspace } from "@/hooks/store";
// components
Expand Down Expand Up @@ -80,10 +80,8 @@ export const IssueCommentCard: FC<TIssueCommentCard> = observer((props) => {
isEditing && setFocus("comment_html");
}, [isEditing, setFocus]);

const isEmpty =
watch("comment_html")?.trim() === "" ||
watch("comment_html") === "<p></p>" ||
isEmptyHtmlString(watch("comment_html") ?? "");
const commentHTML = watch("comment_html");
const isEmpty = isCommentEmpty(commentHTML);

if (!comment || !currentUser) return <></>;
return (
Expand Down Expand Up @@ -148,7 +146,7 @@ export const IssueCommentCard: FC<TIssueCommentCard> = observer((props) => {
workspaceSlug={workspaceSlug}
ref={editorRef}
id={comment.id}
initialValue={watch("comment_html") ?? ""}
initialValue={commentHTML ?? ""}
value={null}
onChange={(comment_json, comment_html) => setValue("comment_html", comment_html)}
onEnterKeyPress={(e) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { LiteTextEditor } from "@/components/editor/lite-text-editor/lite-text-e
import { EIssueCommentAccessSpecifier } from "@/constants/issue";
// helpers
import { cn } from "@/helpers/common.helper";
import { isEmptyHtmlString } from "@/helpers/string.helper";
import { isCommentEmpty } from "@/helpers/string.helper";
// hooks
import { useIssueDetail, useWorkspace } from "@/hooks/store";
// editor
Expand Down Expand Up @@ -53,10 +53,7 @@ export const IssueCommentCreate: FC<TIssueCommentCreate> = (props) => {
});

const commentHTML = watch("comment_html");
const isEmpty =
commentHTML?.trim() === "" ||
commentHTML === "<p></p>" ||
(isEmptyHtmlString(commentHTML ?? "") && !commentHTML?.includes("mention-component"));
const isEmpty = isCommentEmpty(commentHTML);

return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const DraftIssueLayout: React.FC<DraftIssueProps> = observer((props) => {
if (
issueKey === "description_html" &&
changesMade.description_html &&
isEmptyHtmlString(changesMade.description_html)
isEmptyHtmlString(changesMade.description_html, ["img"])
)
delete changesMade.description_html;
});
Expand Down
18 changes: 16 additions & 2 deletions web/helpers/string.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,23 @@ export const checkEmailValidity = (email: string): boolean => {
return isEmailValid;
};

export const isEmptyHtmlString = (htmlString: string) => {
export const isEmptyHtmlString = (htmlString: string, allowedHTMLTags: string[] = []) => {
// Remove HTML tags using regex
const cleanText = DOMPurify.sanitize(htmlString, { ALLOWED_TAGS: ["img"] });
const cleanText = DOMPurify.sanitize(htmlString, { ALLOWED_TAGS: allowedHTMLTags });
Comment on lines +233 to +235
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve the enhancement to isEmptyHtmlString.

The addition of the allowedHTMLTags parameter enhances the flexibility of the isEmptyHtmlString function, allowing it to be customized for different scenarios. This change supports the PR's objective by enabling the function to consider specific HTML tags as non-empty.

The code change is approved.

Would you like me to help create unit tests for this function to ensure it handles various HTML inputs correctly?

// Trim the string and check if it's empty
return cleanText.trim() === "";
};

/**
* @description this function returns whether a comment is empty or not by checking for the following conditions-
* 1. If comment is undefined
* 2. If comment is an empty string
* 3. If comment is "<p></p>"
* @param {string | undefined} comment
* @returns {boolean}
*/
export const isCommentEmpty = (comment: string | undefined): boolean => {
// return true if comment is undefined
if (!comment) return true;
return comment?.trim() === "" || comment === "<p></p>" || isEmptyHtmlString(comment ?? "", ["mention-component"]);
};