Refactor collapse/expand PR comments per new design (#2046)

This commit is contained in:
Tan Nhu 2024-05-21 19:41:14 +00:00 committed by Harness
parent a0ac3e86c5
commit 5fe08d4743
10 changed files with 305 additions and 225 deletions

View File

@ -29,12 +29,14 @@ import css from './CodeCommentStatusSelect.module.scss'
interface CodeCommentStatusSelectProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullReqMetadata'> {
comment: { commentItems: CommentItem<TypesPullReqActivity>[] }
rowElement?: HTMLTableRowElement
}
export const CodeCommentStatusSelect: React.FC<CodeCommentStatusSelectProps> = ({
repoMetadata,
pullReqMetadata,
comment: { commentItems }
comment: { commentItems },
rowElement
}) => {
const { getString } = useStrings()
const { showError } = useToaster()
@ -87,6 +89,15 @@ export const CodeCommentStatusSelect: React.FC<CodeCommentStatusSelectProps> = (
}
}, [parentComment?.id, randomClass])
useEffect(
function updateRowElement() {
if (rowElement) {
rowElement.dataset.commentThreadStatus = codeCommentStatus.value
}
},
[rowElement, codeCommentStatus]
)
return parentComment?.deleted ? null : (
<Select
className={cx(css.select, randomClass)}

View File

@ -154,3 +154,24 @@
padding-top: 2px !important;
}
}
.toggleComment {
all: unset;
border: none !important;
width: 14px;
height: 14px;
position: absolute;
top: 3px;
left: 5px;
&::after {
content: attr(data-threads-count);
position: absolute;
left: 15px;
top: -2px;
font-size: 9px;
font-weight: bold;
line-height: 1;
color: var(--black);
}
}

View File

@ -30,4 +30,5 @@ export declare const optionMenuIcon: string
export declare const outdated: string
export declare const outletTopOfFirstOfComment: string
export declare const replyPlaceHolder: string
export declare const toggleComment: string
export declare const viewer: string

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { useCallback, useEffect, useRef, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type { EditorView } from '@codemirror/view'
import { Render, Match, Truthy, Falsy, Else } from 'react-jsx-match'
import { Container, Layout, Avatar, TextInput, Text, FlexExpander, Button, useIsMounted } from '@harnessio/uicore'
@ -30,10 +30,12 @@ import type { TypesRepository } from 'services/code'
import { OptionsMenuButton } from 'components/OptionsMenuButton/OptionsMenuButton'
import { MarkdownEditorWithPreview } from 'components/MarkdownEditorWithPreview/MarkdownEditorWithPreview'
import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer'
import { ButtonRoleProps } from 'utils/Utils'
import { ButtonRoleProps, CodeCommentState } from 'utils/Utils'
import { useResizeObserver } from 'hooks/useResizeObserver'
import { useCustomEventListener } from 'hooks/useEventListener'
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
import commentActiveIconUrl from './comment.svg?url'
import commentResolvedIconUrl from './comment-resolved.svg?url'
import css from './CommentBox.module.scss'
export interface CommentItem<T = unknown> {
@ -331,227 +333,259 @@ const CommentsThread = <T = unknown,>({
},
[editIndexes]
)
// const collapseResolvedComments = useMemo(() => !!get(commentItems[0], 'payload.resolved'), [commentItems])
// const shouldCollapsedResolvedComments = useMemo(
// () =>
// collapseResolvedComments &&
// !(commentItems.length === 1 && shorten(commentItems[0].content) === commentItems[0].content),
// [commentItems, collapseResolvedComments]
// )
// const [collapsed, setCollapsed] = useState(collapseResolvedComments)
const isCommentThreadResolved = useMemo(() => !!get(commentItems[0], 'payload.resolved'), [commentItems])
const domRef = useRef<HTMLElement>()
const show = useRef(isCommentThreadResolved ? false : true)
const internalFlags = useRef({ initialized: false })
useEffect(
function renderToggleCommentsButton() {
// Get the row that contains the comment. If the comment is spanned for multiple lines, the
// row is the last row
let annotatedRow = domRef.current?.closest('tr') as HTMLTableRowElement
// Make sure annotatedRow is not one of rows which renders a comment
while (annotatedRow && !annotatedRow.dataset.sourceLineNumber) {
annotatedRow = annotatedRow?.previousElementSibling as HTMLTableRowElement
}
if (annotatedRow) {
const lineNumColDOM = annotatedRow.firstElementChild as HTMLElement
const sourceLineNumber = annotatedRow.dataset.sourceLineNumber
const button: HTMLButtonElement = lineNumColDOM?.querySelector('button') || document.createElement('button')
if (!button.onclick) {
const toggleHidden = (dom: Element) => {
if (show.current) dom.setAttribute('hidden', '')
else dom.removeAttribute('hidden')
}
const toggleComments = (e: KeyboardEvent | MouseEvent) => {
let commentRow = annotatedRow.nextElementSibling as HTMLElement
while (commentRow?.dataset?.annotatedLine) {
toggleHidden(commentRow)
// Toggle opposite place-holder as well
const diffParent = commentRow.closest('.d2h-code-wrapper')?.parentElement
const oppositeDiv = diffParent?.classList.contains('right')
? diffParent.previousElementSibling
: diffParent?.nextElementSibling
const oppositePlaceHolders = oppositeDiv?.querySelectorAll(
`[data-place-holder-for-line="${sourceLineNumber}"]`
)
oppositePlaceHolders?.forEach(dom => toggleHidden(dom))
commentRow = commentRow.nextElementSibling as HTMLElement
}
show.current = !show.current
if (!show.current) button.dataset.threadsCount = String(activeThreads + resolvedThreads)
else delete button.dataset.threadsCount
e.stopPropagation()
}
button.classList.add(css.toggleComment)
button.title = getString('pr.toggleComments')
button.addEventListener('keydown', e => {
if (e.key === 'Enter') toggleComments(e)
})
button.onclick = toggleComments
lineNumColDOM.appendChild(button)
}
let commentRow = annotatedRow.nextElementSibling as HTMLElement
let resolvedThreads = 0
let activeThreads = 0
while (commentRow?.dataset?.annotatedLine) {
if (commentRow.dataset.commentThreadStatus == CodeCommentState.RESOLVED) {
resolvedThreads++
if (!internalFlags.current.initialized) show.current = false
} else activeThreads++
commentRow = commentRow.nextElementSibling as HTMLElement
}
button.style.backgroundImage = `url("${activeThreads ? commentActiveIconUrl : commentResolvedIconUrl}")`
if (!internalFlags.current.initialized) {
internalFlags.current.initialized = true
if (!show.current && resolvedThreads) button.dataset.threadsCount = String(resolvedThreads)
else delete button.dataset.threadsCount
}
}
},
[isCommentThreadResolved, getString]
)
return (
<Render when={commentItems.length}>
<Container className={css.viewer} padding="xlarge">
{commentItems
// .filter((_commentItem, index) => {
// return collapseResolvedComments && collapsed ? index === 0 : true
// })
.map((commentItem, index) => {
const isLastItem = index === commentItems.length - 1
<Container className={css.viewer} padding="xlarge" ref={domRef}>
{commentItems.map((commentItem, index) => {
const isLastItem = index === commentItems.length - 1
return (
<ThreadSection
key={index}
title={
<Layout.Horizontal
spacing="small"
style={{ alignItems: 'center' }}
data-outdated={commentItem?.outdated}>
<Text inline icon="code-chat"></Text>
<Avatar name={commentItem?.author} size="small" hoverCard={false} />
<Text inline>
<strong>{commentItem?.author}</strong>
</Text>
<PipeSeparator height={8} />
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
<TimePopoverWithLocal
time={defaultTo(commentItem?.edited as number, 0)}
inline={false}
font={{ variation: FontVariation.SMALL }}
color={Color.GREY_400}
/>
</Text>
return (
<ThreadSection
key={index}
title={
<Layout.Horizontal
spacing="small"
style={{ alignItems: 'center' }}
data-outdated={commentItem?.outdated}>
<Text inline icon="code-chat"></Text>
<Avatar name={commentItem?.author} size="small" hoverCard={false} />
<Text inline>
<strong>{commentItem?.author}</strong>
</Text>
<PipeSeparator height={8} />
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
<TimePopoverWithLocal
time={defaultTo(commentItem?.edited as number, 0)}
inline={false}
font={{ variation: FontVariation.SMALL }}
color={Color.GREY_400}
/>
</Text>
<Render when={commentItem?.edited !== commentItem?.created || !!commentItem?.deleted}>
<>
<PipeSeparator height={8} />
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
{getString(commentItem?.deleted ? 'deleted' : 'edited')}
</Text>
</>
</Render>
<Render when={commentItem?.outdated}>
<Text inline font={{ variation: FontVariation.SMALL }} className={css.outdated}>
{getString('pr.outdated')}
<Render when={commentItem?.edited !== commentItem?.created || !!commentItem?.deleted}>
<>
<PipeSeparator height={8} />
<Text inline font={{ variation: FontVariation.SMALL }} color={Color.GREY_400}>
{getString(commentItem?.deleted ? 'deleted' : 'edited')}
</Text>
</Render>
<FlexExpander />
<Layout.Horizontal>
<Render when={index === 0 && outlets[CommentBoxOutletPosition.LEFT_OF_OPTIONS_MENU]}>
<Container padding={{ right: 'medium' }}>
{outlets[CommentBoxOutletPosition.LEFT_OF_OPTIONS_MENU]}
</Container>
</Render>
<Render when={!commentItem?.deleted}>
<OptionsMenuButton
isDark={true}
icon="Options"
iconProps={{ size: 14 }}
style={{ padding: '5px' }}
width="100px"
items={[
{
hasIcon: true,
className: cx(css.optionMenuIcon, css.edit),
iconName: 'Edit',
text: getString('edit'),
onClick: () => setEditIndexes({ ...editIndexes, ...{ [index]: true } })
},
{
hasIcon: true,
className: css.optionMenuIcon,
iconName: 'code-quote',
text: getString('quote'),
onClick: () => onQuote(commentItem?.content)
},
{
hasIcon: true,
className: css.optionMenuIcon,
iconName: 'code-copy',
text: getString('pr.copyLinkToComment'),
onClick: () => copyLinkToComment(commentItem.id, commentItems[0])
},
'-',
{
className: css.deleteIcon,
hasIcon: true,
iconName: 'main-trash',
isDanger: true,
text: getString('delete'),
onClick: async () => {
if (await handleAction(CommentAction.DELETE, '', commentItem)) {
resetStateAtIndex(index)
}
}
}
]}
/>
</Render>
</Layout.Horizontal>
</Layout.Horizontal>
}
hideGutter={isLastItem /*|| (collapseResolvedComments && collapsed)*/}>
<Container padding={{ bottom: isLastItem ? undefined : 'xsmall' }} data-comment-id={commentItem.id}>
<Render when={index === 0 && outlets[CommentBoxOutletPosition.TOP_OF_FIRST_COMMENT]}>
<Container className={css.outletTopOfFirstOfComment}>
{outlets[CommentBoxOutletPosition.TOP_OF_FIRST_COMMENT]}
</Container>
</>
</Render>
<Match expr={editIndexes[index]}>
<Truthy>
<Container className={css.editCommentContainer} data-comment-editor-shown="true">
<MarkdownEditorWithPreview
routingId={routingId}
standalone={standalone}
repoMetadata={repoMetadata}
value={commentItem?.content}
onSave={async value => {
if (await handleAction(CommentAction.UPDATE, value, commentItem)) {
commentItem.content = value
resetStateAtIndex(index)
}
}}
onCancel={() => resetStateAtIndex(index)}
setDirty={_dirty => {
setDirty(index, _dirty)
}}
i18n={{
placeHolder: getString('leaveAComment'),
tabEdit: getString('write'),
tabPreview: getString('preview'),
save: getString('save'),
cancel: getString('cancel')
}}
autoFocusAndPosition
suggestionBlock={suggestionBlock}
/>
</Container>
</Truthy>
<Else>
<Match expr={commentItem?.deleted}>
<Truthy>
<Text className={css.deleted}>{getString('commentDeleted')}</Text>
</Truthy>
<Else>
<MarkdownViewer
source={
/* collapseResolvedComments && collapsed
? shorten(commentItem?.content)
:*/ commentItem?.content
}
suggestionBlock={Object.assign(
{
commentId: commentItem.id,
appliedCheckSum: get(commentItem, 'payload.metadata.suggestions.applied_check_sum', ''),
appliedCommitSha: get(
commentItem,
'payload.metadata.suggestions.applied_commit_sha',
''
)
},
suggestionBlock
)}
suggestionCheckSums={get(commentItem, 'payload.metadata.suggestions.check_sums', [])}
/>
</Else>
</Match>
</Else>
</Match>
</Container>
</ThreadSection>
)
})}
<Render when={commentItem?.outdated}>
<Text inline font={{ variation: FontVariation.SMALL }} className={css.outdated}>
{getString('pr.outdated')}
</Text>
</Render>
{/* <Render when={shouldCollapsedResolvedComments}>
<Container
flex={{ justifyContent: 'space-around' }}
padding={{ bottom: 'xsmall' }}
data-comments-collapsed={collapsed}>
<Layout.Horizontal>
{collapsed && commentItems.length > 1 && (
<AvatarGroup
avatars={uniq(
commentItems.filter((_, index) => !!index).map((item, index) => ({ name: item.author, index }))
)}
overlap={true}
restrictLengthTo={10}
avatarGroupProps={{
hoverCard: false
}}
size="small"
/>
)}
<Button
rightIcon={collapsed ? 'main-chevron-down' : 'main-chevron-up'}
iconProps={{ size: 14, margin: { left: 'xsmall' } }}
variation={ButtonVariation.LINK}
size={ButtonSize.SMALL}
onClick={() => setCollapsed(!collapsed)}>
<StringSubstitute
str={getString(collapsed ? 'pr.moreComments' : 'showLessMatches')}
vars={{
num: commentItems.length <= 2 ? ' ' : commentItems.length - 1,
count: commentItems.length - 1
}}
/>
</Button>
</Layout.Horizontal>
</Container>
</Render> */}
<FlexExpander />
<Layout.Horizontal>
<Render when={index === 0 && outlets[CommentBoxOutletPosition.LEFT_OF_OPTIONS_MENU]}>
<Container padding={{ right: 'medium' }}>
{outlets[CommentBoxOutletPosition.LEFT_OF_OPTIONS_MENU]}
</Container>
</Render>
<Render when={!commentItem?.deleted}>
<OptionsMenuButton
isDark={true}
icon="Options"
iconProps={{ size: 14 }}
style={{ padding: '5px' }}
width="100px"
items={[
{
hasIcon: true,
className: cx(css.optionMenuIcon, css.edit),
iconName: 'Edit',
text: getString('edit'),
onClick: () => setEditIndexes({ ...editIndexes, ...{ [index]: true } })
},
{
hasIcon: true,
className: css.optionMenuIcon,
iconName: 'code-quote',
text: getString('quote'),
onClick: () => onQuote(commentItem?.content)
},
{
hasIcon: true,
className: css.optionMenuIcon,
iconName: 'code-copy',
text: getString('pr.copyLinkToComment'),
onClick: () => copyLinkToComment(commentItem.id, commentItems[0])
},
'-',
{
className: css.deleteIcon,
hasIcon: true,
iconName: 'main-trash',
isDanger: true,
text: getString('delete'),
onClick: async () => {
if (await handleAction(CommentAction.DELETE, '', commentItem)) {
resetStateAtIndex(index)
}
}
}
]}
/>
</Render>
</Layout.Horizontal>
</Layout.Horizontal>
}
hideGutter={isLastItem}>
<Container padding={{ bottom: isLastItem ? undefined : 'xsmall' }} data-comment-id={commentItem.id}>
<Render when={index === 0 && outlets[CommentBoxOutletPosition.TOP_OF_FIRST_COMMENT]}>
<Container className={css.outletTopOfFirstOfComment}>
{outlets[CommentBoxOutletPosition.TOP_OF_FIRST_COMMENT]}
</Container>
</Render>
<Match expr={editIndexes[index]}>
<Truthy>
<Container className={css.editCommentContainer} data-comment-editor-shown="true">
<MarkdownEditorWithPreview
routingId={routingId}
standalone={standalone}
repoMetadata={repoMetadata}
value={commentItem?.content}
onSave={async value => {
if (await handleAction(CommentAction.UPDATE, value, commentItem)) {
commentItem.content = value
resetStateAtIndex(index)
}
}}
onCancel={() => resetStateAtIndex(index)}
setDirty={_dirty => {
setDirty(index, _dirty)
}}
i18n={{
placeHolder: getString('leaveAComment'),
tabEdit: getString('write'),
tabPreview: getString('preview'),
save: getString('save'),
cancel: getString('cancel')
}}
autoFocusAndPosition
suggestionBlock={suggestionBlock}
/>
</Container>
</Truthy>
<Else>
<Match expr={commentItem?.deleted}>
<Truthy>
<Text className={css.deleted}>{getString('commentDeleted')}</Text>
</Truthy>
<Else>
<MarkdownViewer
source={commentItem?.content}
suggestionBlock={Object.assign(
{
commentId: commentItem.id,
appliedCheckSum: get(commentItem, 'payload.metadata.suggestions.applied_check_sum', ''),
appliedCommitSha: get(commentItem, 'payload.metadata.suggestions.applied_commit_sha', '')
},
suggestionBlock
)}
suggestionCheckSums={get(commentItem, 'payload.metadata.suggestions.check_sums', [])}
/>
</Else>
</Match>
</Else>
</Match>
</Container>
</ThreadSection>
)
})}
</Container>
</Render>
)
@ -561,11 +595,4 @@ export const CommentBox = React.memo(CommentBoxInternal)
export const customEventForCommentWithId = (id: number) => `CommentBoxCustomEvent-${id}`
// const shorten = (str = '', maxLen = 140, separator = ' ') => {
// const s = str.split('\n')[0]
// const sub = s.length <= maxLen ? s : s.substr(0, s.lastIndexOf(separator, maxLen))
// return sub.length < str.length ? sub + '...' : sub
// }
const CRLF = '\n'

View File

@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><mask id="a" height="20" maskUnits="userSpaceOnUse" width="20" x="0" y="0"><path clip-rule="evenodd" d="m10 20c5.5228 0 10-4.4772 10-10 0-5.52285-4.4772-10-10-10-5.52285 0-10 4.47715-10 10 0 5.5228 4.47715 10 10 10z" fill="#fff" fill-rule="evenodd"/></mask><mask id="b" fill="#fff"><path clip-rule="evenodd" d="m7.33325 7.91797c-.27614 0-.5.22386-.5.5s.22386.5.5.5h5.33335c.2761 0 .5-.22386.5-.5s-.2239-.5-.5-.5zm0 1.83333c-.27614 0-.5.22386-.5.5 0 .2761.22386.5.5.5h4.66665c.2762 0 .5-.2239.5-.5 0-.27614-.2238-.5-.5-.5z" fill="#fff" fill-rule="evenodd"/></mask><g opacity=".9"><path clip-rule="evenodd" d="m10 20c5.5228 0 10-4.4772 10-10 0-5.52285-4.4772-10-10-10-5.52285 0-10 4.47715-10 10 0 5.5228 4.47715 10 10 10z" fill="#d8d8d8" fill-rule="evenodd"/><g mask="url(#a)"><path d="m0 0h20v20h-20z" fill="#ff661a"/><path d="m7.33341 5.33203h5.33329c1.4728 0 2.6667 1.19391 2.6667 2.66667v2.6667c0 1.4727-1.1939 2.6666-2.6667 2.6666h-2.6666l-2.66669 2v-2c-1.47275 0-2.66666-1.1939-2.66666-2.6666v-2.6667c0-1.47276 1.19391-2.66667 2.66666-2.66667z" stroke="#f3f3fa" stroke-linecap="round" stroke-linejoin="round"/><path d="m7.83325 8.41797c0 .27614-.22386.5-.5.5v-2c-.82843 0-1.5.67157-1.5 1.5zm-.5-.5c.27614 0 .5.22386.5.5h-2c0 .82843.67157 1.5 1.5 1.5zm5.33335 0h-5.33335v2h5.33335zm-.5.5c0-.27614.2238-.5.5-.5v2c.8284 0 1.5-.67157 1.5-1.5zm.5.5c-.2762 0-.5-.22386-.5-.5h2c0-.82843-.6716-1.5-1.5-1.5zm-5.33335 0h5.33335v-2h-5.33335zm.5 1.33333c0 .2761-.22386.5-.5.5v-2c-.82843 0-1.5.67157-1.5 1.5zm-.5-.5c.27614 0 .5.22386.5.5h-2c0 .8284.67157 1.5 1.5 1.5zm4.66665 0h-4.66665v2h4.66665zm-.5.5c0-.27614.2239-.5.5-.5v2c.8284 0 1.5-.6716 1.5-1.5zm.5.5c-.2761 0-.5-.2239-.5-.5h2c0-.82843-.6716-1.5-1.5-1.5zm-4.66665 0h4.66665v-2h-4.66665z" fill="#f3f3fa" mask="url(#b)" opacity=".5"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><mask id="a" height="20" maskUnits="userSpaceOnUse" width="20" x="0" y="0"><path clip-rule="evenodd" d="m10 20c5.5228 0 10-4.4772 10-10 0-5.52285-4.4772-10-10-10-5.52285 0-10 4.47715-10 10 0 5.5228 4.47715 10 10 10z" fill="#fff" fill-rule="evenodd"/></mask><mask id="b" fill="#fff"><path clip-rule="evenodd" d="m7.33325 7.91797c-.27614 0-.5.22386-.5.5s.22386.5.5.5h5.33335c.2761 0 .5-.22386.5-.5s-.2239-.5-.5-.5zm0 1.83333c-.27614 0-.5.22386-.5.5 0 .2761.22386.5.5.5h4.66665c.2762 0 .5-.2239.5-.5 0-.27614-.2238-.5-.5-.5z" fill="#fff" fill-rule="evenodd"/></mask><g opacity=".9"><path clip-rule="evenodd" d="m10 20c5.5228 0 10-4.4772 10-10 0-5.52285-4.4772-10-10-10-5.52285 0-10 4.47715-10 10 0 5.5228 4.47715 10 10 10z" fill="#d8d8d8" fill-rule="evenodd"/><g mask="url(#a)"><path d="m0 0h20v20h-20z" fill="#0278d5"/><path d="m7.33341 5.33203h5.33329c1.4728 0 2.6667 1.19391 2.6667 2.66667v2.6667c0 1.4727-1.1939 2.6666-2.6667 2.6666h-2.6666l-2.66669 2v-2c-1.47275 0-2.66666-1.1939-2.66666-2.6666v-2.6667c0-1.47276 1.19391-2.66667 2.66666-2.66667z" stroke="#f3f3fa" stroke-linecap="round" stroke-linejoin="round"/><path d="m7.83325 8.41797c0 .27614-.22386.5-.5.5v-2c-.82843 0-1.5.67157-1.5 1.5zm-.5-.5c.27614 0 .5.22386.5.5h-2c0 .82843.67157 1.5 1.5 1.5zm5.33335 0h-5.33335v2h5.33335zm-.5.5c0-.27614.2238-.5.5-.5v2c.8284 0 1.5-.67157 1.5-1.5zm.5.5c-.2762 0-.5-.22386-.5-.5h2c0-.82843-.6716-1.5-1.5-1.5zm-5.33335 0h5.33335v-2h-5.33335zm.5 1.33333c0 .2761-.22386.5-.5.5v-2c-.82843 0-1.5.67157-1.5 1.5zm-.5-.5c.27614 0 .5.22386.5.5h-2c0 .8284.67157 1.5 1.5 1.5zm4.66665 0h-4.66665v2h4.66665zm-.5.5c0-.27614.2239-.5.5-.5v2c.8284 0 1.5-.6716 1.5-1.5zm.5.5c-.2761 0-.5-.2239-.5-.5h2c0-.82843-.6716-1.5-1.5-1.5zm-4.66665 0h4.66665v-2h-4.66665z" fill="#f3f3fa" mask="url(#b)" opacity=".5"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -77,7 +77,8 @@
&[data-annotated-line] {
background-color: var(--white);
& + [data-annotated-line] {
& + [data-annotated-line][hidden] + [data-annotated-line],
&:not([hidden]) + [data-annotated-line] {
[data-comment-thread-id] {
padding-top: 0;
}

View File

@ -20,14 +20,14 @@ import Selecto from 'selecto'
import ReactDOM from 'react-dom'
import { useLocation } from 'react-router-dom'
import { useToaster, ButtonProps, Utils } from '@harnessio/uicore'
import { findLastIndex, isEqual, max, noop, random, uniq } from 'lodash-es'
import { findLastIndex, get, isEqual, max, noop, random, uniq } from 'lodash-es'
import { useStrings } from 'framework/strings'
import type { GitInfoProps } from 'utils/GitUtils'
import type { DiffFileEntry } from 'utils/types'
import { useConfirmAct } from 'hooks/useConfirmAction'
import { useAppContext } from 'AppContext'
import type { OpenapiCommentCreatePullReqRequest, TypesPullReq, TypesPullReqActivity } from 'services/code'
import { PullRequestSection, filenameToLanguage, getErrorMessage } from 'utils/Utils'
import { CodeCommentState, PullRequestSection, filenameToLanguage, getErrorMessage } from 'utils/Utils'
import { AppWrapper } from 'App'
import { CodeCommentStatusButton } from 'components/CodeCommentStatusButton/CodeCommentStatusButton'
import { CodeCommentSecondarySaveButton } from 'components/CodeCommentSecondarySaveButton/CodeCommentSecondarySaveButton'
@ -231,9 +231,13 @@ export function usePullReqComments({
// Add `selected` class into selected lines / rows (only if selected lines count > 1)
markSelectedLines(comment, lineInfo.rowElement, true)
lineInfo.rowElement.dataset.sourceLineNumber = String(comment.lineNumberEnd)
// Annotate that the row is taken. We only support one thread per line as now
updateDataCommentIds(lineInfo.rowElement, comment.inner.id as number, true)
const isCommentThreadResolved = !!get(comment.commentItems[0], 'payload.resolved', false)
// Create placeholder for opposite row (id < 0 means this is a new thread). This placeholder
// expands itself when CommentBox's height is changed (in split view)
const oppositeRowPlaceHolder = createCommentOppositePlaceHolder(comment.lineNumberEnd, commentThreadId < 0)
@ -249,6 +253,16 @@ export function usePullReqComments({
commentRowElement.innerHTML = `<td colspan="2"></td>`
lineInfo.rowElement.after(commentRowElement)
// Set both place-holder and comment box hidden when comment thread is resolved
if (isCommentThreadResolved) {
oppositeRowPlaceHolder.setAttribute('hidden', '')
commentRowElement.setAttribute('hidden', '')
}
commentRowElement.dataset.commentThreadStatus = isCommentThreadResolved
? CodeCommentState.RESOLVED
: CodeCommentState.ACTIVE
// `element` is where CommentBox will be mounted
const element = commentRowElement.firstElementChild as HTMLTableCellElement
@ -434,6 +448,7 @@ export function usePullReqComments({
repoMetadata={repoMetadata}
pullReqMetadata={pullReqMetadata as TypesPullReq}
comment={comment}
rowElement={commentRowElement}
/>
),
[CommentBoxOutletPosition.RIGHT_OF_REPLY_PLACEHOLDER]: (

View File

@ -682,6 +682,7 @@ export interface StringsMap {
'pr.titleChangedTable': string
'pr.titleIsRequired': string
'pr.titlePlaceHolder': string
'pr.toggleComments': string
'pr.unified': string
'prChecks.error': string
'prChecks.failure': string

View File

@ -242,6 +242,7 @@ diff: Diff
draft: Draft
conversation: Conversation
pr:
toggleComments: Toggle comments
suggestedChange: Suggested change
addSuggestion: Add suggestion to batch
removeSuggestion: Remove suggestion from batch