From 47c13c991654bbd7e8708e85301c38f663d19684 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 4 Jan 2024 22:30:22 -0500 Subject: [PATCH] Show comment context in thread --- .../Document/components/CommentThread.tsx | 9 ++++ .../Document/components/CommentThreadItem.tsx | 41 +++++++++++++++---- app/typings/styled-components.d.ts | 1 + shared/editor/components/Styles.ts | 4 +- shared/styles/theme.ts | 1 + shared/utils/ProsemirrorHelper.ts | 16 ++++---- 6 files changed, 55 insertions(+), 17 deletions(-) diff --git a/app/scenes/Document/components/CommentThread.tsx b/app/scenes/Document/components/CommentThread.tsx index 1bfc741fa..e82c98f65 100644 --- a/app/scenes/Document/components/CommentThread.tsx +++ b/app/scenes/Document/components/CommentThread.tsx @@ -11,6 +11,7 @@ import { ProsemirrorData } from "@shared/types"; import Comment from "~/models/Comment"; import Document from "~/models/Document"; import Avatar from "~/components/Avatar"; +import { useDocumentContext } from "~/components/DocumentContext"; import Fade from "~/components/Fade"; import Flex from "~/components/Flex"; import { ResizingHeightContainer } from "~/components/ResizingHeightContainer"; @@ -63,6 +64,7 @@ function CommentThread({ recessed, focused, }: Props) { + const { editor } = useDocumentContext(); const { comments } = useStores(); const topRef = React.useRef(null); const user = useCurrentUser(); @@ -75,6 +77,11 @@ function CommentThread({ }); const can = usePolicy(document); + const highlightedCommentMarks = editor + ?.getComments() + .filter((comment) => comment.id === thread.id); + const highlightedText = highlightedCommentMarks?.map((c) => c.text).join(""); + const commentsInThread = comments .inThread(thread.id) .filter((comment) => !comment.isNew); @@ -167,7 +174,9 @@ function CommentThread({ return ( editor?.removeComment(comment.id)} key={comment.id} firstOfThread={index === 0} lastOfThread={index === commentsInThread.length - 1 && !draft} diff --git a/app/scenes/Document/components/CommentThreadItem.tsx b/app/scenes/Document/components/CommentThreadItem.tsx index 65db246ea..22962a60a 100644 --- a/app/scenes/Document/components/CommentThreadItem.tsx +++ b/app/scenes/Document/components/CommentThreadItem.tsx @@ -14,13 +14,12 @@ import { Minute } from "@shared/utils/time"; import Comment from "~/models/Comment"; import Avatar from "~/components/Avatar"; import ButtonSmall from "~/components/ButtonSmall"; -import { useDocumentContext } from "~/components/DocumentContext"; import Flex from "~/components/Flex"; import Text from "~/components/Text"; import Time from "~/components/Time"; import useBoolean from "~/hooks/useBoolean"; import CommentMenu from "~/menus/CommentMenu"; -import { hover } from "~/styles"; +import { hover, truncateMultiline } from "~/styles"; import CommentEditor from "./CommentEditor"; /** @@ -74,6 +73,10 @@ type Props = { previousCommentCreatedAt?: string; /** Whether the user can reply in the thread */ canReply: boolean; + /** Callback when the comment has been deleted */ + onDelete: () => void; + /** Text to highlight at the top of the comment */ + highlightedText?: string; }; function CommentThreadItem({ @@ -84,8 +87,9 @@ function CommentThreadItem({ dir, previousCommentCreatedAt, canReply, + onDelete, + highlightedText, }: Props) { - const { editor } = useDocumentContext(); const { t } = useTranslation(); const [forceRender, setForceRender] = React.useState(0); const [data, setData] = React.useState(toJS(comment.data)); @@ -120,10 +124,6 @@ function CommentThreadItem({ } }; - const handleDelete = () => { - editor?.removeComment(comment.id); - }; - const handleCancel = () => { setData(toJS(comment.data)); setReadOnly(); @@ -174,6 +174,9 @@ function CommentThreadItem({ )} )} + {highlightedText && ( + {highlightedText} + )} )} @@ -237,6 +240,28 @@ const Body = styled.form` border-radius: 2px; `; +const HighlightedText = styled(Text)` + position: relative; + color: ${s("textSecondary")}; + font-size: 14px; + padding: 0 8px; + margin: 4px 0; + display: inline-block; + + ${truncateMultiline(3)} + + &:after { + content: ""; + width: 2px; + position: absolute; + left: 0; + top: 2px; + bottom: 2px; + background: ${s("commentMarkBackground")}; + border-radius: 2px; + } +`; + const Menu = styled(CommentMenu)<{ dir?: "rtl" | "ltr" }>` position: absolute; left: ${(props) => (props.dir !== "rtl" ? "auto" : "4px")}; diff --git a/app/typings/styled-components.d.ts b/app/typings/styled-components.d.ts index bb65ceb13..be368444f 100644 --- a/app/typings/styled-components.d.ts +++ b/app/typings/styled-components.d.ts @@ -134,6 +134,7 @@ declare module "styled-components" { textDiffDeleted: string; textDiffDeletedBackground: string; placeholder: string; + commentMarkBackground: string; commentBackground: string; sidebarBackground: string; sidebarActiveBackground: string; diff --git a/shared/editor/components/Styles.ts b/shared/editor/components/Styles.ts index b0112d934..6ef819cf6 100644 --- a/shared/editor/components/Styles.ts +++ b/shared/editor/components/Styles.ts @@ -788,13 +788,13 @@ h6 { } .comment-marker { - border-bottom: 2px solid ${transparentize(0.5, props.theme.brand.marine)}; + border-bottom: 2px solid ${props.theme.commentMarkBackground}; transition: background 100ms ease-in-out; border-radius: 2px; &:hover { ${props.readOnly ? "cursor: var(--pointer);" : ""} - background: ${transparentize(0.5, props.theme.brand.marine)}; + background: ${props.theme.commentMarkBackground}; } } diff --git a/shared/styles/theme.ts b/shared/styles/theme.ts index 3b849efc6..8514c2084 100644 --- a/shared/styles/theme.ts +++ b/shared/styles/theme.ts @@ -69,6 +69,7 @@ const buildBaseTheme = (input: Partial) => { selected: colors.accent, textHighlight: "#FDEA9B", textHighlightForeground: colors.almostBlack, + commentMarkBackground: transparentize(0.5, "#2BC2FF"), code: colors.lightBlack, codeComment: "#6a737d", codePunctuation: "#5e6687", diff --git a/shared/utils/ProsemirrorHelper.ts b/shared/utils/ProsemirrorHelper.ts index 4b1069b11..7c4f17998 100644 --- a/shared/utils/ProsemirrorHelper.ts +++ b/shared/utils/ProsemirrorHelper.ts @@ -16,6 +16,8 @@ export type CommentMark = { id: string; /* The id of the user who created the comment */ userId: string; + /* The text of the comment */ + text: string; }; export type Task = { @@ -88,8 +90,7 @@ export default class ProsemirrorHelper { } /** - * Returns true if the trimmed content of the passed document is an empty - * string. + * Returns true if the trimmed content of the passed document is an empty string. * * @returns True if the editor is empty */ @@ -98,8 +99,7 @@ export default class ProsemirrorHelper { } /** - * Iterates through the document to find all of the comments that exist as - * marks. + * Iterates through the document to find all of the comments that exist as marks. * * @param doc Prosemirror document node * @returns Array @@ -110,7 +110,10 @@ export default class ProsemirrorHelper { doc.descendants((node) => { node.marks.forEach((mark) => { if (mark.type.name === "comment") { - comments.push(mark.attrs as CommentMark); + comments.push({ + ...mark.attrs, + text: node.textContent, + } as CommentMark); } }); @@ -121,8 +124,7 @@ export default class ProsemirrorHelper { } /** - * Iterates through the document to find all of the tasks and their completion - * state. + * Iterates through the document to find all of the tasks and their completion state. * * @param doc Prosemirror document node * @returns Array