mirror of https://github.com/harness/drone.git
Pull Request Code suggestions (#2032)
parent
ea205ff7ba
commit
44d1a82e6f
|
@ -58,6 +58,7 @@
|
|||
"diff2html": "3.4.22",
|
||||
"dompurify": "^3.0.5",
|
||||
"formik": "2.2.9",
|
||||
"hast-util-to-html": "^9.0.1",
|
||||
"highlight.js": "^11.8.0",
|
||||
"iconoir-react": "^6.11.0",
|
||||
"jotai": "^2.6.3",
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { atom } from 'jotai'
|
||||
|
||||
export interface Suggestion {
|
||||
check_sum: string
|
||||
comment_id: number
|
||||
}
|
||||
|
||||
export const pullReqSuggestionsAtom = atom<Suggestion[]>([])
|
|
@ -54,10 +54,12 @@ import { useAppContext } from 'AppContext'
|
|||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||
import { createRequestIdleCallbackTaskPool } from 'utils/Task'
|
||||
import { PlainButton } from 'components/PlainButton/PlainButton'
|
||||
import { dispatchCustomEvent, useEventListener } from 'hooks/useEventListener'
|
||||
import { dispatchCustomEvent, useCustomEventListener, useEventListener } from 'hooks/useEventListener'
|
||||
import type { UseGetPullRequestInfoResult } from 'pages/PullRequest/useGetPullRequestInfo'
|
||||
import { InViewDiffBlockRenderer } from 'components/DiffViewer/InViewDiffBlockRenderer'
|
||||
import Config from 'Config'
|
||||
import { PullReqSuggestionsBatch } from 'components/PullReqSuggestionsBatch/PullReqSuggestionsBatch'
|
||||
import { PullReqCustomEvent } from 'pages/PullRequest/PullRequestUtils'
|
||||
import { ChangesDropdown } from './ChangesDropdown'
|
||||
import { DiffViewConfiguration } from './DiffViewConfiguration'
|
||||
import ReviewSplitButton from './ReviewSplitButton/ReviewSplitButton'
|
||||
|
@ -406,6 +408,16 @@ const ChangesInternal: React.FC<ChangesProps> = ({
|
|||
[diffBlocks, isMounted]
|
||||
)
|
||||
|
||||
const refreshPullReq = useCallback(() => {
|
||||
setCachedDiff({})
|
||||
setTargetRef(_targetRef)
|
||||
setSourceRef(_sourceRef)
|
||||
setPrHasChanged(false)
|
||||
refetchCommits?.()
|
||||
}, [_sourceRef, _targetRef, refetchCommits, setCachedDiff])
|
||||
|
||||
useCustomEventListener(PullReqCustomEvent.REFETCH_DIFF, refreshPullReq)
|
||||
|
||||
/*
|
||||
* Jump to file and comment if they are specified in URL. When path and commentId
|
||||
* are specified in URL, leverage onJumpToFile() to jump to file, then comment.
|
||||
|
@ -430,6 +442,7 @@ const ChangesInternal: React.FC<ChangesProps> = ({
|
|||
}, [diffs, setPullReqChangesCount])
|
||||
|
||||
useShowRequestError(errorFileViews, 0)
|
||||
|
||||
return (
|
||||
<Container className={cx(css.container, className)} {...(!!loadingRawDiff || !!error ? { flex: true } : {})}>
|
||||
<LoadingSpinner visible={loading} withBorder={true} />
|
||||
|
@ -471,13 +484,8 @@ const ChangesInternal: React.FC<ChangesProps> = ({
|
|||
<PlainButton
|
||||
text={getString('refresh')}
|
||||
className={css.refreshBtn}
|
||||
onClick={() => {
|
||||
setCachedDiff({})
|
||||
setTargetRef(_targetRef)
|
||||
setSourceRef(_sourceRef)
|
||||
setPrHasChanged(false)
|
||||
refetchCommits?.()
|
||||
}}
|
||||
onClick={refreshPullReq}
|
||||
data-button-name="refresh-pr"
|
||||
/>
|
||||
</Render>
|
||||
|
||||
|
@ -497,13 +505,19 @@ const ChangesInternal: React.FC<ChangesProps> = ({
|
|||
|
||||
<FlexExpander />
|
||||
|
||||
<ReviewSplitButton
|
||||
shouldHide={shouldHideReviewButton}
|
||||
repoMetadata={repoMetadata}
|
||||
pullRequestMetadata={pullRequestMetadata}
|
||||
refreshPr={voidFn(noop)}
|
||||
disabled={isActiveUserPROwner}
|
||||
/>
|
||||
<Container flex={{ alignItems: 'center' }}>
|
||||
<Layout.Horizontal spacing="medium">
|
||||
<PullReqSuggestionsBatch />
|
||||
|
||||
<ReviewSplitButton
|
||||
shouldHide={shouldHideReviewButton}
|
||||
repoMetadata={repoMetadata}
|
||||
pullRequestMetadata={pullRequestMetadata}
|
||||
refreshPr={voidFn(noop)}
|
||||
disabled={isActiveUserPROwner}
|
||||
/>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
</Render>
|
||||
|
|
|
@ -38,6 +38,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:has([data-outdated='true']) [data-section-id='CodeSuggestionBlockButtons'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.box {
|
||||
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16),
|
||||
0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
|
||||
|
|
|
@ -14,26 +14,13 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import React, { useCallback, useEffect, 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,
|
||||
ButtonVariation,
|
||||
ButtonSize,
|
||||
StringSubstitute,
|
||||
AvatarGroup
|
||||
} from '@harnessio/uicore'
|
||||
import { Container, Layout, Avatar, TextInput, Text, FlexExpander, Button, useIsMounted } from '@harnessio/uicore'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
import cx from 'classnames'
|
||||
import { isEqual, noop, defaultTo, get, uniq } from 'lodash-es'
|
||||
import { isEqual, noop, defaultTo, get } from 'lodash-es'
|
||||
import { TimePopoverWithLocal } from 'utils/timePopoverLocal/TimePopoverWithLocal'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { ThreadSection } from 'components/ThreadSection/ThreadSection'
|
||||
|
@ -46,6 +33,7 @@ import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer'
|
|||
import { ButtonRoleProps } from 'utils/Utils'
|
||||
import { useResizeObserver } from 'hooks/useResizeObserver'
|
||||
import { useCustomEventListener } from 'hooks/useEventListener'
|
||||
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
|
||||
import css from './CommentBox.module.scss'
|
||||
|
||||
export interface CommentItem<T = unknown> {
|
||||
|
@ -108,6 +96,7 @@ interface CommentBoxProps<T> {
|
|||
standalone: boolean
|
||||
routingId: string
|
||||
copyLinkToComment: (commentId: number, commentItem: CommentItem<T>) => void
|
||||
suggestionBlock?: SuggestionBlock
|
||||
}
|
||||
|
||||
const CommentBoxInternal = <T = unknown,>({
|
||||
|
@ -131,7 +120,8 @@ const CommentBoxInternal = <T = unknown,>({
|
|||
repoMetadata,
|
||||
standalone,
|
||||
routingId,
|
||||
copyLinkToComment
|
||||
copyLinkToComment,
|
||||
suggestionBlock
|
||||
}: CommentBoxProps<T>) => {
|
||||
const { getString } = useStrings()
|
||||
const [comments, setComments] = useState<CommentItem<T>[]>(commentItems)
|
||||
|
@ -217,6 +207,7 @@ const CommentBoxInternal = <T = unknown,>({
|
|||
}}
|
||||
outlets={outlets}
|
||||
copyLinkToComment={copyLinkToComment}
|
||||
suggestionBlock={suggestionBlock}
|
||||
/>
|
||||
<Match expr={showReplyPlaceHolder && enableReplyPlaceHolderRef.current}>
|
||||
<Truthy>
|
||||
|
@ -298,6 +289,7 @@ const CommentBoxInternal = <T = unknown,>({
|
|||
setDirties({ ...dirties, ['new']: _dirty })
|
||||
}}
|
||||
autoFocusAndPosition={autoFocusAndPosition ? !showReplyPlaceHolder : false}
|
||||
suggestionBlock={suggestionBlock}
|
||||
/>
|
||||
</Container>
|
||||
</Falsy>
|
||||
|
@ -310,7 +302,10 @@ const CommentBoxInternal = <T = unknown,>({
|
|||
}
|
||||
|
||||
interface CommentsThreadProps<T>
|
||||
extends Pick<CommentBoxProps<T>, 'commentItems' | 'handleAction' | 'outlets' | 'copyLinkToComment'> {
|
||||
extends Pick<
|
||||
CommentBoxProps<T>,
|
||||
'commentItems' | 'handleAction' | 'outlets' | 'copyLinkToComment' | 'suggestionBlock'
|
||||
> {
|
||||
onQuote: (content: string) => void
|
||||
setDirty: (index: number, dirty: boolean) => void
|
||||
repoMetadata: TypesRepository | undefined
|
||||
|
@ -323,7 +318,8 @@ const CommentsThread = <T = unknown,>({
|
|||
setDirty,
|
||||
outlets = {},
|
||||
repoMetadata,
|
||||
copyLinkToComment
|
||||
copyLinkToComment,
|
||||
suggestionBlock
|
||||
}: CommentsThreadProps<T>) => {
|
||||
const { getString } = useStrings()
|
||||
const { standalone, routingId } = useAppContext()
|
||||
|
@ -335,22 +331,22 @@ 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 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)
|
||||
|
||||
return (
|
||||
<Render when={commentItems.length}>
|
||||
<Container className={css.viewer} padding="xlarge">
|
||||
{commentItems
|
||||
.filter((_commentItem, index) => {
|
||||
return collapseResolvedComments && collapsed ? index === 0 : true
|
||||
})
|
||||
// .filter((_commentItem, index) => {
|
||||
// return collapseResolvedComments && collapsed ? index === 0 : true
|
||||
// })
|
||||
.map((commentItem, index) => {
|
||||
const isLastItem = index === commentItems.length - 1
|
||||
|
||||
|
@ -358,7 +354,10 @@ const CommentsThread = <T = unknown,>({
|
|||
<ThreadSection
|
||||
key={index}
|
||||
title={
|
||||
<Layout.Horizontal spacing="small" style={{ alignItems: 'center' }}>
|
||||
<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>
|
||||
|
@ -444,7 +443,7 @@ const CommentsThread = <T = unknown,>({
|
|||
</Layout.Horizontal>
|
||||
</Layout.Horizontal>
|
||||
}
|
||||
hideGutter={isLastItem || (collapseResolvedComments && collapsed)}>
|
||||
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}>
|
||||
|
@ -478,6 +477,7 @@ const CommentsThread = <T = unknown,>({
|
|||
cancel: getString('cancel')
|
||||
}}
|
||||
autoFocusAndPosition
|
||||
suggestionBlock={suggestionBlock}
|
||||
/>
|
||||
</Container>
|
||||
</Truthy>
|
||||
|
@ -489,10 +489,23 @@ const CommentsThread = <T = unknown,>({
|
|||
<Else>
|
||||
<MarkdownViewer
|
||||
source={
|
||||
collapseResolvedComments && collapsed
|
||||
? shorten(commentItem?.content)
|
||||
: commentItem?.content
|
||||
/* 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>
|
||||
|
@ -503,7 +516,7 @@ const CommentsThread = <T = unknown,>({
|
|||
)
|
||||
})}
|
||||
|
||||
<Render when={shouldCollapsedResolvedComments}>
|
||||
{/* <Render when={shouldCollapsedResolvedComments}>
|
||||
<Container
|
||||
flex={{ justifyContent: 'space-around' }}
|
||||
padding={{ bottom: 'xsmall' }}
|
||||
|
@ -538,7 +551,7 @@ const CommentsThread = <T = unknown,>({
|
|||
</Button>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
</Render>
|
||||
</Render> */}
|
||||
</Container>
|
||||
</Render>
|
||||
)
|
||||
|
@ -548,11 +561,11 @@ 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))
|
||||
// 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
|
||||
}
|
||||
// return sub.length < str.length ? sub + '...' : sub
|
||||
// }
|
||||
|
||||
const CRLF = '\n'
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { Text } from '@harnessio/uicore'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
import { useStrings } from 'framework/strings'
|
||||
|
||||
export const CommentThreadTopDecoration: React.FC<{ startLine: number; endLine: number }> = ({
|
||||
startLine,
|
||||
endLine
|
||||
}) => {
|
||||
const { getString } = useStrings()
|
||||
|
||||
return startLine !== endLine ? (
|
||||
<Text
|
||||
color={Color.GREY_500}
|
||||
padding={{ bottom: 'small' }}
|
||||
font={{ variation: FontVariation.BODY }}
|
||||
data-start-line={startLine}
|
||||
data-end-line={endLine}>
|
||||
{getString('pr.commentLineNumbers', { start: startLine, end: endLine })}
|
||||
</Text>
|
||||
) : null
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { Dialog, Intent } from '@blueprintjs/core'
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
Layout,
|
||||
FlexExpander,
|
||||
Formik,
|
||||
FormikForm,
|
||||
Heading,
|
||||
useToaster,
|
||||
FormInput,
|
||||
ButtonVariation
|
||||
} from '@harnessio/uicore'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { Icon } from '@harnessio/icons'
|
||||
import { useMutate } from 'restful-react'
|
||||
import cx from 'classnames'
|
||||
import { FontVariation } from '@harnessio/design-system'
|
||||
import { useModalHook } from 'hooks/useModalHook'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { pullReqAtom } from 'pages/PullRequest/useGetPullRequestInfo'
|
||||
import { repoMetadataAtom } from 'atoms/repoMetadata'
|
||||
import css from './CommitModalButton.module.scss'
|
||||
|
||||
interface FormData {
|
||||
commitMessage?: string
|
||||
extendedDescription?: string
|
||||
}
|
||||
|
||||
interface CommitModalProps extends FormData {
|
||||
title?: string
|
||||
onCommit: (formData: FormData) => Promise<Nullable<string>>
|
||||
}
|
||||
|
||||
export function useCommitSuggestionsModal({
|
||||
title = '',
|
||||
commitMessage = '',
|
||||
extendedDescription = '',
|
||||
onCommit
|
||||
}: CommitModalProps) {
|
||||
const ModalComponent: React.FC = () => {
|
||||
const { getString } = useStrings()
|
||||
const { showError } = useToaster()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const onSubmit = useCallback(
|
||||
async (formData: FormData) => {
|
||||
setLoading(true)
|
||||
const error = await onCommit({
|
||||
commitMessage: formData.commitMessage || '',
|
||||
extendedDescription: formData.extendedDescription || ''
|
||||
})
|
||||
setLoading(false)
|
||||
|
||||
if (error) {
|
||||
showError(error)
|
||||
} else {
|
||||
hideModal()
|
||||
}
|
||||
},
|
||||
[showError]
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen
|
||||
enforceFocus={false}
|
||||
onClose={hideModal}
|
||||
title={''}
|
||||
style={{ width: 700, maxHeight: '95vh', overflow: 'auto' }}>
|
||||
<Layout.Vertical className={cx(css.main)}>
|
||||
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}>
|
||||
{title || getString('commitChanges')}
|
||||
</Heading>
|
||||
|
||||
<Container margin={{ right: 'xxlarge' }}>
|
||||
<Formik<FormData>
|
||||
initialValues={{
|
||||
commitMessage,
|
||||
extendedDescription
|
||||
}}
|
||||
formName="commitChanges"
|
||||
enableReinitialize={true}
|
||||
validateOnChange
|
||||
validateOnBlur
|
||||
onSubmit={onSubmit}>
|
||||
<FormikForm>
|
||||
<FormInput.Text
|
||||
name="commitMessage"
|
||||
label={getString('commitMessage')}
|
||||
placeholder={commitMessage}
|
||||
inputGroup={{ autoFocus: true }}
|
||||
/>
|
||||
<FormInput.TextArea
|
||||
className={css.extendedDescription}
|
||||
name="extendedDescription"
|
||||
placeholder={extendedDescription || getString('optionalExtendedDescription')}
|
||||
/>
|
||||
|
||||
<Layout.Horizontal spacing="small" padding={{ right: 'xxlarge', top: 'xxlarge', bottom: 'large' }}>
|
||||
<Button
|
||||
type="submit"
|
||||
variation={ButtonVariation.PRIMARY}
|
||||
text={getString('commit')}
|
||||
disabled={loading}
|
||||
/>
|
||||
<Button text={getString('cancel')} variation={ButtonVariation.LINK} onClick={hideModal} />
|
||||
<FlexExpander />
|
||||
|
||||
{loading && <Icon intent={Intent.PRIMARY} name="steps-spinner" size={16} />}
|
||||
</Layout.Horizontal>
|
||||
</FormikForm>
|
||||
</Formik>
|
||||
</Container>
|
||||
</Layout.Vertical>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
const [openModal, hideModal] = useModalHook(ModalComponent, [])
|
||||
|
||||
return [openModal, hideModal]
|
||||
}
|
||||
|
||||
export function useCommitPullReqSuggestions() {
|
||||
const repoMetadata = useAtomValue(repoMetadataAtom)
|
||||
const pullReq = useAtomValue(pullReqAtom)
|
||||
const { mutate } = useMutate({
|
||||
verb: 'POST',
|
||||
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq/${pullReq?.number}/comments/apply-suggestions`
|
||||
})
|
||||
|
||||
return mutate
|
||||
}
|
|
@ -67,6 +67,7 @@ export interface DiffCommentItem<T = Unknown> {
|
|||
commentItems: CommentItem<T>[]
|
||||
_commentItems?: CommentItem<T>[]
|
||||
filePath: string
|
||||
codeBlockContent?: string
|
||||
destroy: (() => void) | undefined
|
||||
}
|
||||
|
||||
|
@ -245,6 +246,40 @@ export function activitiesToDiffCommentItems(
|
|||
const lineNumberStart = (right ? activity.code_comment?.line_new : activity.code_comment?.line_old) as number
|
||||
const lineNumberEnd = lineNumberStart + span - 1
|
||||
|
||||
const diffSnapshotLines = get(activity.payload, 'lines', []) as string[]
|
||||
const leftLines: string[] = []
|
||||
const rightLines: string[] = []
|
||||
|
||||
diffSnapshotLines.forEach(line => {
|
||||
const lineContent = line.substring(1) // line has a `prefix` (space, +, or -), always remove it
|
||||
|
||||
if (line.startsWith('-')) {
|
||||
leftLines.push(lineContent)
|
||||
} else if (line.startsWith('+')) {
|
||||
rightLines.push(lineContent)
|
||||
} else {
|
||||
leftLines.push(lineContent)
|
||||
rightLines.push(lineContent)
|
||||
}
|
||||
})
|
||||
const diffHeader = get(activity.payload, 'title', '') as string
|
||||
const [oldStartLine, newStartLine] = diffHeader
|
||||
.replaceAll(/@|\+|-/g, '')
|
||||
.trim()
|
||||
.split(' ')
|
||||
.map(token => token.split(',')[0])
|
||||
.map(Number)
|
||||
const _startLine = right ? newStartLine : oldStartLine
|
||||
const codeLines = right ? rightLines : leftLines
|
||||
let lineIndex = 0
|
||||
|
||||
while (lineIndex + _startLine < lineNumberStart) {
|
||||
lineIndex++
|
||||
}
|
||||
const codeBlockContent = codeLines
|
||||
.slice(lineNumberStart - _startLine, lineNumberStart - _startLine + lineNumberEnd - lineNumberStart + 1)
|
||||
.join('\n')
|
||||
|
||||
return {
|
||||
inner: activity,
|
||||
left: !right,
|
||||
|
@ -256,7 +291,8 @@ export function activitiesToDiffCommentItems(
|
|||
commentItems: [activityToCommentItem(activity)].concat(replyComments),
|
||||
filePath: filePath,
|
||||
destroy: undefined,
|
||||
eventStream: undefined
|
||||
eventStream: undefined,
|
||||
codeBlockContent
|
||||
}
|
||||
}) || []
|
||||
)
|
||||
|
|
|
@ -19,8 +19,7 @@ import { useMutate } from 'restful-react'
|
|||
import Selecto from 'selecto'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useToaster, ButtonProps, Utils, Text } from '@harnessio/uicore'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
import { useToaster, ButtonProps, Utils } from '@harnessio/uicore'
|
||||
import { findLastIndex, isEqual, max, noop, random, uniq } from 'lodash-es'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import type { GitInfoProps } from 'utils/GitUtils'
|
||||
|
@ -28,13 +27,15 @@ 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, getErrorMessage } from 'utils/Utils'
|
||||
import { PullRequestSection, filenameToLanguage, getErrorMessage } from 'utils/Utils'
|
||||
import { AppWrapper } from 'App'
|
||||
import { CodeCommentStatusButton } from 'components/CodeCommentStatusButton/CodeCommentStatusButton'
|
||||
import { CodeCommentSecondarySaveButton } from 'components/CodeCommentSecondarySaveButton/CodeCommentSecondarySaveButton'
|
||||
import { CodeCommentStatusSelect } from 'components/CodeCommentStatusSelect/CodeCommentStatusSelect'
|
||||
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,
|
||||
|
@ -273,6 +274,15 @@ 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())
|
||||
}
|
||||
|
||||
// Note: CommentBox is rendered as an independent React component.
|
||||
// Everything passed to it must be either values, or refs.
|
||||
// If you pass callbacks or states, they won't be updated and
|
||||
|
@ -302,6 +312,7 @@ export function usePullReqComments({
|
|||
setDirty={setDirty || noop}
|
||||
currentUserName={currentUser?.display_name || currentUser?.email || ''}
|
||||
copyLinkToComment={copyLinkToComment}
|
||||
suggestionBlock={suggestionBlock}
|
||||
handleAction={async (action, value, commentItem) => {
|
||||
let result = true
|
||||
let updatedItem: CommentItem<TypesPullReqActivity> | undefined = undefined
|
||||
|
@ -415,7 +426,9 @@ export function usePullReqComments({
|
|||
return [result, updatedItem]
|
||||
}}
|
||||
outlets={{
|
||||
[CommentBoxOutletPosition.TOP]: <CommentThreadTopDecoration comment={comment} />,
|
||||
[CommentBoxOutletPosition.TOP]: (
|
||||
<CommentThreadTopDecoration startLine={comment.lineNumberStart} endLine={comment.lineNumberEnd} />
|
||||
),
|
||||
[CommentBoxOutletPosition.LEFT_OF_OPTIONS_MENU]: (
|
||||
<CodeCommentStatusSelect
|
||||
repoMetadata={repoMetadata}
|
||||
|
@ -445,6 +458,7 @@ export function usePullReqComments({
|
|||
)
|
||||
},
|
||||
[
|
||||
diff,
|
||||
comments,
|
||||
contentRef,
|
||||
viewStyle,
|
||||
|
@ -455,9 +469,6 @@ export function usePullReqComments({
|
|||
currentUser?.display_name,
|
||||
currentUser?.email,
|
||||
pullReqMetadata,
|
||||
diff.isRename,
|
||||
diff.oldName,
|
||||
diff.filePath,
|
||||
sourceRef,
|
||||
targetRef,
|
||||
save,
|
||||
|
@ -921,15 +932,4 @@ function isDiffRendered(ref: React.RefObject<HTMLDivElement | null>) {
|
|||
return !!ref.current?.querySelector('[data]' || !!ref.current?.querySelector('.d2h-wrapper'))
|
||||
}
|
||||
|
||||
const CommentThreadTopDecoration: React.FC<{ comment: DiffCommentItem<TypesPullReqActivity> }> = ({ comment }) => {
|
||||
const { getString } = useStrings()
|
||||
const { lineNumberStart: start, lineNumberEnd: end } = comment
|
||||
|
||||
return start !== end ? (
|
||||
<Text color={Color.GREY_500} padding={{ bottom: 'small' }} font={{ variation: FontVariation.BODY }}>
|
||||
{getString('pr.commentLineNumbers', { start, end })}
|
||||
</Text>
|
||||
) : null
|
||||
}
|
||||
|
||||
const selected = 'selected'
|
||||
|
|
|
@ -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 {
|
||||
Text,
|
||||
Button,
|
||||
|
@ -51,6 +51,7 @@ import { defaultUsefulOrNot } from 'components/DefaultUsefulOrNot/UsefulOrNot'
|
|||
import { AidaClient } from 'utils/types'
|
||||
import type { TypesRepository } from 'services/code'
|
||||
import { useEventListener } from 'hooks/useEventListener'
|
||||
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
|
||||
import css from './MarkdownEditorWithPreview.module.scss'
|
||||
|
||||
enum MarkdownEditorTab {
|
||||
|
@ -65,25 +66,17 @@ enum ToolbarAction {
|
|||
UPLOAD = 'UPLOAD',
|
||||
UNORDER_LIST = 'UNORDER_LIST',
|
||||
CHECK_LIST = 'CHECK_LIST',
|
||||
CODE_BLOCK = 'CODE_BLOCK'
|
||||
CODE_BLOCK = 'CODE_BLOCK',
|
||||
SUGGESTION = 'SUGGESTION'
|
||||
}
|
||||
|
||||
interface ToolbarItem {
|
||||
icon: IconName
|
||||
action: ToolbarAction
|
||||
title?: string
|
||||
size?: number
|
||||
}
|
||||
|
||||
const toolbar: ToolbarItem[] = [
|
||||
{ icon: 'header', action: ToolbarAction.HEADER },
|
||||
{ icon: 'bold', action: ToolbarAction.BOLD },
|
||||
{ icon: 'italic', action: ToolbarAction.ITALIC },
|
||||
{ icon: 'paperclip', action: ToolbarAction.UPLOAD },
|
||||
|
||||
{ icon: 'properties', action: ToolbarAction.UNORDER_LIST },
|
||||
{ icon: 'form', action: ToolbarAction.CHECK_LIST },
|
||||
{ icon: 'main-code-yaml', action: ToolbarAction.CODE_BLOCK }
|
||||
]
|
||||
|
||||
// Define a unique effect to update decorations
|
||||
const addDecorationEffect = StateEffect.define<{ decoration: Decoration; from: number; to: number }[]>()
|
||||
const removeDecorationEffect = StateEffect.define<{}>() // No payload needed for removal in this simple case// Create a state field to hold decorations
|
||||
|
@ -144,6 +137,7 @@ interface MarkdownEditorWithPreviewProps {
|
|||
repoMetadata: TypesRepository | undefined
|
||||
standalone: boolean
|
||||
routingId: string
|
||||
suggestionBlock?: SuggestionBlock
|
||||
}
|
||||
|
||||
export function MarkdownEditorWithPreview({
|
||||
|
@ -170,7 +164,8 @@ export function MarkdownEditorWithPreview({
|
|||
setFlag,
|
||||
flag,
|
||||
sourceGitRef,
|
||||
targetGitRef
|
||||
targetGitRef,
|
||||
suggestionBlock
|
||||
}: MarkdownEditorWithPreviewProps) {
|
||||
const { getString } = useStrings()
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
@ -192,6 +187,22 @@ export function MarkdownEditorWithPreview({
|
|||
})
|
||||
const isDirty = useRef(dirty)
|
||||
const [data, setData] = useState({})
|
||||
const toolbar: ToolbarItem[] = useMemo(() => {
|
||||
const initial: ToolbarItem[] = suggestionBlock
|
||||
? [{ icon: 'suggestion', action: ToolbarAction.SUGGESTION, title: getString('suggestion'), size: 20 }]
|
||||
: []
|
||||
|
||||
return [
|
||||
...initial,
|
||||
{ icon: 'header', action: ToolbarAction.HEADER, title: getString('heading') },
|
||||
{ icon: 'bold', action: ToolbarAction.BOLD, title: getString('bold') },
|
||||
{ icon: 'italic', action: ToolbarAction.ITALIC, title: getString('italic') },
|
||||
{ icon: 'paperclip', action: ToolbarAction.UPLOAD, title: getString('upload') },
|
||||
{ icon: 'properties', action: ToolbarAction.UNORDER_LIST, title: getString('unorderedList') },
|
||||
{ icon: 'form', action: ToolbarAction.CHECK_LIST, title: getString('checklist') },
|
||||
{ icon: 'main-code-yaml', action: ToolbarAction.CODE_BLOCK, title: getString('code') }
|
||||
]
|
||||
}, [getString, suggestionBlock])
|
||||
|
||||
useEffect(
|
||||
function setDirtyRef() {
|
||||
|
@ -200,7 +211,7 @@ export function MarkdownEditorWithPreview({
|
|||
[dirty]
|
||||
)
|
||||
|
||||
const myKeymap = keymap.of([
|
||||
const shortcuts = keymap.of([
|
||||
{
|
||||
key: 'Mod-z',
|
||||
run: undo,
|
||||
|
@ -214,8 +225,19 @@ export function MarkdownEditorWithPreview({
|
|||
return true
|
||||
},
|
||||
preventDefault: true
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-g',
|
||||
run: () => {
|
||||
if (suggestionBlock) {
|
||||
onToolbarAction(ToolbarAction.SUGGESTION)
|
||||
}
|
||||
return true
|
||||
},
|
||||
preventDefault: !!suggestionBlock
|
||||
}
|
||||
])
|
||||
|
||||
const handleMouseDown = useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(event: any) => {
|
||||
|
@ -289,128 +311,146 @@ export function MarkdownEditorWithPreview({
|
|||
}
|
||||
}, [data]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const onToolbarAction = useCallback((action: ToolbarAction) => {
|
||||
const view = viewRef.current
|
||||
const onToolbarAction = useCallback(
|
||||
(action: ToolbarAction) => {
|
||||
const view = viewRef.current
|
||||
|
||||
if (!view?.state) {
|
||||
return
|
||||
}
|
||||
if (!view?.state) {
|
||||
return
|
||||
}
|
||||
|
||||
// Note: Part of this code is copied from @uiwjs/react-markdown-editor
|
||||
// MIT License, Copyright (c) 2020 uiw
|
||||
// @see https://github.dev/uiwjs/react-markdown-editor/blob/2d3f45079c79616b867ef03681a8ba9799169921/src/commands/header.tsx
|
||||
switch (action) {
|
||||
case ToolbarAction.HEADER: {
|
||||
const lineInfo = view.state.doc.lineAt(view.state.selection.main.from)
|
||||
let mark = '#'
|
||||
const matchMark = lineInfo.text.match(/^#+/)
|
||||
if (matchMark && matchMark[0]) {
|
||||
const txt = matchMark[0]
|
||||
if (txt.length < 6) {
|
||||
mark = txt + '#'
|
||||
// Note: Part of this code is copied from @uiwjs/react-markdown-editor
|
||||
// MIT License, Copyright (c) 2020 uiw
|
||||
// @see https://github.dev/uiwjs/react-markdown-editor/blob/2d3f45079c79616b867ef03681a8ba9799169921/src/commands/header.tsx
|
||||
switch (action) {
|
||||
case ToolbarAction.HEADER: {
|
||||
const lineInfo = view.state.doc.lineAt(view.state.selection.main.from)
|
||||
let mark = '#'
|
||||
const matchMark = lineInfo.text.match(/^#+/)
|
||||
if (matchMark && matchMark[0]) {
|
||||
const txt = matchMark[0]
|
||||
if (txt.length < 6) {
|
||||
mark = txt + '#'
|
||||
}
|
||||
}
|
||||
if (mark.length > 6) {
|
||||
mark = '#'
|
||||
}
|
||||
const title = lineInfo.text.replace(/^#+/, '')
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: lineInfo.from,
|
||||
to: lineInfo.to,
|
||||
insert: `${mark} ${title}`
|
||||
},
|
||||
// selection: EditorSelection.range(lineInfo.from + mark.length, lineInfo.to),
|
||||
selection: { anchor: lineInfo.from + mark.length + 1 }
|
||||
})
|
||||
break
|
||||
}
|
||||
if (mark.length > 6) {
|
||||
mark = '#'
|
||||
|
||||
case ToolbarAction.UPLOAD: {
|
||||
setFile(undefined)
|
||||
setOpen(true)
|
||||
break
|
||||
}
|
||||
const title = lineInfo.text.replace(/^#+/, '')
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: lineInfo.from,
|
||||
to: lineInfo.to,
|
||||
insert: `${mark} ${title}`
|
||||
},
|
||||
// selection: EditorSelection.range(lineInfo.from + mark.length, lineInfo.to),
|
||||
selection: { anchor: lineInfo.from + mark.length + 1 }
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case ToolbarAction.UPLOAD: {
|
||||
setFile(undefined)
|
||||
setOpen(true)
|
||||
break
|
||||
}
|
||||
|
||||
case ToolbarAction.BOLD: {
|
||||
view.dispatch(
|
||||
view.state.changeByRange(range => ({
|
||||
changes: [
|
||||
{ from: range.from, insert: '**' },
|
||||
{ from: range.to, insert: '**' }
|
||||
],
|
||||
range: EditorSelection.range(range.from + 2, range.to + 2)
|
||||
}))
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
case ToolbarAction.ITALIC: {
|
||||
view.dispatch(
|
||||
view.state.changeByRange(range => ({
|
||||
changes: [
|
||||
{ from: range.from, insert: '*' },
|
||||
{ from: range.to, insert: '*' }
|
||||
],
|
||||
range: EditorSelection.range(range.from + 1, range.to + 1)
|
||||
}))
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
case ToolbarAction.UNORDER_LIST: {
|
||||
const lineInfo = view.state.doc.lineAt(view.state.selection.main.from)
|
||||
let mark = '- '
|
||||
const matchMark = lineInfo.text.match(/^-/)
|
||||
if (matchMark && matchMark[0]) {
|
||||
mark = ''
|
||||
case ToolbarAction.BOLD: {
|
||||
view.dispatch(
|
||||
view.state.changeByRange(range => ({
|
||||
changes: [
|
||||
{ from: range.from, insert: '**' },
|
||||
{ from: range.to, insert: '**' }
|
||||
],
|
||||
range: EditorSelection.range(range.from + 2, range.to + 2)
|
||||
}))
|
||||
)
|
||||
break
|
||||
}
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: lineInfo.from,
|
||||
to: lineInfo.to,
|
||||
insert: `${mark}${lineInfo.text}`
|
||||
},
|
||||
// selection: EditorSelection.range(lineInfo.from + mark.length, lineInfo.to),
|
||||
selection: { anchor: view.state.selection.main.from + mark.length }
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case ToolbarAction.CHECK_LIST: {
|
||||
const lineInfo = view.state.doc.lineAt(view.state.selection.main.from)
|
||||
let mark = '- [ ] '
|
||||
const matchMark = lineInfo.text.match(/^-\s\[\s\]\s/)
|
||||
if (matchMark && matchMark[0]) {
|
||||
mark = ''
|
||||
case ToolbarAction.ITALIC: {
|
||||
view.dispatch(
|
||||
view.state.changeByRange(range => ({
|
||||
changes: [
|
||||
{ from: range.from, insert: '*' },
|
||||
{ from: range.to, insert: '*' }
|
||||
],
|
||||
range: EditorSelection.range(range.from + 1, range.to + 1)
|
||||
}))
|
||||
)
|
||||
break
|
||||
}
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: lineInfo.from,
|
||||
to: lineInfo.to,
|
||||
insert: `${mark}${lineInfo.text}`
|
||||
},
|
||||
// selection: EditorSelection.range(lineInfo.from + mark.length, lineInfo.to),
|
||||
selection: { anchor: view.state.selection.main.from + mark.length }
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case ToolbarAction.CODE_BLOCK: {
|
||||
const main = view.state.selection.main
|
||||
const txt = view.state.sliceDoc(view.state.selection.main.from, view.state.selection.main.to)
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: main.from,
|
||||
to: main.to,
|
||||
insert: `\`\`\`tsx\n${txt}\n\`\`\``
|
||||
},
|
||||
selection: EditorSelection.range(main.from + 3, main.from + 6)
|
||||
})
|
||||
break
|
||||
case ToolbarAction.UNORDER_LIST: {
|
||||
const lineInfo = view.state.doc.lineAt(view.state.selection.main.from)
|
||||
let mark = '- '
|
||||
const matchMark = lineInfo.text.match(/^-/)
|
||||
if (matchMark && matchMark[0]) {
|
||||
mark = ''
|
||||
}
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: lineInfo.from,
|
||||
to: lineInfo.to,
|
||||
insert: `${mark}${lineInfo.text}`
|
||||
},
|
||||
// selection: EditorSelection.range(lineInfo.from + mark.length, lineInfo.to),
|
||||
selection: { anchor: view.state.selection.main.from + mark.length }
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case ToolbarAction.CHECK_LIST: {
|
||||
const lineInfo = view.state.doc.lineAt(view.state.selection.main.from)
|
||||
let mark = '- [ ] '
|
||||
const matchMark = lineInfo.text.match(/^-\s\[\s\]\s/)
|
||||
if (matchMark && matchMark[0]) {
|
||||
mark = ''
|
||||
}
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: lineInfo.from,
|
||||
to: lineInfo.to,
|
||||
insert: `${mark}${lineInfo.text}`
|
||||
},
|
||||
// selection: EditorSelection.range(lineInfo.from + mark.length, lineInfo.to),
|
||||
selection: { anchor: view.state.selection.main.from + mark.length }
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case ToolbarAction.CODE_BLOCK: {
|
||||
const main = view.state.selection.main
|
||||
const txt = view.state.sliceDoc(view.state.selection.main.from, view.state.selection.main.to)
|
||||
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: main.from,
|
||||
to: main.to,
|
||||
insert: `\`\`\`tsx\n${txt}\n\`\`\``
|
||||
},
|
||||
selection: EditorSelection.range(main.from + 3, main.from + 6)
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
case ToolbarAction.SUGGESTION: {
|
||||
const main = view.state.selection.main
|
||||
const txt = suggestionBlock?.source || ''
|
||||
|
||||
view.dispatch({
|
||||
changes: {
|
||||
from: main.from,
|
||||
to: main.to,
|
||||
insert: `\`\`\`suggestion\n${txt}\n\`\`\``
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
},
|
||||
[suggestionBlock]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setDirtyProp?.(dirty)
|
||||
|
@ -605,8 +645,9 @@ export function MarkdownEditorWithPreview({
|
|||
size={ButtonSize.SMALL}
|
||||
variation={ButtonVariation.ICON}
|
||||
icon={item.icon}
|
||||
title={item.title}
|
||||
withoutCurrentColor
|
||||
iconProps={{ color: Color.PRIMARY_10, size: 14 }}
|
||||
iconProps={{ color: Color.PRIMARY_10, size: item.size || 14 }}
|
||||
onClick={() => onToolbarAction(item.action)}
|
||||
/>
|
||||
)
|
||||
|
@ -614,7 +655,7 @@ export function MarkdownEditorWithPreview({
|
|||
</Container>
|
||||
<Container className={css.tabContent}>
|
||||
<Editor
|
||||
extensions={[myKeymap, decorationField, history()]}
|
||||
extensions={[shortcuts, decorationField, history()]}
|
||||
routingId={routingId}
|
||||
standalone={standalone}
|
||||
repoMetadata={repoMetadata}
|
||||
|
@ -633,7 +674,11 @@ export function MarkdownEditorWithPreview({
|
|||
}}
|
||||
/>
|
||||
{selectedTab === MarkdownEditorTab.PREVIEW && (
|
||||
<MarkdownViewer source={viewRef.current?.state.doc.toString() || ''} maxHeight={800} />
|
||||
<MarkdownViewer
|
||||
source={viewRef.current?.state.doc.toString() || ''}
|
||||
maxHeight={800}
|
||||
suggestionBlock={suggestionBlock}
|
||||
/>
|
||||
)}
|
||||
{!standalone && showFeedback && (
|
||||
<Container
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, ButtonVariation, Container, Layout, Text, Utils, stringSubstitute } from '@harnessio/uicore'
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useAtom } from 'jotai'
|
||||
import { refractor } from 'refractor'
|
||||
import { Else, Match, Truthy } from 'react-jsx-match'
|
||||
import { toHtml } from 'hast-util-to-html'
|
||||
import type { Nodes } from 'hast-util-to-html/lib'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
|
||||
import { Suggestion, pullReqSuggestionsAtom } from 'atoms/pullReqSuggestions'
|
||||
import {
|
||||
useCommitPullReqSuggestions,
|
||||
useCommitSuggestionsModal
|
||||
} from 'components/CommitModalButton/useCommitSuggestionModal'
|
||||
import { PullRequestSection, getErrorMessage, waitUntil } from 'utils/Utils'
|
||||
import { PullReqCustomEvent, getActivePullReqPageSection } from 'pages/PullRequest/PullRequestUtils'
|
||||
import { dispatchCustomEvent } from 'hooks/useEventListener'
|
||||
import css from './MarkdownViewer.module.scss'
|
||||
|
||||
interface CodeSuggestionBlockProps {
|
||||
code: string
|
||||
suggestionBlock?: SuggestionBlock
|
||||
suggestionCheckSums?: string[]
|
||||
}
|
||||
//
|
||||
// NOTE: Adding this component to MarkdownViewer is not ideal as
|
||||
// it makes MarkdownViewer less independent. It'd be better to adopt
|
||||
// concept such as Outlet to make the Code Suggestion business on its own
|
||||
//
|
||||
export const CodeSuggestionBlock: React.FC<CodeSuggestionBlockProps> = ({
|
||||
code,
|
||||
suggestionBlock,
|
||||
suggestionCheckSums
|
||||
}) => {
|
||||
const { getString } = useStrings()
|
||||
const codeBlockContent = suggestionBlock?.source || ''
|
||||
const lang = suggestionBlock?.lang || 'plaintext'
|
||||
const language = `language-${lang}`
|
||||
const html1 = toHtml(refractor.highlight(codeBlockContent, lang) as unknown as Nodes)
|
||||
const html2 = toHtml(refractor.highlight(code, lang) as unknown as Nodes)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const suggestionRef = useRef<Suggestion>()
|
||||
const [checksum, setChecksum] = useState('')
|
||||
|
||||
// TODO: Use `fast-diff` to decorate `removed, `added` blocks
|
||||
// Similar to Github. Otherwise, it looks plain
|
||||
// https://codesandbox.io/p/sandbox/intelligent-noether-3qd6mj?file=%2Fsrc%2FApp.js%3A1%2C19-1%2C28
|
||||
// Flow:
|
||||
// For removed block: Scan fast diff result, if a removed block is matched, mark bg red
|
||||
// For added block: Scan fast diff result, if an added block is matched, mark bg green
|
||||
|
||||
// Notes: Since the suggestion checksums are on the comment level (JSON), and the suggestions themselves are
|
||||
// embedded in the comment content (Text), which make them be nothing related in terms of structure. We need
|
||||
// a way to link them together:
|
||||
// 1- Render suggestion block, each being marked with the comment
|
||||
// 2- When rendering is complete, we query all suggestions block and match each block to its check sum
|
||||
// by index.
|
||||
useEffect(() => {
|
||||
const commentId = suggestionBlock?.commentId
|
||||
|
||||
if (commentId && suggestionCheckSums?.length && ref.current) {
|
||||
const parent = ref.current.closest(`[data-comment-id="${commentId}"]`)
|
||||
const suggestionBlockDOMs = parent?.querySelectorAll(`[data-suggestion-comment-id="${commentId}"]`)
|
||||
let index = 0
|
||||
|
||||
if (suggestionBlockDOMs?.length) {
|
||||
while (suggestionBlockDOMs[index]) {
|
||||
if (suggestionBlockDOMs[index] === ref.current) {
|
||||
setChecksum(suggestionCheckSums[index])
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [code, suggestionBlock?.commentId, suggestionCheckSums])
|
||||
|
||||
const text = useMemo(() => stringSubstitute(getString('pr.commitSuggestions'), { count: 1 }), [getString])
|
||||
const [suggestions, setSuggestions] = useAtom(pullReqSuggestionsAtom)
|
||||
const commitPullReqSuggestions = useCommitPullReqSuggestions()
|
||||
const [openCommitSuggestionsModal] = useCommitSuggestionsModal({
|
||||
title: text as string,
|
||||
commitMessage: stringSubstitute(getString('pr.applySuggestions'), { count: 1 }) as string,
|
||||
onCommit: async formData => {
|
||||
return new Promise(resolve => {
|
||||
commitPullReqSuggestions({
|
||||
bypass_rules: true,
|
||||
dry_run_rules: false,
|
||||
title: formData.commitMessage,
|
||||
message: formData.extendedDescription,
|
||||
suggestions: [suggestionRef.current]
|
||||
})
|
||||
.then(() => {
|
||||
resolve(null)
|
||||
|
||||
switch (getActivePullReqPageSection()) {
|
||||
case PullRequestSection.FILES_CHANGED:
|
||||
waitUntil({
|
||||
test: () => document.querySelector('[data-button-name="refresh-pr"]') as HTMLElement,
|
||||
onMatched: dom => {
|
||||
dom?.click?.()
|
||||
},
|
||||
onExpired: () => {
|
||||
dispatchCustomEvent(PullReqCustomEvent.REFETCH_DIFF, null)
|
||||
}
|
||||
})
|
||||
break
|
||||
|
||||
case PullRequestSection.CONVERSATION:
|
||||
// Activities are refetched by SSE event, nothing to do here
|
||||
break
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
resolve(getErrorMessage(e))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
suggestionRef.current = {
|
||||
check_sum: checksum,
|
||||
comment_id: suggestionBlock?.commentId as number
|
||||
}
|
||||
}, [checksum, suggestionBlock?.commentId])
|
||||
|
||||
const states = useMemo(
|
||||
() => ({
|
||||
addedToBatch: suggestions?.find(item => item.check_sum === checksum),
|
||||
otherAddedToBatch: suggestions?.find(
|
||||
item => item.check_sum !== checksum && item.comment_id === suggestionBlock?.commentId
|
||||
)
|
||||
}),
|
||||
[suggestions, checksum, suggestionBlock]
|
||||
)
|
||||
|
||||
const actions = useMemo(
|
||||
() => ({
|
||||
addToBatch: () => {
|
||||
setSuggestions([...suggestions, { check_sum: checksum, comment_id: suggestionBlock?.commentId as number }])
|
||||
},
|
||||
removeFromBatch: () => {
|
||||
setSuggestions(suggestions.filter(suggestion => suggestion.check_sum !== checksum))
|
||||
},
|
||||
commit: openCommitSuggestionsModal
|
||||
}),
|
||||
[checksum, suggestionBlock, suggestions, setSuggestions, openCommitSuggestionsModal]
|
||||
)
|
||||
|
||||
return (
|
||||
<Container
|
||||
ref={ref}
|
||||
className={css.suggestion}
|
||||
onClick={Utils.stopEvent}
|
||||
data-suggestion-comment-id={suggestionBlock?.commentId}>
|
||||
<Layout.Vertical>
|
||||
<Text className={css.text}>
|
||||
{getString(
|
||||
suggestionBlock?.appliedCheckSum && suggestionBlock?.appliedCheckSum === checksum
|
||||
? 'pr.suggestionApplied'
|
||||
: 'pr.suggestedChange'
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Container>
|
||||
<Container className={css.removed}>
|
||||
<pre className={language}>
|
||||
<code className={`${language} code-highlight`} dangerouslySetInnerHTML={{ __html: html1 }}></code>
|
||||
</pre>
|
||||
</Container>
|
||||
<Container className={css.added}>
|
||||
<pre className={language}>
|
||||
<code className={`${language} code-highlight`} dangerouslySetInnerHTML={{ __html: html2 }}></code>
|
||||
</pre>
|
||||
</Container>
|
||||
</Container>
|
||||
{!!suggestionCheckSums?.length && (
|
||||
<Container data-section-id="CodeSuggestionBlockButtons">
|
||||
<Layout.Horizontal spacing="small" padding="medium">
|
||||
<Match expr={states.addedToBatch}>
|
||||
<Truthy>
|
||||
<Button
|
||||
intent="danger"
|
||||
variation={ButtonVariation.SECONDARY}
|
||||
text={getString('pr.removeSuggestion')}
|
||||
onClick={actions.removeFromBatch}
|
||||
/>
|
||||
</Truthy>
|
||||
<Else>
|
||||
<Button
|
||||
variation={ButtonVariation.TERTIARY}
|
||||
text={getString('pr.addSuggestion')}
|
||||
onClick={actions.addToBatch}
|
||||
disabled={!!states.otherAddedToBatch}
|
||||
/>
|
||||
<Button
|
||||
variation={ButtonVariation.TERTIARY}
|
||||
text={getString('pr.commitSuggestion')}
|
||||
onClick={actions.commit}
|
||||
disabled={!!states.otherAddedToBatch}
|
||||
/>
|
||||
</Else>
|
||||
</Match>
|
||||
</Layout.Horizontal>
|
||||
</Container>
|
||||
)}
|
||||
</Layout.Vertical>
|
||||
</Container>
|
||||
)
|
||||
}
|
|
@ -66,3 +66,32 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion {
|
||||
background-color: var(--white) !important;
|
||||
border: 1px solid var(--grey-200);
|
||||
border-radius: 4px;
|
||||
|
||||
pre {
|
||||
margin-bottom: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.removed pre {
|
||||
background-color: var(--red-100) !important;
|
||||
}
|
||||
|
||||
.added pre {
|
||||
background-color: var(--green-100) !important;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: var(--grey-500);
|
||||
font-size: 11px !important;
|
||||
padding: var(--spacing-small) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion + [data-code] {
|
||||
display: none !important;
|
||||
}
|
||||
|
|
|
@ -16,5 +16,9 @@
|
|||
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const added: string
|
||||
export declare const main: string
|
||||
export declare const removed: string
|
||||
export declare const suggestion: string
|
||||
export declare const text: string
|
||||
export declare const withMaxHeight: string
|
||||
|
|
|
@ -15,15 +15,18 @@
|
|||
*/
|
||||
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { Container, Utils } from '@harnessio/uicore'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Container } from '@harnessio/uicore'
|
||||
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 ImageCarousel from 'components/ImageCarousel/ImageCarousel'
|
||||
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
|
||||
import { CodeSuggestionBlock } from './CodeSuggestionBlock'
|
||||
import css from './MarkdownViewer.module.scss'
|
||||
|
||||
interface MarkdownViewerProps {
|
||||
|
@ -34,6 +37,8 @@ interface MarkdownViewerProps {
|
|||
darkMode?: boolean
|
||||
handleDescUpdate?: (payload: string) => void
|
||||
setOriginalContent?: React.Dispatch<React.SetStateAction<string>>
|
||||
suggestionBlock?: SuggestionBlock
|
||||
suggestionCheckSums?: string[]
|
||||
}
|
||||
|
||||
export function MarkdownViewer({
|
||||
|
@ -41,10 +46,11 @@ export function MarkdownViewer({
|
|||
className,
|
||||
maxHeight,
|
||||
darkMode,
|
||||
|
||||
setOriginalContent,
|
||||
handleDescUpdate,
|
||||
inDescriptionBox = false
|
||||
inDescriptionBox = false,
|
||||
suggestionBlock,
|
||||
suggestionCheckSums
|
||||
}: MarkdownViewerProps) {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
||||
const history = useHistory()
|
||||
|
@ -203,6 +209,32 @@ export function MarkdownViewer({
|
|||
[rehypeVideo, { test: /\/(.*)(.mp4|.mov|.webm|.mkv|.flv)$/, details: null }],
|
||||
[rehypeExternalLinks, { rel: ['nofollow noreferrer noopener'], target: '_blank' }]
|
||||
]}
|
||||
components={{
|
||||
// Rewriting the code component to support code suggestions
|
||||
code: ({ children = [], className: _className, ...props }) => {
|
||||
const code = props.node && props.node.children ? getCodeString(props.node.children) : children
|
||||
|
||||
if (
|
||||
typeof code === 'string' &&
|
||||
typeof _className === 'string' &&
|
||||
/^language-suggestion/.test(_className.toLocaleLowerCase())
|
||||
) {
|
||||
return (
|
||||
<CodeSuggestionBlock
|
||||
code={code}
|
||||
suggestionBlock={suggestionBlock}
|
||||
suggestionCheckSums={suggestionCheckSums}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<code onClick={Utils.stopEvent} className={String(_className)}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ImageCarousel
|
||||
isOpen={isOpen}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
.count {
|
||||
display: inline-block;
|
||||
margin-left: var(--spacing-small);
|
||||
display: inline-block;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--white) !important;
|
||||
background-color: var(--primary-7) !important;
|
||||
padding: 3px 6px !important;
|
||||
}
|
19
web/src/components/PullReqSuggestionsBatch/PullReqSuggestionsBatch.module.scss.d.ts
vendored
Normal file
19
web/src/components/PullReqSuggestionsBatch/PullReqSuggestionsBatch.module.scss.d.ts
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
// This is an auto-generated file
|
||||
export declare const count: string
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright 2023 Harness, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useMemo, useRef } from 'react'
|
||||
import { Button, ButtonVariation, Container, stringSubstitute } from '@harnessio/uicore'
|
||||
import { Render } from 'react-jsx-match'
|
||||
import { useAtom } from 'jotai'
|
||||
import { Suggestion, pullReqSuggestionsAtom } from 'atoms/pullReqSuggestions'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import {
|
||||
useCommitPullReqSuggestions,
|
||||
useCommitSuggestionsModal
|
||||
} from 'components/CommitModalButton/useCommitSuggestionModal'
|
||||
import { PullRequestSection, getErrorMessage, waitUntil } from 'utils/Utils'
|
||||
import { dispatchCustomEvent } from 'hooks/useEventListener'
|
||||
import { PullReqCustomEvent, getActivePullReqPageSection } from 'pages/PullRequest/PullRequestUtils'
|
||||
import css from './PullReqSuggestionsBatch.module.scss'
|
||||
|
||||
export const PullReqSuggestionsBatch: React.FC = () => {
|
||||
const [suggestions, setSuggestions] = useAtom(pullReqSuggestionsAtom)
|
||||
const suggestionsRef = useRef<Suggestion[]>(suggestions)
|
||||
const { getString } = useStrings()
|
||||
const text = useMemo(
|
||||
() => stringSubstitute(getString('pr.commitSuggestions'), { count: suggestions?.length }),
|
||||
[suggestions, getString]
|
||||
)
|
||||
const commitPullReqSuggestions = useCommitPullReqSuggestions()
|
||||
const [openCommitSuggestionsModal] = useCommitSuggestionsModal({
|
||||
title: text as string,
|
||||
commitMessage: stringSubstitute(getString('pr.applySuggestions'), { count: suggestions?.length }) as string,
|
||||
onCommit: async formData => {
|
||||
return new Promise(resolve => {
|
||||
commitPullReqSuggestions({
|
||||
bypass_rules: true,
|
||||
dry_run_rules: false,
|
||||
title: formData.commitMessage,
|
||||
message: formData.extendedDescription,
|
||||
suggestions: suggestionsRef.current
|
||||
})
|
||||
.then(() => {
|
||||
resolve(null)
|
||||
|
||||
switch (getActivePullReqPageSection()) {
|
||||
case PullRequestSection.FILES_CHANGED:
|
||||
waitUntil({
|
||||
test: () => document.querySelector('[data-button-name="refresh-pr"]') as HTMLElement,
|
||||
onMatched: dom => {
|
||||
dom?.click?.()
|
||||
},
|
||||
onExpired: () => {
|
||||
dispatchCustomEvent(PullReqCustomEvent.REFETCH_DIFF, null)
|
||||
}
|
||||
})
|
||||
break
|
||||
|
||||
case PullRequestSection.CONVERSATION:
|
||||
// Activities are refetched by SSE event, nothing to do here
|
||||
break
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
resolve(getErrorMessage(e))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
suggestionsRef.current = suggestions
|
||||
}, [suggestions])
|
||||
|
||||
useEffect(() => {
|
||||
setSuggestions([])
|
||||
}, [setSuggestions])
|
||||
|
||||
return (
|
||||
<Render when={suggestions?.length}>
|
||||
<Container flex={{ alignItems: 'center' }}>
|
||||
<Button variation={ButtonVariation.TERTIARY} text={text} onClick={openCommitSuggestionsModal}>
|
||||
<span className={css.count}>{suggestions?.length}</span>
|
||||
</Button>
|
||||
</Container>
|
||||
</Render>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* SuggestionBlock represents a suggestion block.
|
||||
*/
|
||||
export interface SuggestionBlock {
|
||||
/** Orginal source - lines that have suggestion comment */
|
||||
source: string
|
||||
|
||||
/** Language of the diff/file */
|
||||
lang?: string
|
||||
|
||||
/** Comment id */
|
||||
commentId?: number
|
||||
|
||||
/** Applied check sum */
|
||||
appliedCheckSum?: string
|
||||
|
||||
/** Applied commit SHA */
|
||||
appliedCommitSha?: string
|
||||
}
|
|
@ -39,6 +39,7 @@ export interface StringsMap {
|
|||
blame: string
|
||||
blameCommitLine: string
|
||||
blameEmpty: string
|
||||
bold: string
|
||||
botAlerts: string
|
||||
bottom: string
|
||||
branch: string
|
||||
|
@ -140,6 +141,7 @@ export interface StringsMap {
|
|||
characterLimit: string
|
||||
checkRuns: string
|
||||
checkSuites: string
|
||||
checklist: string
|
||||
checks: string
|
||||
clear: string
|
||||
clickHereToDownload: string
|
||||
|
@ -148,6 +150,7 @@ export interface StringsMap {
|
|||
cloneText: string
|
||||
close: string
|
||||
closed: string
|
||||
code: string
|
||||
'codeOwner.approvalCompleted': string
|
||||
'codeOwner.changesRequested': string
|
||||
'codeOwner.title': string
|
||||
|
@ -356,6 +359,7 @@ export interface StringsMap {
|
|||
getMyCloneTitle: string
|
||||
gitIgnore: string
|
||||
gitness: string
|
||||
heading: string
|
||||
help: string
|
||||
hideCommitHistory: string
|
||||
history: string
|
||||
|
@ -422,6 +426,7 @@ export interface StringsMap {
|
|||
inactiveBranches: string
|
||||
invalidResponse: string
|
||||
isRequired: string
|
||||
italic: string
|
||||
key: string
|
||||
'keywordSearch.sampleQueries.searchForClass': string
|
||||
'keywordSearch.sampleQueries.searchForFilesWithCMD': string
|
||||
|
@ -598,6 +603,8 @@ export interface StringsMap {
|
|||
poweredByAI: string
|
||||
'pr.ableToMerge': string
|
||||
'pr.addDescription': string
|
||||
'pr.addSuggestion': string
|
||||
'pr.applySuggestions': string
|
||||
'pr.authorCommentedPR': string
|
||||
'pr.branchHasNoConflicts': string
|
||||
'pr.cantBeMerged': string
|
||||
|
@ -607,6 +614,8 @@ export interface StringsMap {
|
|||
'pr.checksFailure': string
|
||||
'pr.collapseFullFile': string
|
||||
'pr.commentLineNumbers': string
|
||||
'pr.commitSuggestion': string
|
||||
'pr.commitSuggestions': string
|
||||
'pr.copyLinkToComment': string
|
||||
'pr.createDraftPR': string
|
||||
'pr.descHasTooLongLine': string
|
||||
|
@ -657,6 +666,7 @@ export interface StringsMap {
|
|||
'pr.prStateChanged': string
|
||||
'pr.prStateChangedDraft': string
|
||||
'pr.readyForReview': string
|
||||
'pr.removeSuggestion': string
|
||||
'pr.requestSubmitted': string
|
||||
'pr.reviewChanges': string
|
||||
'pr.reviewSubmitted': string
|
||||
|
@ -666,6 +676,8 @@ export interface StringsMap {
|
|||
'pr.state': string
|
||||
'pr.status': string
|
||||
'pr.statusLine': string
|
||||
'pr.suggestedChange': string
|
||||
'pr.suggestionApplied': string
|
||||
'pr.titleChanged': string
|
||||
'pr.titleChangedTable': string
|
||||
'pr.titleIsRequired': string
|
||||
|
@ -837,6 +849,7 @@ export interface StringsMap {
|
|||
'stepCategory.select': string
|
||||
submitReview: string
|
||||
success: string
|
||||
suggestion: string
|
||||
summary: string
|
||||
switchBranch: string
|
||||
switchBranchesTags: string
|
||||
|
@ -867,6 +880,7 @@ export interface StringsMap {
|
|||
'triggers.newTrigger': string
|
||||
'triggers.updateSuccess': string
|
||||
turnOnSemanticSearch: string
|
||||
unorderedList: string
|
||||
unrsolvedComment: string
|
||||
'unsavedChanges.leave': string
|
||||
'unsavedChanges.message': string
|
||||
|
@ -877,6 +891,7 @@ export interface StringsMap {
|
|||
updateUser: string
|
||||
updateWebhook: string
|
||||
updated: string
|
||||
upload: string
|
||||
uploadAFileError: string
|
||||
user: string
|
||||
userCreated: string
|
||||
|
|
|
@ -242,6 +242,13 @@ diff: Diff
|
|||
draft: Draft
|
||||
conversation: Conversation
|
||||
pr:
|
||||
suggestedChange: Suggested change
|
||||
addSuggestion: Add suggestion to batch
|
||||
removeSuggestion: Remove suggestion from batch
|
||||
commitSuggestions: 'Commit {count|1:suggestion,suggestions}'
|
||||
commitSuggestion: Commit suggestion
|
||||
applySuggestions: 'Apply {count|1:suggestion,suggestions} from code review'
|
||||
suggestionApplied: Suggestion applied
|
||||
commentLineNumbers: Comment on line {{start}} to {{end}}
|
||||
moreComments: '{num} {count|0:Show more,1:1 more comment,more comments}'
|
||||
copyLinkToComment: Copy link to comment
|
||||
|
@ -287,8 +294,8 @@ pr:
|
|||
requestSubmitted: Request for changes submitted.
|
||||
prReviewSubmit: '{user} {state|approved:approved, rejected:rejected,changereq:requested changes to, reviewed} this pull request. {time}'
|
||||
prMergedBannerInfo: '{user} merged branch {source} into {target} {time}.'
|
||||
prMergedInfo: '{user}{bypassed|true: bypassed rules and , }merged changes from {source} into {target} as {mergeSha} {time}'
|
||||
prRebasedInfo: '{user}{bypassed|true: bypassed rules and , }rebased changes from branch {source} onto {target}, now at {mergeSha} {time}'
|
||||
prMergedInfo: '{user} merged changes from {source} into {target} as {mergeSha} {time}'
|
||||
prRebasedInfo: '{user} rebased changes from branch {source} onto {target}, now at {mergeSha} {time}'
|
||||
prBranchPushInfo: '{user} pushed a new commit {commit}'
|
||||
prBranchDeleteInfo: '{user} deleted the source branch with latest commit {commit}'
|
||||
prStateChanged: '{user} changed pull request state from {old} to {new}.'
|
||||
|
@ -1028,3 +1035,11 @@ securitySettings:
|
|||
detectDesc: passive vulnerability will report errors but not block
|
||||
block: Block
|
||||
blockDesc: active vulnerability blocks commit if any vulnerability is found
|
||||
suggestion: 'Add a suggestion, <Ctrl-g>'
|
||||
heading: Heading
|
||||
bold: Bold
|
||||
italic: Italic
|
||||
upload: Upload
|
||||
unorderedList: Unordered list
|
||||
checklist: Check list
|
||||
code: Code
|
||||
|
|
|
@ -22,7 +22,7 @@ import { useHistory } from 'react-router-dom'
|
|||
import { Container, Layout, Text, FlexExpander, Button, ButtonVariation, ButtonSize } from '@harnessio/uicore'
|
||||
import { Color, FontVariation } from '@harnessio/design-system'
|
||||
import { LogViewer } from 'components/LogViewer/LogViewer'
|
||||
import { PullRequestCheckType } from 'utils/Utils'
|
||||
import { PullRequestCheckType, PullRequestSection } from 'utils/Utils'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { Split } from 'components/Split/Split'
|
||||
|
@ -176,7 +176,7 @@ export const Checks: React.FC<ChecksProps> = ({ repoMetadata, pullReqMetadata, p
|
|||
}
|
||||
|
||||
return (
|
||||
<Container className={css.main}>
|
||||
<Container className={css.main} data-page-section={PullRequestSection.CHECKS}>
|
||||
<Match expr={prChecksDecisionResult?.overallStatus}>
|
||||
<Truthy>
|
||||
<Split split="vertical" size={400} minSize={300} maxSize={700} primary="first">
|
||||
|
|
|
@ -53,8 +53,8 @@ export const CodeCommentHeader: React.FC<CodeCommentHeaderProps> = ({
|
|||
`diff --git a/src b/dest`,
|
||||
`new file mode 100644`,
|
||||
'index 0000000..0000000',
|
||||
'--- a/src',
|
||||
'+++ b/dest',
|
||||
`--- a/src/${get(commentItems[0], 'payload.code_comment.path')}`,
|
||||
`+++ b/dest/${get(commentItems[0], 'payload.code_comment.path')}`,
|
||||
get(commentItems[0], 'payload.payload.title', ''),
|
||||
...get(commentItems[0], 'payload.payload.lines', [])
|
||||
].join('\n')
|
||||
|
|
|
@ -28,15 +28,15 @@ import {
|
|||
} from '@harnessio/uicore'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useGet, useMutate } from 'restful-react'
|
||||
import { orderBy } from 'lodash-es'
|
||||
import { get, orderBy } from 'lodash-es'
|
||||
import type { GitInfoProps } from 'utils/GitUtils'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { useAppContext } from 'AppContext'
|
||||
import type { TypesPullReqActivity, TypesPullReq, TypesPullReqStats, TypesCodeOwnerEvaluation } from 'services/code'
|
||||
import { CommentAction, CommentBox, CommentBoxOutletPosition, CommentItem } from 'components/CommentBox/CommentBox'
|
||||
import { useConfirmAct } from 'hooks/useConfirmAction'
|
||||
import { getErrorMessage, orderSortDate, ButtonRoleProps, PullRequestSection } from 'utils/Utils'
|
||||
import { activityToCommentItem } from 'components/DiffViewer/DiffViewerUtils'
|
||||
import { getErrorMessage, orderSortDate, ButtonRoleProps, PullRequestSection, filenameToLanguage } from 'utils/Utils'
|
||||
import { activitiesToDiffCommentItems, activityToCommentItem } from 'components/DiffViewer/DiffViewerUtils'
|
||||
import { NavigationCheck } from 'components/NavigationCheck/NavigationCheck'
|
||||
import { ThreadSection } from 'components/ThreadSection/ThreadSection'
|
||||
import { CodeCommentStatusSelect } from 'components/CodeCommentStatusSelect/CodeCommentStatusSelect'
|
||||
|
@ -44,6 +44,7 @@ import { CodeCommentStatusButton } from 'components/CodeCommentStatusButton/Code
|
|||
import { CodeCommentSecondarySaveButton } from 'components/CodeCommentSecondarySaveButton/CodeCommentSecondarySaveButton'
|
||||
import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision'
|
||||
import { UserPreference, useUserPreference } from 'hooks/useUserPreference'
|
||||
import { CommentThreadTopDecoration } from 'components/CommentThreadTopDecoration/CommentThreadTopDecoration'
|
||||
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
||||
import { DescriptionBox } from './DescriptionBox'
|
||||
import { PullRequestActionsBox } from './PullRequestActionsBox/PullRequestActionsBox'
|
||||
|
@ -254,9 +255,24 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
}></ThreadSection>
|
||||
)
|
||||
}
|
||||
|
||||
const activity = commentItems[0].payload
|
||||
const right = get(activity?.payload, 'line_start_new', false)
|
||||
const span = right ? activity?.code_comment?.span_new || 0 : activity?.code_comment?.span_old || 0
|
||||
const startLine = (right ? activity?.code_comment?.line_new : activity?.code_comment?.line_old) as number
|
||||
const endLine = startLine + span - 1
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
return (
|
||||
<ThreadSection
|
||||
key={`comment-${threadId}`}
|
||||
key={`comment-${threadId}-${activity?.created}-${activity?.edited}-${activity?.resolved}-${activity?.code_comment?.outdated}`}
|
||||
onlyTitle={
|
||||
activityBlocks[index + 1] !== undefined && isSystemComment(activityBlocks[index + 1]) ? true : false
|
||||
}
|
||||
|
@ -278,6 +294,7 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
enableReplyPlaceHolder={true}
|
||||
autoFocusAndPosition={true}
|
||||
copyLinkToComment={copyLinkToComment}
|
||||
suggestionBlock={suggestionBlock}
|
||||
handleAction={async (action, value, commentItem) => {
|
||||
let result = true
|
||||
let updatedItem: CommentItem<TypesPullReqActivity> | undefined = undefined
|
||||
|
@ -328,12 +345,15 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
}}
|
||||
outlets={{
|
||||
[CommentBoxOutletPosition.TOP_OF_FIRST_COMMENT]: isCodeComment(commentItems) && (
|
||||
<CodeCommentHeader
|
||||
commentItems={commentItems}
|
||||
threadId={threadId}
|
||||
repoMetadata={repoMetadata}
|
||||
pullReqMetadata={pullReqMetadata}
|
||||
/>
|
||||
<>
|
||||
<CommentThreadTopDecoration startLine={startLine} endLine={endLine} />
|
||||
<CodeCommentHeader
|
||||
commentItems={commentItems}
|
||||
threadId={threadId}
|
||||
repoMetadata={repoMetadata}
|
||||
pullReqMetadata={pullReqMetadata}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
[CommentBoxOutletPosition.LEFT_OF_OPTIONS_MENU]: (
|
||||
<CodeCommentStatusSelect
|
||||
|
@ -363,11 +383,11 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||
)
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[activityBlocks, currentUser, pullReqMetadata]
|
||||
[activityBlocks, currentUser, pullReqMetadata, activities]
|
||||
)
|
||||
|
||||
return (
|
||||
<PullRequestTabContentWrapper>
|
||||
<PullRequestTabContentWrapper section={PullRequestSection.CONVERSATION}>
|
||||
<Container>
|
||||
<Layout.Vertical spacing="xlarge">
|
||||
<PullRequestActionsBox
|
||||
|
|
|
@ -55,6 +55,7 @@ import {
|
|||
import { UserPreference, useUserPreference } from 'hooks/useUserPreference'
|
||||
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||
import RuleViolationAlertModal from 'components/RuleViolationAlertModal/RuleViolationAlertModal'
|
||||
import { PullReqSuggestionsBatch } from 'components/PullReqSuggestionsBatch/PullReqSuggestionsBatch'
|
||||
import css from './PullRequestActionsBox.module.scss'
|
||||
|
||||
const codeOwnersNotFoundMessage = 'CODEOWNERS file not found'
|
||||
|
@ -330,6 +331,7 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||
<Render when={loading || loadingState}>
|
||||
<Icon name={CodeIcon.InputSpinner} size={16} margin={{ right: 'xsmall' }} />
|
||||
</Render>
|
||||
<PullReqSuggestionsBatch />
|
||||
<Match expr={isDraft}>
|
||||
<Truthy>
|
||||
<SplitButton
|
||||
|
|
|
@ -189,7 +189,7 @@ export default function PullRequest() {
|
|||
/>
|
||||
),
|
||||
panel: (
|
||||
<Container className={css.changes}>
|
||||
<Container className={css.changes} data-page-section={PullRequestSection.FILES_CHANGED}>
|
||||
{!!repoMetadata && !!pullReqMetadata && !!pullReqStats && (
|
||||
<Changes
|
||||
repoMetadata={repoMetadata}
|
||||
|
|
|
@ -19,6 +19,7 @@ import type { TypesListCommitResponse } from 'services/code'
|
|||
import type { GitInfoProps } from 'utils/GitUtils'
|
||||
import { useStrings } from 'framework/strings'
|
||||
import { CommitsView } from 'components/CommitsView/CommitsView'
|
||||
import { PullRequestSection } from 'utils/Utils'
|
||||
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
||||
|
||||
interface PullRequestCommitsProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullReqMetadata'> {
|
||||
|
@ -33,7 +34,7 @@ export const PullRequestCommits: React.FC<PullRequestCommitsProps> = ({
|
|||
const { getString } = useStrings()
|
||||
|
||||
return (
|
||||
<PullRequestTabContentWrapper>
|
||||
<PullRequestTabContentWrapper section={PullRequestSection.COMMITS}>
|
||||
<CommitsView
|
||||
commits={pullReqCommits?.commits || []}
|
||||
repoMetadata={repoMetadata}
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
|
||||
import React from 'react'
|
||||
import { Container, PageError } from '@harnessio/uicore'
|
||||
import { getErrorMessage } from 'utils/Utils'
|
||||
import { PullRequestSection, getErrorMessage } from 'utils/Utils'
|
||||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||
|
||||
interface PullRequestTabContentWrapperProps {
|
||||
section: PullRequestSection
|
||||
className?: string
|
||||
loading?: boolean
|
||||
error?: Unknown
|
||||
|
@ -27,6 +28,7 @@ interface PullRequestTabContentWrapperProps {
|
|||
}
|
||||
|
||||
export const PullRequestTabContentWrapper: React.FC<PullRequestTabContentWrapperProps> = ({
|
||||
section,
|
||||
className,
|
||||
loading,
|
||||
error,
|
||||
|
@ -34,7 +36,7 @@ export const PullRequestTabContentWrapper: React.FC<PullRequestTabContentWrapper
|
|||
children
|
||||
}) => {
|
||||
return (
|
||||
<Container className={className} padding="xlarge">
|
||||
<Container className={className} padding="xlarge" data-page-section={section}>
|
||||
<LoadingSpinner visible={loading} withBorder={true} />
|
||||
{error && <PageError message={getErrorMessage(error)} onClick={onRetry} />}
|
||||
{!error && children}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
import type { EnumPullReqReviewDecision, TypesPullReqActivity } from 'services/code'
|
||||
import type { CommentItem } from 'components/CommentBox/CommentBox'
|
||||
import { CommentType } from 'components/DiffViewer/DiffViewerUtils'
|
||||
import type { PullRequestSection } from 'utils/Utils'
|
||||
|
||||
export function isCodeComment(commentItems: CommentItem<TypesPullReqActivity>[]) {
|
||||
return commentItems[0]?.payload?.type === CommentType.CODE_COMMENT
|
||||
|
@ -45,3 +46,12 @@ export const processReviewDecision = (
|
|||
review_decision === PullReqReviewDecision.approved && reviewedSHA !== sourceSHA
|
||||
? PullReqReviewDecision.outdated
|
||||
: review_decision
|
||||
|
||||
export function getActivePullReqPageSection(): PullRequestSection | undefined {
|
||||
return (document.querySelector('[data-page-section]') as HTMLElement)?.dataset?.pageSection as PullRequestSection
|
||||
}
|
||||
|
||||
export enum PullReqCustomEvent {
|
||||
REFETCH_DIFF = 'PullReqCustomEvent_REFETCH',
|
||||
REFETCH_ACTIVITIES = 'PullReqCustomEvent_REFETCH_ACTIVITIES'
|
||||
}
|
||||
|
|
|
@ -218,9 +218,9 @@ export function usePullReqActivities() {
|
|||
return activities
|
||||
}
|
||||
|
||||
const pullReqAtom = atom<TypesPullReq | undefined>(undefined)
|
||||
export const pullReqAtom = atom<TypesPullReq | undefined>(undefined)
|
||||
const pullReqStatsAtom = atom<TypesPullReqStats | undefined>(undefined)
|
||||
const pullReqActivitiesAtom = atom<TypesPullReqActivity[] | undefined>(undefined)
|
||||
export const pullReqActivitiesAtom = atom<TypesPullReqActivity[] | undefined>(undefined)
|
||||
const pullReqCommitsAtom = atom<TypesListCommitResponse | undefined>(undefined)
|
||||
|
||||
// Note: We just list COMMITS_LIMIT commits in PR page
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"target": "es2018",
|
||||
"lib": ["dom"],
|
||||
"lib": ["dom", "es2021"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"allowJs": true,
|
||||
|
|
257
web/yarn.lock
257
web/yarn.lock
|
@ -2441,6 +2441,13 @@
|
|||
dependencies:
|
||||
"@types/unist" "*"
|
||||
|
||||
"@types/hast@^3.0.0":
|
||||
version "3.0.4"
|
||||
resolved "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa"
|
||||
integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==
|
||||
dependencies:
|
||||
"@types/unist" "*"
|
||||
|
||||
"@types/history@^4.7.11":
|
||||
version "4.7.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
|
||||
|
@ -2541,6 +2548,13 @@
|
|||
dependencies:
|
||||
"@types/unist" "*"
|
||||
|
||||
"@types/mdast@^4.0.0":
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz#1e011ff013566e919a4232d1701ad30d70cab333"
|
||||
integrity sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==
|
||||
dependencies:
|
||||
"@types/unist" "*"
|
||||
|
||||
"@types/mime@*":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
|
||||
|
@ -2745,6 +2759,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
||||
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
||||
|
||||
"@types/unist@^3.0.0":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz#6dd61e43ef60b34086287f83683a5c1b2dc53d20"
|
||||
integrity sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==
|
||||
|
||||
"@types/ws@^8.5.5":
|
||||
version "8.5.5"
|
||||
resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb"
|
||||
|
@ -3143,6 +3162,11 @@
|
|||
remark-gfm "~3.0.1"
|
||||
unist-util-visit "^4.1.0"
|
||||
|
||||
"@ungap/structured-clone@^1.0.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
|
||||
|
||||
"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5":
|
||||
version "1.11.5"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c"
|
||||
|
@ -4121,6 +4145,11 @@ char-regex@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
|
||||
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
|
||||
|
||||
character-entities-html4@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b"
|
||||
integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==
|
||||
|
||||
character-entities-legacy@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
|
||||
|
@ -4901,6 +4930,13 @@ detect-node@^2.0.4:
|
|||
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
|
||||
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
|
||||
|
||||
devlop@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018"
|
||||
integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==
|
||||
dependencies:
|
||||
dequal "^2.0.0"
|
||||
|
||||
diff-sequences@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
|
||||
|
@ -6531,6 +6567,20 @@ hast-util-from-parse5@^7.0.0:
|
|||
vfile-location "^4.0.0"
|
||||
web-namespaces "^2.0.0"
|
||||
|
||||
hast-util-from-parse5@^8.0.0:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz#654a5676a41211e14ee80d1b1758c399a0327651"
|
||||
integrity sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==
|
||||
dependencies:
|
||||
"@types/hast" "^3.0.0"
|
||||
"@types/unist" "^3.0.0"
|
||||
devlop "^1.0.0"
|
||||
hastscript "^8.0.0"
|
||||
property-information "^6.0.0"
|
||||
vfile "^6.0.0"
|
||||
vfile-location "^5.0.0"
|
||||
web-namespaces "^2.0.0"
|
||||
|
||||
hast-util-has-property@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz#8ec99c3e8f02626304ee438cdb9f0528b017e083"
|
||||
|
@ -6558,6 +6608,13 @@ hast-util-parse-selector@^3.0.0:
|
|||
dependencies:
|
||||
"@types/hast" "^2.0.0"
|
||||
|
||||
hast-util-parse-selector@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27"
|
||||
integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==
|
||||
dependencies:
|
||||
"@types/hast" "^3.0.0"
|
||||
|
||||
hast-util-raw@^7.2.0:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-7.2.3.tgz#dcb5b22a22073436dbdc4aa09660a644f4991d99"
|
||||
|
@ -6575,6 +6632,25 @@ hast-util-raw@^7.2.0:
|
|||
web-namespaces "^2.0.0"
|
||||
zwitch "^2.0.0"
|
||||
|
||||
hast-util-raw@^9.0.0:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.2.tgz#39b4a4886bd9f0a5dd42e86d02c966c2c152884c"
|
||||
integrity sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==
|
||||
dependencies:
|
||||
"@types/hast" "^3.0.0"
|
||||
"@types/unist" "^3.0.0"
|
||||
"@ungap/structured-clone" "^1.0.0"
|
||||
hast-util-from-parse5 "^8.0.0"
|
||||
hast-util-to-parse5 "^8.0.0"
|
||||
html-void-elements "^3.0.0"
|
||||
mdast-util-to-hast "^13.0.0"
|
||||
parse5 "^7.0.0"
|
||||
unist-util-position "^5.0.0"
|
||||
unist-util-visit "^5.0.0"
|
||||
vfile "^6.0.0"
|
||||
web-namespaces "^2.0.0"
|
||||
zwitch "^2.0.0"
|
||||
|
||||
hast-util-select@^5.0.5, hast-util-select@~5.0.1:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-select/-/hast-util-select-5.0.5.tgz#be9ccb71d2278681ca024727f12abd4f93b3e9bc"
|
||||
|
@ -6596,6 +6672,24 @@ hast-util-select@^5.0.5, hast-util-select@~5.0.1:
|
|||
unist-util-visit "^4.0.0"
|
||||
zwitch "^2.0.0"
|
||||
|
||||
hast-util-to-html@^9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.1.tgz#d108aba473c0ced8377267b1a725b25e818ff3c8"
|
||||
integrity sha512-hZOofyZANbyWo+9RP75xIDV/gq+OUKx+T46IlwERnKmfpwp81XBFbT9mi26ws+SJchA4RVUQwIBJpqEOBhMzEQ==
|
||||
dependencies:
|
||||
"@types/hast" "^3.0.0"
|
||||
"@types/unist" "^3.0.0"
|
||||
ccount "^2.0.0"
|
||||
comma-separated-tokens "^2.0.0"
|
||||
hast-util-raw "^9.0.0"
|
||||
hast-util-whitespace "^3.0.0"
|
||||
html-void-elements "^3.0.0"
|
||||
mdast-util-to-hast "^13.0.0"
|
||||
property-information "^6.0.0"
|
||||
space-separated-tokens "^2.0.0"
|
||||
stringify-entities "^4.0.0"
|
||||
zwitch "^2.0.4"
|
||||
|
||||
hast-util-to-parse5@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz#c49391bf8f151973e0c9adcd116b561e8daf29f3"
|
||||
|
@ -6608,6 +6702,19 @@ hast-util-to-parse5@^7.0.0:
|
|||
web-namespaces "^2.0.0"
|
||||
zwitch "^2.0.0"
|
||||
|
||||
hast-util-to-parse5@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz#477cd42d278d4f036bc2ea58586130f6f39ee6ed"
|
||||
integrity sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==
|
||||
dependencies:
|
||||
"@types/hast" "^3.0.0"
|
||||
comma-separated-tokens "^2.0.0"
|
||||
devlop "^1.0.0"
|
||||
property-information "^6.0.0"
|
||||
space-separated-tokens "^2.0.0"
|
||||
web-namespaces "^2.0.0"
|
||||
zwitch "^2.0.0"
|
||||
|
||||
hast-util-to-string@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz#b008b0a4ea472bf34dd390b7eea1018726ae152a"
|
||||
|
@ -6620,6 +6727,13 @@ hast-util-whitespace@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz#0ec64e257e6fc216c7d14c8a1b74d27d650b4557"
|
||||
integrity sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==
|
||||
|
||||
hast-util-whitespace@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621"
|
||||
integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==
|
||||
dependencies:
|
||||
"@types/hast" "^3.0.0"
|
||||
|
||||
hastscript@^7.0.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-7.2.0.tgz#0eafb7afb153d047077fa2a833dc9b7ec604d10b"
|
||||
|
@ -6631,6 +6745,17 @@ hastscript@^7.0.0:
|
|||
property-information "^6.0.0"
|
||||
space-separated-tokens "^2.0.0"
|
||||
|
||||
hastscript@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz#4ef795ec8dee867101b9f23cc830d4baf4fd781a"
|
||||
integrity sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==
|
||||
dependencies:
|
||||
"@types/hast" "^3.0.0"
|
||||
comma-separated-tokens "^2.0.0"
|
||||
hast-util-parse-selector "^4.0.0"
|
||||
property-information "^6.0.0"
|
||||
space-separated-tokens "^2.0.0"
|
||||
|
||||
he@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
|
@ -6736,6 +6861,11 @@ html-void-elements@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f"
|
||||
integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==
|
||||
|
||||
html-void-elements@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7"
|
||||
integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==
|
||||
|
||||
html-webpack-plugin@^5.3.1:
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.1.tgz#826838e31b427f5f7f30971f8d8fa2422dfa6763"
|
||||
|
@ -8597,6 +8727,21 @@ mdast-util-to-hast@^12.1.0:
|
|||
unist-util-position "^4.0.0"
|
||||
unist-util-visit "^4.0.0"
|
||||
|
||||
mdast-util-to-hast@^13.0.0:
|
||||
version "13.1.0"
|
||||
resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz#1ae54d903150a10fe04d59f03b2b95fd210b2124"
|
||||
integrity sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==
|
||||
dependencies:
|
||||
"@types/hast" "^3.0.0"
|
||||
"@types/mdast" "^4.0.0"
|
||||
"@ungap/structured-clone" "^1.0.0"
|
||||
devlop "^1.0.0"
|
||||
micromark-util-sanitize-uri "^2.0.0"
|
||||
trim-lines "^3.0.0"
|
||||
unist-util-position "^5.0.0"
|
||||
unist-util-visit "^5.0.0"
|
||||
vfile "^6.0.0"
|
||||
|
||||
mdast-util-to-markdown@^1.0.0, mdast-util-to-markdown@^1.3.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz#c13343cb3fc98621911d33b5cd42e7d0731171c6"
|
||||
|
@ -8839,6 +8984,14 @@ micromark-util-character@^1.0.0:
|
|||
micromark-util-symbol "^1.0.0"
|
||||
micromark-util-types "^1.0.0"
|
||||
|
||||
micromark-util-character@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz#31320ace16b4644316f6bf057531689c71e2aee1"
|
||||
integrity sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==
|
||||
dependencies:
|
||||
micromark-util-symbol "^2.0.0"
|
||||
micromark-util-types "^2.0.0"
|
||||
|
||||
micromark-util-chunked@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz#5b40d83f3d53b84c4c6bce30ed4257e9a4c79d06"
|
||||
|
@ -8885,6 +9038,11 @@ micromark-util-encode@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz#2c1c22d3800870ad770ece5686ebca5920353383"
|
||||
integrity sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==
|
||||
|
||||
micromark-util-encode@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz#0921ac7953dc3f1fd281e3d1932decfdb9382ab1"
|
||||
integrity sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==
|
||||
|
||||
micromark-util-html-tag-name@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz#eb227118befd51f48858e879b7a419fc0df20497"
|
||||
|
@ -8913,6 +9071,15 @@ micromark-util-sanitize-uri@^1.0.0, micromark-util-sanitize-uri@^1.1.0:
|
|||
micromark-util-encode "^1.0.0"
|
||||
micromark-util-symbol "^1.0.0"
|
||||
|
||||
micromark-util-sanitize-uri@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz#ec8fbf0258e9e6d8f13d9e4770f9be64342673de"
|
||||
integrity sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==
|
||||
dependencies:
|
||||
micromark-util-character "^2.0.0"
|
||||
micromark-util-encode "^2.0.0"
|
||||
micromark-util-symbol "^2.0.0"
|
||||
|
||||
micromark-util-subtokenize@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz#ff6f1af6ac836f8bfdbf9b02f40431760ad89105"
|
||||
|
@ -8928,11 +9095,21 @@ micromark-util-symbol@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz#b90344db62042ce454f351cf0bebcc0a6da4920e"
|
||||
integrity sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==
|
||||
|
||||
micromark-util-symbol@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz#12225c8f95edf8b17254e47080ce0862d5db8044"
|
||||
integrity sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==
|
||||
|
||||
micromark-util-types@^1.0.0, micromark-util-types@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.0.2.tgz#f4220fdb319205812f99c40f8c87a9be83eded20"
|
||||
integrity sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==
|
||||
|
||||
micromark-util-types@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz#63b4b7ffeb35d3ecf50d1ca20e68fc7caa36d95e"
|
||||
integrity sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==
|
||||
|
||||
micromark@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.1.0.tgz#eeba0fe0ac1c9aaef675157b52c166f125e89f62"
|
||||
|
@ -9705,6 +9882,13 @@ parse5@6.0.1, parse5@^6.0.0:
|
|||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
||||
|
||||
parse5@^7.0.0:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
|
||||
integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
|
||||
dependencies:
|
||||
entities "^4.4.0"
|
||||
|
||||
parseurl@~1.3.2, parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
|
@ -11617,6 +11801,14 @@ string_decoder@~1.1.1:
|
|||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
stringify-entities@^4.0.0:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3"
|
||||
integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==
|
||||
dependencies:
|
||||
character-entities-html4 "^2.0.0"
|
||||
character-entities-legacy "^3.0.0"
|
||||
|
||||
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||
|
@ -12236,6 +12428,13 @@ unist-util-is@^5.0.0:
|
|||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
|
||||
unist-util-is@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424"
|
||||
integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==
|
||||
dependencies:
|
||||
"@types/unist" "^3.0.0"
|
||||
|
||||
unist-util-position@^4.0.0:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-4.0.4.tgz#93f6d8c7d6b373d9b825844645877c127455f037"
|
||||
|
@ -12243,6 +12442,13 @@ unist-util-position@^4.0.0:
|
|||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
|
||||
unist-util-position@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4"
|
||||
integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==
|
||||
dependencies:
|
||||
"@types/unist" "^3.0.0"
|
||||
|
||||
unist-util-stringify-position@^3.0.0:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d"
|
||||
|
@ -12250,6 +12456,13 @@ unist-util-stringify-position@^3.0.0:
|
|||
dependencies:
|
||||
"@types/unist" "^2.0.0"
|
||||
|
||||
unist-util-stringify-position@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2"
|
||||
integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==
|
||||
dependencies:
|
||||
"@types/unist" "^3.0.0"
|
||||
|
||||
unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb"
|
||||
|
@ -12258,6 +12471,14 @@ unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1:
|
|||
"@types/unist" "^2.0.0"
|
||||
unist-util-is "^5.0.0"
|
||||
|
||||
unist-util-visit-parents@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815"
|
||||
integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==
|
||||
dependencies:
|
||||
"@types/unist" "^3.0.0"
|
||||
unist-util-is "^6.0.0"
|
||||
|
||||
unist-util-visit@^4.0.0, unist-util-visit@^4.1.0, unist-util-visit@^4.1.2, unist-util-visit@~4.1.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz#125a42d1eb876283715a3cb5cceaa531828c72e2"
|
||||
|
@ -12267,6 +12488,15 @@ unist-util-visit@^4.0.0, unist-util-visit@^4.1.0, unist-util-visit@^4.1.2, unist
|
|||
unist-util-is "^5.0.0"
|
||||
unist-util-visit-parents "^5.1.1"
|
||||
|
||||
unist-util-visit@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6"
|
||||
integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==
|
||||
dependencies:
|
||||
"@types/unist" "^3.0.0"
|
||||
unist-util-is "^6.0.0"
|
||||
unist-util-visit-parents "^6.0.0"
|
||||
|
||||
universalify@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||
|
@ -12479,6 +12709,14 @@ vfile-location@^4.0.0:
|
|||
"@types/unist" "^2.0.0"
|
||||
vfile "^5.0.0"
|
||||
|
||||
vfile-location@^5.0.0:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz#220d9ca1ab6f8b2504a4db398f7ebc149f9cb464"
|
||||
integrity sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==
|
||||
dependencies:
|
||||
"@types/unist" "^3.0.0"
|
||||
vfile "^6.0.0"
|
||||
|
||||
vfile-message@^3.0.0:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.4.tgz#15a50816ae7d7c2d1fa87090a7f9f96612b59dea"
|
||||
|
@ -12487,6 +12725,14 @@ vfile-message@^3.0.0:
|
|||
"@types/unist" "^2.0.0"
|
||||
unist-util-stringify-position "^3.0.0"
|
||||
|
||||
vfile-message@^4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181"
|
||||
integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==
|
||||
dependencies:
|
||||
"@types/unist" "^3.0.0"
|
||||
unist-util-stringify-position "^4.0.0"
|
||||
|
||||
vfile@^5.0.0:
|
||||
version "5.3.7"
|
||||
resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.7.tgz#de0677e6683e3380fafc46544cfe603118826ab7"
|
||||
|
@ -12497,6 +12743,15 @@ vfile@^5.0.0:
|
|||
unist-util-stringify-position "^3.0.0"
|
||||
vfile-message "^3.0.0"
|
||||
|
||||
vfile@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz#1e8327f41eac91947d4fe9d237a2dd9209762536"
|
||||
integrity sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==
|
||||
dependencies:
|
||||
"@types/unist" "^3.0.0"
|
||||
unist-util-stringify-position "^4.0.0"
|
||||
vfile-message "^4.0.0"
|
||||
|
||||
vscode-languageserver-textdocument@^1.0.0:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz#9eae94509cbd945ea44bca8dcfe4bb0c15bb3ac0"
|
||||
|
@ -13035,7 +13290,7 @@ yup@^0.29.1:
|
|||
synchronous-promise "^2.0.13"
|
||||
toposort "^2.0.2"
|
||||
|
||||
zwitch@^2.0.0:
|
||||
zwitch@^2.0.0, zwitch@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
|
||||
integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==
|
||||
|
|
Loading…
Reference in New Issue