mirror of https://github.com/harness/drone.git
PR Page changes per demo feedbacks (#2056)
parent
0d1e536531
commit
2f2388aa42
|
@ -60,6 +60,7 @@ import { InViewDiffBlockRenderer } from 'components/DiffViewer/InViewDiffBlockRe
|
|||
import Config from 'Config'
|
||||
import { PullReqSuggestionsBatch } from 'components/PullReqSuggestionsBatch/PullReqSuggestionsBatch'
|
||||
import { PullReqCustomEvent } from 'pages/PullRequest/PullRequestUtils'
|
||||
import { useCollapseHarnessNav } from 'hooks/useIsSidebarExpanded'
|
||||
import { ChangesDropdown } from './ChangesDropdown'
|
||||
import { DiffViewConfiguration } from './DiffViewConfiguration'
|
||||
import ReviewSplitButton from './ReviewSplitButton/ReviewSplitButton'
|
||||
|
@ -442,6 +443,7 @@ const ChangesInternal: React.FC<ChangesProps> = ({
|
|||
}, [diffs, setPullReqChangesCount])
|
||||
|
||||
useShowRequestError(errorFileViews, 0)
|
||||
useCollapseHarnessNav()
|
||||
|
||||
return (
|
||||
<Container className={cx(css.container, className)} {...(!!loadingRawDiff || !!error ? { flex: true } : {})}>
|
||||
|
|
|
@ -84,3 +84,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.formContainer {
|
||||
form + div {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
// This is an auto-generated file
|
||||
export declare const directly: string
|
||||
export declare const extendedDescription: string
|
||||
export declare const formContainer: string
|
||||
export declare const main: string
|
||||
export declare const newBranch: string
|
||||
export declare const newBranchContainer: string
|
||||
|
|
|
@ -89,7 +89,7 @@ export function useCommitSuggestionsModal({
|
|||
{title || getString('commitChanges')}
|
||||
</Heading>
|
||||
|
||||
<Container margin={{ right: 'xxlarge' }}>
|
||||
<Container margin={{ right: 'xxlarge' }} className={css.formContainer}>
|
||||
<Formik<FormData>
|
||||
initialValues={{
|
||||
commitMessage,
|
||||
|
@ -113,7 +113,7 @@ export function useCommitSuggestionsModal({
|
|||
placeholder={extendedDescription || getString('optionalExtendedDescription')}
|
||||
/>
|
||||
|
||||
<Layout.Horizontal spacing="small" padding={{ right: 'xxlarge', top: 'xxlarge', bottom: 'large' }}>
|
||||
<Layout.Horizontal spacing="small" padding={{ top: 'xxlarge', bottom: 'large' }}>
|
||||
<Button
|
||||
type="submit"
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
|
|
|
@ -35,7 +35,6 @@ import { CodeCommentStatusSelect } from 'components/CodeCommentStatusSelect/Code
|
|||
import { dispatchCustomEvent } from 'hooks/useEventListener'
|
||||
import { UseGetPullRequestInfoResult, usePullReqActivities } from 'pages/PullRequest/useGetPullRequestInfo'
|
||||
import { CommentThreadTopDecoration } from 'components/CommentThreadTopDecoration/CommentThreadTopDecoration'
|
||||
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
|
||||
import {
|
||||
activitiesToDiffCommentItems,
|
||||
activityToCommentItem,
|
||||
|
@ -288,14 +287,18 @@ export function usePullReqComments({
|
|||
// update to the latest data
|
||||
comment._commentItems = structuredClone(comment.commentItems)
|
||||
|
||||
const suggestionBlock: SuggestionBlock = {
|
||||
source:
|
||||
comment.codeBlockContent ||
|
||||
(lineElements?.length
|
||||
? lineElements.map(td => td.nextElementSibling?.querySelector('.d2h-code-line-ctn')?.textContent).join('\n')
|
||||
: lineInfo.rowElement?.lastElementChild?.querySelector('.d2h-code-line-ctn')?.textContent || ''),
|
||||
lang: filenameToLanguage(diff.filePath.split('/').pop())
|
||||
}
|
||||
const suggestionBlock = comment.left
|
||||
? undefined
|
||||
: {
|
||||
source:
|
||||
comment.codeBlockContent ||
|
||||
(lineElements?.length
|
||||
? lineElements
|
||||
.map(td => td.nextElementSibling?.querySelector('.d2h-code-line-ctn')?.textContent)
|
||||
.join('\n')
|
||||
: lineInfo.rowElement?.lastElementChild?.querySelector('.d2h-code-line-ctn')?.textContent || ''),
|
||||
lang: filenameToLanguage(diff.filePath.split('/').pop())
|
||||
}
|
||||
|
||||
// Note: CommentBox is rendered as an independent React component.
|
||||
// Everything passed to it must be either values, or refs.
|
||||
|
|
|
@ -75,6 +75,10 @@
|
|||
pre {
|
||||
margin-bottom: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
|
||||
code {
|
||||
padding: var(--spacing-small) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.removed pre {
|
||||
|
|
|
@ -17,14 +17,14 @@
|
|||
import { useHistory } from 'react-router-dom'
|
||||
import { Container, Utils } from '@harnessio/uicore'
|
||||
import rehypeSanitize from 'rehype-sanitize'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import cx from 'classnames'
|
||||
import { getCodeString } from 'rehype-rewrite'
|
||||
import MarkdownPreview from '@uiw/react-markdown-preview'
|
||||
import rehypeVideo from 'rehype-video'
|
||||
import rehypeExternalLinks, { Element } from 'rehype-external-links'
|
||||
import { INITIAL_ZOOM_LEVEL, generateAlphaNumericHash } from 'utils/Utils'
|
||||
import rehypeExternalLinks from 'rehype-external-links'
|
||||
import { INITIAL_ZOOM_LEVEL } from 'utils/Utils'
|
||||
import ImageCarousel from 'components/ImageCarousel/ImageCarousel'
|
||||
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
|
||||
import { CodeSuggestionBlock } from './CodeSuggestionBlock'
|
||||
|
@ -32,12 +32,9 @@ import css from './MarkdownViewer.module.scss'
|
|||
|
||||
interface MarkdownViewerProps {
|
||||
source: string
|
||||
inDescriptionBox?: boolean
|
||||
className?: string
|
||||
maxHeight?: string | number
|
||||
darkMode?: boolean
|
||||
handleDescUpdate?: (payload: string) => void
|
||||
setOriginalContent?: React.Dispatch<React.SetStateAction<string>>
|
||||
suggestionBlock?: SuggestionBlock
|
||||
suggestionCheckSums?: string[]
|
||||
}
|
||||
|
@ -47,9 +44,6 @@ export function MarkdownViewer({
|
|||
className,
|
||||
maxHeight,
|
||||
darkMode,
|
||||
setOriginalContent,
|
||||
handleDescUpdate,
|
||||
inDescriptionBox = false,
|
||||
suggestionBlock,
|
||||
suggestionCheckSums
|
||||
}: MarkdownViewerProps) {
|
||||
|
@ -59,7 +53,6 @@ export function MarkdownViewer({
|
|||
const [imgEvent, setImageEvent] = useState<string[]>([])
|
||||
const refRootHref = useMemo(() => document.getElementById('repository-ref-root')?.getAttribute('href'), [])
|
||||
const ref = useRef<HTMLDivElement>()
|
||||
const [markdown, setMarkdown] = useState(source)
|
||||
|
||||
const interceptClickEventOnViewerContainer = useCallback(
|
||||
event => {
|
||||
|
@ -100,42 +93,6 @@ export function MarkdownViewer({
|
|||
},
|
||||
[history]
|
||||
)
|
||||
const [flag, setFlag] = useState(false)
|
||||
const handleCheckboxChange = useCallback(
|
||||
async (lineNumber: number) => {
|
||||
const newMarkdown = source
|
||||
.split('\n')
|
||||
.map((line, index) => {
|
||||
if (index === lineNumber) {
|
||||
return line.startsWith('- [ ]') ? line.replace('- [ ]', '- [x]') : line.replace('- [x]', '- [ ]')
|
||||
}
|
||||
return line
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
setOriginalContent?.(newMarkdown)
|
||||
setFlag(true)
|
||||
setMarkdown(newMarkdown)
|
||||
handleDescUpdate?.(newMarkdown)
|
||||
},
|
||||
[source]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLInputElement
|
||||
if (target.type === 'checkbox') {
|
||||
const lineNumber = parseInt(target.getAttribute('data-line-number') || '0', 10)
|
||||
handleCheckboxChange(lineNumber)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', handleClick)
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClick)
|
||||
}
|
||||
}, [source])
|
||||
const hash = generateAlphaNumericHash(6)
|
||||
|
||||
return (
|
||||
<Container
|
||||
|
@ -144,8 +101,7 @@ export function MarkdownViewer({
|
|||
style={{ maxHeight: maxHeight }}
|
||||
ref={ref}>
|
||||
<MarkdownPreview
|
||||
key={flag ? hash : 0}
|
||||
source={markdown}
|
||||
source={source}
|
||||
skipHtml={false}
|
||||
warpperElement={{ 'data-color-mode': darkMode ? 'dark' : 'light' }}
|
||||
rehypeRewrite={(node, _index, parent) => {
|
||||
|
@ -195,16 +151,6 @@ export function MarkdownViewer({
|
|||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
(node as unknown as HTMLDivElement).tagName === 'input' &&
|
||||
(node as Unknown as Element)?.properties?.type === 'checkbox'
|
||||
) {
|
||||
const lineNumber = parent?.position?.start?.line ? parent?.position?.start?.line - 1 : 0
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const element = node as any
|
||||
element.properties['data-line-number'] = lineNumber.toString()
|
||||
element.properties.disabled = !inDescriptionBox
|
||||
}
|
||||
}}
|
||||
rehypePlugins={[
|
||||
[rehypeSanitize],
|
||||
|
@ -219,7 +165,7 @@ export function MarkdownViewer({
|
|||
if (
|
||||
typeof code === 'string' &&
|
||||
typeof _className === 'string' &&
|
||||
/^language-suggestion/.test(_className.toLocaleLowerCase())
|
||||
'language-suggestion' === _className.toLocaleLowerCase()
|
||||
) {
|
||||
return (
|
||||
<CodeSuggestionBlock
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { CustomEventName } from 'utils/Utils'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import { useCustomEventListener } from './useEventListener'
|
||||
|
||||
/**
|
||||
|
@ -35,3 +36,47 @@ export function useIsSidebarExpanded() {
|
|||
|
||||
return isSidebarExpanded
|
||||
}
|
||||
|
||||
export function useCollapseHarnessNav() {
|
||||
const { standalone } = useAppContext()
|
||||
const isSidebarExpanded = useIsSidebarExpanded()
|
||||
const handled = useRef(!standalone && isSidebarExpanded)
|
||||
const internalFlags = useRef({
|
||||
initialized: false
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (handled.current) {
|
||||
const nav = document.getElementById('main-side-nav')
|
||||
const pullReqNavItem = nav?.querySelector('[data-code-repo-section="pull-requests"]')
|
||||
const toggleNavButton = nav?.querySelector('span[icon][class*="SideNavToggleButton"]') as HTMLElement
|
||||
|
||||
if (pullReqNavItem && toggleNavButton) {
|
||||
const isCollapsed = pullReqNavItem.clientWidth <= 64
|
||||
|
||||
if (!isCollapsed) {
|
||||
setTimeout(() => {
|
||||
toggleNavButton.click()
|
||||
internalFlags.current.initialized = true
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (handled.current) {
|
||||
toggleNavButton.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (internalFlags.current.initialized && !isSidebarExpanded) {
|
||||
internalFlags.current.initialized = false
|
||||
|
||||
if (handled.current) {
|
||||
handled.current = false
|
||||
}
|
||||
}
|
||||
}, [isSidebarExpanded])
|
||||
}
|
||||
|
|
|
@ -34,6 +34,13 @@
|
|||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.task-list-item,
|
||||
.task-list-item input {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -265,10 +265,13 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
const comment = activitiesToDiffCommentItems(activity?.code_comment?.path as string, [
|
||||
activity as TypesPullReqActivity
|
||||
])[0]
|
||||
const suggestionBlock = {
|
||||
source: comment.codeBlockContent as string,
|
||||
lang: filenameToLanguage(activity?.code_comment?.path?.split('/').pop())
|
||||
}
|
||||
|
||||
const suggestionBlock = comment.left
|
||||
? undefined
|
||||
: {
|
||||
source: comment.codeBlockContent as string,
|
||||
lang: filenameToLanguage(activity?.code_comment?.path?.split('/').pop())
|
||||
}
|
||||
|
||||
return (
|
||||
<ThreadSection
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Button, ButtonSize, ButtonVariation, Container, Layout, useToaster, Text } from '@harnessio/uicore'
|
||||
import cx from 'classnames'
|
||||
import { useParams } from 'react-router-dom'
|
||||
|
@ -80,18 +80,78 @@ export const DescriptionBox: React.FC<DescriptionBoxProps> = ({
|
|||
setFlag(true)
|
||||
}, [])
|
||||
|
||||
const handleDescUpdate = useCallback((markdown: string) => {
|
||||
const payload: OpenapiUpdatePullReqRequest = {
|
||||
title: pullReqMetadata.title,
|
||||
description: markdown || ''
|
||||
}
|
||||
setOriginalContent(markdown)
|
||||
mutate(payload)
|
||||
.then(() => {
|
||||
setContent(markdown)
|
||||
})
|
||||
.catch(exception => showError(getErrorMessage(exception), 0, getString('pr.failedToUpdate')))
|
||||
}, [])
|
||||
const handleDescUpdate = useCallback(
|
||||
(markdown: string) => {
|
||||
const payload: OpenapiUpdatePullReqRequest = {
|
||||
title: pullReqMetadata.title,
|
||||
description: markdown || ''
|
||||
}
|
||||
setOriginalContent(markdown)
|
||||
mutate(payload)
|
||||
.then(() => {
|
||||
setContent(markdown)
|
||||
})
|
||||
.catch(exception => showError(getErrorMessage(exception), 0, getString('pr.failedToUpdate')))
|
||||
},
|
||||
[getString, mutate, pullReqMetadata.title, showError]
|
||||
)
|
||||
|
||||
const viewerDOMRef = useRef<HTMLElement>()
|
||||
|
||||
useEffect(
|
||||
function toggleTodoCheck() {
|
||||
const dom = viewerDOMRef.current
|
||||
const TODO_LIST_MARKER = 'data-todo-index'
|
||||
const TODO_LIST_ITEM_CLASS = 'task-list-item'
|
||||
|
||||
if (dom && !edit) {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
const targetIsListItem = (e.target as HTMLElement).classList.contains(TODO_LIST_ITEM_CLASS)
|
||||
const target = (e.target as HTMLElement)?.closest?.(`.${TODO_LIST_ITEM_CLASS}`)
|
||||
const input = target?.firstElementChild as HTMLInputElement
|
||||
const checked = targetIsListItem ? !input?.checked : input?.checked
|
||||
let sourceIndex = -1
|
||||
|
||||
if (!input) return
|
||||
|
||||
const index = Number(target?.getAttribute(TODO_LIST_MARKER))
|
||||
|
||||
const newContent = originalContent
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
if (line.startsWith('- [ ]') || line.startsWith('- [x]')) {
|
||||
sourceIndex++
|
||||
|
||||
if (index === sourceIndex) {
|
||||
return checked ? line.replace('- [ ]', '- [x]') : line.replace('- [x]', '- [ ]')
|
||||
}
|
||||
}
|
||||
return line
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
setContent(newContent)
|
||||
setOriginalContent(newContent)
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
handleDescUpdate(newContent)
|
||||
}
|
||||
|
||||
// Enable all check inputs to allow clicking
|
||||
dom.querySelectorAll(`.${TODO_LIST_ITEM_CLASS} input`)?.forEach((input, index) => {
|
||||
input.removeAttribute('disabled')
|
||||
input.parentElement?.setAttribute(TODO_LIST_MARKER, String(index))
|
||||
})
|
||||
|
||||
dom.addEventListener('click', handleClick)
|
||||
|
||||
return () => dom.removeEventListener('click', handleClick)
|
||||
}
|
||||
},
|
||||
[edit, handleDescUpdate, originalContent]
|
||||
)
|
||||
|
||||
return (
|
||||
<Container className={cx({ [css.box]: !edit, [css.desc]: !edit })}>
|
||||
|
@ -184,31 +244,28 @@ export const DescriptionBox: React.FC<DescriptionBoxProps> = ({
|
|||
autoFocusAndPosition={true}
|
||||
/>
|
||||
)) || (
|
||||
<Container className={css.mdWrapper}>
|
||||
<MarkdownViewer
|
||||
inDescriptionBox={true}
|
||||
setOriginalContent={setOriginalContent}
|
||||
source={content}
|
||||
handleDescUpdate={handleDescUpdate}
|
||||
/>
|
||||
<Container className={css.menuWrapper}>
|
||||
<OptionsMenuButton
|
||||
isDark={true}
|
||||
icon="Options"
|
||||
iconProps={{ size: 14 }}
|
||||
style={{ padding: '5px' }}
|
||||
items={[
|
||||
{
|
||||
text: getString('edit'),
|
||||
className: css.optionMenuIcon,
|
||||
hasIcon: true,
|
||||
iconName: 'Edit',
|
||||
onClick: () => setEdit(true)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<React.Fragment key={originalContent}>
|
||||
<Container className={css.mdWrapper} ref={viewerDOMRef}>
|
||||
<MarkdownViewer source={content} />
|
||||
<Container className={css.menuWrapper}>
|
||||
<OptionsMenuButton
|
||||
isDark={true}
|
||||
icon="Options"
|
||||
iconProps={{ size: 14 }}
|
||||
style={{ padding: '5px' }}
|
||||
items={[
|
||||
{
|
||||
text: getString('edit'),
|
||||
className: css.optionMenuIcon,
|
||||
hasIcon: true,
|
||||
iconName: 'Edit',
|
||||
onClick: () => setEdit(true)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Container>
|
||||
</Container>
|
||||
</Container>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Container>
|
||||
<NavigationCheck when={dirty} />
|
||||
|
|
Loading…
Reference in New Issue