mirror of
https://github.com/harness/drone.git
synced 2025-05-31 11:43:15 +00:00
Pull Request Code suggestions (#2032)
This commit is contained in:
parent
ea205ff7ba
commit
44d1a82e6f
@ -58,6 +58,7 @@
|
|||||||
"diff2html": "3.4.22",
|
"diff2html": "3.4.22",
|
||||||
"dompurify": "^3.0.5",
|
"dompurify": "^3.0.5",
|
||||||
"formik": "2.2.9",
|
"formik": "2.2.9",
|
||||||
|
"hast-util-to-html": "^9.0.1",
|
||||||
"highlight.js": "^11.8.0",
|
"highlight.js": "^11.8.0",
|
||||||
"iconoir-react": "^6.11.0",
|
"iconoir-react": "^6.11.0",
|
||||||
"jotai": "^2.6.3",
|
"jotai": "^2.6.3",
|
||||||
|
24
web/src/atoms/pullReqSuggestions.ts
Normal file
24
web/src/atoms/pullReqSuggestions.ts
Normal file
@ -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 { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||||
import { createRequestIdleCallbackTaskPool } from 'utils/Task'
|
import { createRequestIdleCallbackTaskPool } from 'utils/Task'
|
||||||
import { PlainButton } from 'components/PlainButton/PlainButton'
|
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 type { UseGetPullRequestInfoResult } from 'pages/PullRequest/useGetPullRequestInfo'
|
||||||
import { InViewDiffBlockRenderer } from 'components/DiffViewer/InViewDiffBlockRenderer'
|
import { InViewDiffBlockRenderer } from 'components/DiffViewer/InViewDiffBlockRenderer'
|
||||||
import Config from 'Config'
|
import Config from 'Config'
|
||||||
|
import { PullReqSuggestionsBatch } from 'components/PullReqSuggestionsBatch/PullReqSuggestionsBatch'
|
||||||
|
import { PullReqCustomEvent } from 'pages/PullRequest/PullRequestUtils'
|
||||||
import { ChangesDropdown } from './ChangesDropdown'
|
import { ChangesDropdown } from './ChangesDropdown'
|
||||||
import { DiffViewConfiguration } from './DiffViewConfiguration'
|
import { DiffViewConfiguration } from './DiffViewConfiguration'
|
||||||
import ReviewSplitButton from './ReviewSplitButton/ReviewSplitButton'
|
import ReviewSplitButton from './ReviewSplitButton/ReviewSplitButton'
|
||||||
@ -406,6 +408,16 @@ const ChangesInternal: React.FC<ChangesProps> = ({
|
|||||||
[diffBlocks, isMounted]
|
[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
|
* 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.
|
* are specified in URL, leverage onJumpToFile() to jump to file, then comment.
|
||||||
@ -430,6 +442,7 @@ const ChangesInternal: React.FC<ChangesProps> = ({
|
|||||||
}, [diffs, setPullReqChangesCount])
|
}, [diffs, setPullReqChangesCount])
|
||||||
|
|
||||||
useShowRequestError(errorFileViews, 0)
|
useShowRequestError(errorFileViews, 0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={cx(css.container, className)} {...(!!loadingRawDiff || !!error ? { flex: true } : {})}>
|
<Container className={cx(css.container, className)} {...(!!loadingRawDiff || !!error ? { flex: true } : {})}>
|
||||||
<LoadingSpinner visible={loading} withBorder={true} />
|
<LoadingSpinner visible={loading} withBorder={true} />
|
||||||
@ -471,13 +484,8 @@ const ChangesInternal: React.FC<ChangesProps> = ({
|
|||||||
<PlainButton
|
<PlainButton
|
||||||
text={getString('refresh')}
|
text={getString('refresh')}
|
||||||
className={css.refreshBtn}
|
className={css.refreshBtn}
|
||||||
onClick={() => {
|
onClick={refreshPullReq}
|
||||||
setCachedDiff({})
|
data-button-name="refresh-pr"
|
||||||
setTargetRef(_targetRef)
|
|
||||||
setSourceRef(_sourceRef)
|
|
||||||
setPrHasChanged(false)
|
|
||||||
refetchCommits?.()
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Render>
|
</Render>
|
||||||
|
|
||||||
@ -497,13 +505,19 @@ const ChangesInternal: React.FC<ChangesProps> = ({
|
|||||||
|
|
||||||
<FlexExpander />
|
<FlexExpander />
|
||||||
|
|
||||||
<ReviewSplitButton
|
<Container flex={{ alignItems: 'center' }}>
|
||||||
shouldHide={shouldHideReviewButton}
|
<Layout.Horizontal spacing="medium">
|
||||||
repoMetadata={repoMetadata}
|
<PullReqSuggestionsBatch />
|
||||||
pullRequestMetadata={pullRequestMetadata}
|
|
||||||
refreshPr={voidFn(noop)}
|
<ReviewSplitButton
|
||||||
disabled={isActiveUserPROwner}
|
shouldHide={shouldHideReviewButton}
|
||||||
/>
|
repoMetadata={repoMetadata}
|
||||||
|
pullRequestMetadata={pullRequestMetadata}
|
||||||
|
refreshPr={voidFn(noop)}
|
||||||
|
disabled={isActiveUserPROwner}
|
||||||
|
/>
|
||||||
|
</Layout.Horizontal>
|
||||||
|
</Container>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</Container>
|
</Container>
|
||||||
</Render>
|
</Render>
|
||||||
|
@ -38,6 +38,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:has([data-outdated='true']) [data-section-id='CodeSuggestionBlockButtons'] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16),
|
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);
|
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.
|
* 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 type { EditorView } from '@codemirror/view'
|
||||||
import { Render, Match, Truthy, Falsy, Else } from 'react-jsx-match'
|
import { Render, Match, Truthy, Falsy, Else } from 'react-jsx-match'
|
||||||
import {
|
import { Container, Layout, Avatar, TextInput, Text, FlexExpander, Button, useIsMounted } from '@harnessio/uicore'
|
||||||
Container,
|
|
||||||
Layout,
|
|
||||||
Avatar,
|
|
||||||
TextInput,
|
|
||||||
Text,
|
|
||||||
FlexExpander,
|
|
||||||
Button,
|
|
||||||
useIsMounted,
|
|
||||||
ButtonVariation,
|
|
||||||
ButtonSize,
|
|
||||||
StringSubstitute,
|
|
||||||
AvatarGroup
|
|
||||||
} from '@harnessio/uicore'
|
|
||||||
import { Color, FontVariation } from '@harnessio/design-system'
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
import cx from 'classnames'
|
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 { TimePopoverWithLocal } from 'utils/timePopoverLocal/TimePopoverWithLocal'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { ThreadSection } from 'components/ThreadSection/ThreadSection'
|
import { ThreadSection } from 'components/ThreadSection/ThreadSection'
|
||||||
@ -46,6 +33,7 @@ import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer'
|
|||||||
import { ButtonRoleProps } from 'utils/Utils'
|
import { ButtonRoleProps } from 'utils/Utils'
|
||||||
import { useResizeObserver } from 'hooks/useResizeObserver'
|
import { useResizeObserver } from 'hooks/useResizeObserver'
|
||||||
import { useCustomEventListener } from 'hooks/useEventListener'
|
import { useCustomEventListener } from 'hooks/useEventListener'
|
||||||
|
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
|
||||||
import css from './CommentBox.module.scss'
|
import css from './CommentBox.module.scss'
|
||||||
|
|
||||||
export interface CommentItem<T = unknown> {
|
export interface CommentItem<T = unknown> {
|
||||||
@ -108,6 +96,7 @@ interface CommentBoxProps<T> {
|
|||||||
standalone: boolean
|
standalone: boolean
|
||||||
routingId: string
|
routingId: string
|
||||||
copyLinkToComment: (commentId: number, commentItem: CommentItem<T>) => void
|
copyLinkToComment: (commentId: number, commentItem: CommentItem<T>) => void
|
||||||
|
suggestionBlock?: SuggestionBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommentBoxInternal = <T = unknown,>({
|
const CommentBoxInternal = <T = unknown,>({
|
||||||
@ -131,7 +120,8 @@ const CommentBoxInternal = <T = unknown,>({
|
|||||||
repoMetadata,
|
repoMetadata,
|
||||||
standalone,
|
standalone,
|
||||||
routingId,
|
routingId,
|
||||||
copyLinkToComment
|
copyLinkToComment,
|
||||||
|
suggestionBlock
|
||||||
}: CommentBoxProps<T>) => {
|
}: CommentBoxProps<T>) => {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const [comments, setComments] = useState<CommentItem<T>[]>(commentItems)
|
const [comments, setComments] = useState<CommentItem<T>[]>(commentItems)
|
||||||
@ -217,6 +207,7 @@ const CommentBoxInternal = <T = unknown,>({
|
|||||||
}}
|
}}
|
||||||
outlets={outlets}
|
outlets={outlets}
|
||||||
copyLinkToComment={copyLinkToComment}
|
copyLinkToComment={copyLinkToComment}
|
||||||
|
suggestionBlock={suggestionBlock}
|
||||||
/>
|
/>
|
||||||
<Match expr={showReplyPlaceHolder && enableReplyPlaceHolderRef.current}>
|
<Match expr={showReplyPlaceHolder && enableReplyPlaceHolderRef.current}>
|
||||||
<Truthy>
|
<Truthy>
|
||||||
@ -298,6 +289,7 @@ const CommentBoxInternal = <T = unknown,>({
|
|||||||
setDirties({ ...dirties, ['new']: _dirty })
|
setDirties({ ...dirties, ['new']: _dirty })
|
||||||
}}
|
}}
|
||||||
autoFocusAndPosition={autoFocusAndPosition ? !showReplyPlaceHolder : false}
|
autoFocusAndPosition={autoFocusAndPosition ? !showReplyPlaceHolder : false}
|
||||||
|
suggestionBlock={suggestionBlock}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
</Falsy>
|
</Falsy>
|
||||||
@ -310,7 +302,10 @@ const CommentBoxInternal = <T = unknown,>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface CommentsThreadProps<T>
|
interface CommentsThreadProps<T>
|
||||||
extends Pick<CommentBoxProps<T>, 'commentItems' | 'handleAction' | 'outlets' | 'copyLinkToComment'> {
|
extends Pick<
|
||||||
|
CommentBoxProps<T>,
|
||||||
|
'commentItems' | 'handleAction' | 'outlets' | 'copyLinkToComment' | 'suggestionBlock'
|
||||||
|
> {
|
||||||
onQuote: (content: string) => void
|
onQuote: (content: string) => void
|
||||||
setDirty: (index: number, dirty: boolean) => void
|
setDirty: (index: number, dirty: boolean) => void
|
||||||
repoMetadata: TypesRepository | undefined
|
repoMetadata: TypesRepository | undefined
|
||||||
@ -323,7 +318,8 @@ const CommentsThread = <T = unknown,>({
|
|||||||
setDirty,
|
setDirty,
|
||||||
outlets = {},
|
outlets = {},
|
||||||
repoMetadata,
|
repoMetadata,
|
||||||
copyLinkToComment
|
copyLinkToComment,
|
||||||
|
suggestionBlock
|
||||||
}: CommentsThreadProps<T>) => {
|
}: CommentsThreadProps<T>) => {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const { standalone, routingId } = useAppContext()
|
const { standalone, routingId } = useAppContext()
|
||||||
@ -335,22 +331,22 @@ const CommentsThread = <T = unknown,>({
|
|||||||
},
|
},
|
||||||
[editIndexes]
|
[editIndexes]
|
||||||
)
|
)
|
||||||
const collapseResolvedComments = useMemo(() => !!get(commentItems[0], 'payload.resolved'), [commentItems])
|
// const collapseResolvedComments = useMemo(() => !!get(commentItems[0], 'payload.resolved'), [commentItems])
|
||||||
const shouldCollapsedResolvedComments = useMemo(
|
// const shouldCollapsedResolvedComments = useMemo(
|
||||||
() =>
|
// () =>
|
||||||
collapseResolvedComments &&
|
// collapseResolvedComments &&
|
||||||
!(commentItems.length === 1 && shorten(commentItems[0].content) === commentItems[0].content),
|
// !(commentItems.length === 1 && shorten(commentItems[0].content) === commentItems[0].content),
|
||||||
[commentItems, collapseResolvedComments]
|
// [commentItems, collapseResolvedComments]
|
||||||
)
|
// )
|
||||||
const [collapsed, setCollapsed] = useState(collapseResolvedComments)
|
// const [collapsed, setCollapsed] = useState(collapseResolvedComments)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Render when={commentItems.length}>
|
<Render when={commentItems.length}>
|
||||||
<Container className={css.viewer} padding="xlarge">
|
<Container className={css.viewer} padding="xlarge">
|
||||||
{commentItems
|
{commentItems
|
||||||
.filter((_commentItem, index) => {
|
// .filter((_commentItem, index) => {
|
||||||
return collapseResolvedComments && collapsed ? index === 0 : true
|
// return collapseResolvedComments && collapsed ? index === 0 : true
|
||||||
})
|
// })
|
||||||
.map((commentItem, index) => {
|
.map((commentItem, index) => {
|
||||||
const isLastItem = index === commentItems.length - 1
|
const isLastItem = index === commentItems.length - 1
|
||||||
|
|
||||||
@ -358,7 +354,10 @@ const CommentsThread = <T = unknown,>({
|
|||||||
<ThreadSection
|
<ThreadSection
|
||||||
key={index}
|
key={index}
|
||||||
title={
|
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>
|
<Text inline icon="code-chat"></Text>
|
||||||
<Avatar name={commentItem?.author} size="small" hoverCard={false} />
|
<Avatar name={commentItem?.author} size="small" hoverCard={false} />
|
||||||
<Text inline>
|
<Text inline>
|
||||||
@ -444,7 +443,7 @@ const CommentsThread = <T = unknown,>({
|
|||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
}
|
}
|
||||||
hideGutter={isLastItem || (collapseResolvedComments && collapsed)}>
|
hideGutter={isLastItem /*|| (collapseResolvedComments && collapsed)*/}>
|
||||||
<Container padding={{ bottom: isLastItem ? undefined : 'xsmall' }} data-comment-id={commentItem.id}>
|
<Container padding={{ bottom: isLastItem ? undefined : 'xsmall' }} data-comment-id={commentItem.id}>
|
||||||
<Render when={index === 0 && outlets[CommentBoxOutletPosition.TOP_OF_FIRST_COMMENT]}>
|
<Render when={index === 0 && outlets[CommentBoxOutletPosition.TOP_OF_FIRST_COMMENT]}>
|
||||||
<Container className={css.outletTopOfFirstOfComment}>
|
<Container className={css.outletTopOfFirstOfComment}>
|
||||||
@ -478,6 +477,7 @@ const CommentsThread = <T = unknown,>({
|
|||||||
cancel: getString('cancel')
|
cancel: getString('cancel')
|
||||||
}}
|
}}
|
||||||
autoFocusAndPosition
|
autoFocusAndPosition
|
||||||
|
suggestionBlock={suggestionBlock}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
</Truthy>
|
</Truthy>
|
||||||
@ -489,10 +489,23 @@ const CommentsThread = <T = unknown,>({
|
|||||||
<Else>
|
<Else>
|
||||||
<MarkdownViewer
|
<MarkdownViewer
|
||||||
source={
|
source={
|
||||||
collapseResolvedComments && collapsed
|
/* collapseResolvedComments && collapsed
|
||||||
? shorten(commentItem?.content)
|
? shorten(commentItem?.content)
|
||||||
: 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>
|
</Else>
|
||||||
</Match>
|
</Match>
|
||||||
@ -503,7 +516,7 @@ const CommentsThread = <T = unknown,>({
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<Render when={shouldCollapsedResolvedComments}>
|
{/* <Render when={shouldCollapsedResolvedComments}>
|
||||||
<Container
|
<Container
|
||||||
flex={{ justifyContent: 'space-around' }}
|
flex={{ justifyContent: 'space-around' }}
|
||||||
padding={{ bottom: 'xsmall' }}
|
padding={{ bottom: 'xsmall' }}
|
||||||
@ -538,7 +551,7 @@ const CommentsThread = <T = unknown,>({
|
|||||||
</Button>
|
</Button>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
</Container>
|
</Container>
|
||||||
</Render>
|
</Render> */}
|
||||||
</Container>
|
</Container>
|
||||||
</Render>
|
</Render>
|
||||||
)
|
)
|
||||||
@ -548,11 +561,11 @@ export const CommentBox = React.memo(CommentBoxInternal)
|
|||||||
|
|
||||||
export const customEventForCommentWithId = (id: number) => `CommentBoxCustomEvent-${id}`
|
export const customEventForCommentWithId = (id: number) => `CommentBoxCustomEvent-${id}`
|
||||||
|
|
||||||
const shorten = (str = '', maxLen = 140, separator = ' ') => {
|
// const shorten = (str = '', maxLen = 140, separator = ' ') => {
|
||||||
const s = str.split('\n')[0]
|
// const s = str.split('\n')[0]
|
||||||
const sub = s.length <= maxLen ? s : s.substr(0, s.lastIndexOf(separator, maxLen))
|
// 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'
|
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>[]
|
||||||
_commentItems?: CommentItem<T>[]
|
_commentItems?: CommentItem<T>[]
|
||||||
filePath: string
|
filePath: string
|
||||||
|
codeBlockContent?: string
|
||||||
destroy: (() => void) | undefined
|
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 lineNumberStart = (right ? activity.code_comment?.line_new : activity.code_comment?.line_old) as number
|
||||||
const lineNumberEnd = lineNumberStart + span - 1
|
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 {
|
return {
|
||||||
inner: activity,
|
inner: activity,
|
||||||
left: !right,
|
left: !right,
|
||||||
@ -256,7 +291,8 @@ export function activitiesToDiffCommentItems(
|
|||||||
commentItems: [activityToCommentItem(activity)].concat(replyComments),
|
commentItems: [activityToCommentItem(activity)].concat(replyComments),
|
||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
destroy: undefined,
|
destroy: undefined,
|
||||||
eventStream: undefined
|
eventStream: undefined,
|
||||||
|
codeBlockContent
|
||||||
}
|
}
|
||||||
}) || []
|
}) || []
|
||||||
)
|
)
|
||||||
|
@ -19,8 +19,7 @@ import { useMutate } from 'restful-react'
|
|||||||
import Selecto from 'selecto'
|
import Selecto from 'selecto'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { useToaster, ButtonProps, Utils, Text } from '@harnessio/uicore'
|
import { useToaster, ButtonProps, Utils } from '@harnessio/uicore'
|
||||||
import { Color, FontVariation } from '@harnessio/design-system'
|
|
||||||
import { findLastIndex, isEqual, max, noop, random, uniq } from 'lodash-es'
|
import { findLastIndex, isEqual, max, noop, random, uniq } from 'lodash-es'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import type { GitInfoProps } from 'utils/GitUtils'
|
import type { GitInfoProps } from 'utils/GitUtils'
|
||||||
@ -28,13 +27,15 @@ import type { DiffFileEntry } from 'utils/types'
|
|||||||
import { useConfirmAct } from 'hooks/useConfirmAction'
|
import { useConfirmAct } from 'hooks/useConfirmAction'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import type { OpenapiCommentCreatePullReqRequest, TypesPullReq, TypesPullReqActivity } from 'services/code'
|
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 { AppWrapper } from 'App'
|
||||||
import { CodeCommentStatusButton } from 'components/CodeCommentStatusButton/CodeCommentStatusButton'
|
import { CodeCommentStatusButton } from 'components/CodeCommentStatusButton/CodeCommentStatusButton'
|
||||||
import { CodeCommentSecondarySaveButton } from 'components/CodeCommentSecondarySaveButton/CodeCommentSecondarySaveButton'
|
import { CodeCommentSecondarySaveButton } from 'components/CodeCommentSecondarySaveButton/CodeCommentSecondarySaveButton'
|
||||||
import { CodeCommentStatusSelect } from 'components/CodeCommentStatusSelect/CodeCommentStatusSelect'
|
import { CodeCommentStatusSelect } from 'components/CodeCommentStatusSelect/CodeCommentStatusSelect'
|
||||||
import { dispatchCustomEvent } from 'hooks/useEventListener'
|
import { dispatchCustomEvent } from 'hooks/useEventListener'
|
||||||
import { UseGetPullRequestInfoResult, usePullReqActivities } from 'pages/PullRequest/useGetPullRequestInfo'
|
import { UseGetPullRequestInfoResult, usePullReqActivities } from 'pages/PullRequest/useGetPullRequestInfo'
|
||||||
|
import { CommentThreadTopDecoration } from 'components/CommentThreadTopDecoration/CommentThreadTopDecoration'
|
||||||
|
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
|
||||||
import {
|
import {
|
||||||
activitiesToDiffCommentItems,
|
activitiesToDiffCommentItems,
|
||||||
activityToCommentItem,
|
activityToCommentItem,
|
||||||
@ -273,6 +274,15 @@ export function usePullReqComments({
|
|||||||
// update to the latest data
|
// update to the latest data
|
||||||
comment._commentItems = structuredClone(comment.commentItems)
|
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.
|
// Note: CommentBox is rendered as an independent React component.
|
||||||
// Everything passed to it must be either values, or refs.
|
// Everything passed to it must be either values, or refs.
|
||||||
// If you pass callbacks or states, they won't be updated and
|
// If you pass callbacks or states, they won't be updated and
|
||||||
@ -302,6 +312,7 @@ export function usePullReqComments({
|
|||||||
setDirty={setDirty || noop}
|
setDirty={setDirty || noop}
|
||||||
currentUserName={currentUser?.display_name || currentUser?.email || ''}
|
currentUserName={currentUser?.display_name || currentUser?.email || ''}
|
||||||
copyLinkToComment={copyLinkToComment}
|
copyLinkToComment={copyLinkToComment}
|
||||||
|
suggestionBlock={suggestionBlock}
|
||||||
handleAction={async (action, value, commentItem) => {
|
handleAction={async (action, value, commentItem) => {
|
||||||
let result = true
|
let result = true
|
||||||
let updatedItem: CommentItem<TypesPullReqActivity> | undefined = undefined
|
let updatedItem: CommentItem<TypesPullReqActivity> | undefined = undefined
|
||||||
@ -415,7 +426,9 @@ export function usePullReqComments({
|
|||||||
return [result, updatedItem]
|
return [result, updatedItem]
|
||||||
}}
|
}}
|
||||||
outlets={{
|
outlets={{
|
||||||
[CommentBoxOutletPosition.TOP]: <CommentThreadTopDecoration comment={comment} />,
|
[CommentBoxOutletPosition.TOP]: (
|
||||||
|
<CommentThreadTopDecoration startLine={comment.lineNumberStart} endLine={comment.lineNumberEnd} />
|
||||||
|
),
|
||||||
[CommentBoxOutletPosition.LEFT_OF_OPTIONS_MENU]: (
|
[CommentBoxOutletPosition.LEFT_OF_OPTIONS_MENU]: (
|
||||||
<CodeCommentStatusSelect
|
<CodeCommentStatusSelect
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
@ -445,6 +458,7 @@ export function usePullReqComments({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
diff,
|
||||||
comments,
|
comments,
|
||||||
contentRef,
|
contentRef,
|
||||||
viewStyle,
|
viewStyle,
|
||||||
@ -455,9 +469,6 @@ export function usePullReqComments({
|
|||||||
currentUser?.display_name,
|
currentUser?.display_name,
|
||||||
currentUser?.email,
|
currentUser?.email,
|
||||||
pullReqMetadata,
|
pullReqMetadata,
|
||||||
diff.isRename,
|
|
||||||
diff.oldName,
|
|
||||||
diff.filePath,
|
|
||||||
sourceRef,
|
sourceRef,
|
||||||
targetRef,
|
targetRef,
|
||||||
save,
|
save,
|
||||||
@ -921,15 +932,4 @@ function isDiffRendered(ref: React.RefObject<HTMLDivElement | null>) {
|
|||||||
return !!ref.current?.querySelector('[data]' || !!ref.current?.querySelector('.d2h-wrapper'))
|
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'
|
const selected = 'selected'
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Text,
|
Text,
|
||||||
Button,
|
Button,
|
||||||
@ -51,6 +51,7 @@ import { defaultUsefulOrNot } from 'components/DefaultUsefulOrNot/UsefulOrNot'
|
|||||||
import { AidaClient } from 'utils/types'
|
import { AidaClient } from 'utils/types'
|
||||||
import type { TypesRepository } from 'services/code'
|
import type { TypesRepository } from 'services/code'
|
||||||
import { useEventListener } from 'hooks/useEventListener'
|
import { useEventListener } from 'hooks/useEventListener'
|
||||||
|
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
|
||||||
import css from './MarkdownEditorWithPreview.module.scss'
|
import css from './MarkdownEditorWithPreview.module.scss'
|
||||||
|
|
||||||
enum MarkdownEditorTab {
|
enum MarkdownEditorTab {
|
||||||
@ -65,25 +66,17 @@ enum ToolbarAction {
|
|||||||
UPLOAD = 'UPLOAD',
|
UPLOAD = 'UPLOAD',
|
||||||
UNORDER_LIST = 'UNORDER_LIST',
|
UNORDER_LIST = 'UNORDER_LIST',
|
||||||
CHECK_LIST = 'CHECK_LIST',
|
CHECK_LIST = 'CHECK_LIST',
|
||||||
CODE_BLOCK = 'CODE_BLOCK'
|
CODE_BLOCK = 'CODE_BLOCK',
|
||||||
|
SUGGESTION = 'SUGGESTION'
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToolbarItem {
|
interface ToolbarItem {
|
||||||
icon: IconName
|
icon: IconName
|
||||||
action: ToolbarAction
|
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
|
// Define a unique effect to update decorations
|
||||||
const addDecorationEffect = StateEffect.define<{ decoration: Decoration; from: number; to: number }[]>()
|
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
|
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
|
repoMetadata: TypesRepository | undefined
|
||||||
standalone: boolean
|
standalone: boolean
|
||||||
routingId: string
|
routingId: string
|
||||||
|
suggestionBlock?: SuggestionBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MarkdownEditorWithPreview({
|
export function MarkdownEditorWithPreview({
|
||||||
@ -170,7 +164,8 @@ export function MarkdownEditorWithPreview({
|
|||||||
setFlag,
|
setFlag,
|
||||||
flag,
|
flag,
|
||||||
sourceGitRef,
|
sourceGitRef,
|
||||||
targetGitRef
|
targetGitRef,
|
||||||
|
suggestionBlock
|
||||||
}: MarkdownEditorWithPreviewProps) {
|
}: MarkdownEditorWithPreviewProps) {
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
@ -192,6 +187,22 @@ export function MarkdownEditorWithPreview({
|
|||||||
})
|
})
|
||||||
const isDirty = useRef(dirty)
|
const isDirty = useRef(dirty)
|
||||||
const [data, setData] = useState({})
|
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(
|
useEffect(
|
||||||
function setDirtyRef() {
|
function setDirtyRef() {
|
||||||
@ -200,7 +211,7 @@ export function MarkdownEditorWithPreview({
|
|||||||
[dirty]
|
[dirty]
|
||||||
)
|
)
|
||||||
|
|
||||||
const myKeymap = keymap.of([
|
const shortcuts = keymap.of([
|
||||||
{
|
{
|
||||||
key: 'Mod-z',
|
key: 'Mod-z',
|
||||||
run: undo,
|
run: undo,
|
||||||
@ -214,8 +225,19 @@ export function MarkdownEditorWithPreview({
|
|||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
preventDefault: true
|
preventDefault: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'Ctrl-g',
|
||||||
|
run: () => {
|
||||||
|
if (suggestionBlock) {
|
||||||
|
onToolbarAction(ToolbarAction.SUGGESTION)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
preventDefault: !!suggestionBlock
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
const handleMouseDown = useCallback(
|
const handleMouseDown = useCallback(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(event: any) => {
|
(event: any) => {
|
||||||
@ -289,128 +311,146 @@ export function MarkdownEditorWithPreview({
|
|||||||
}
|
}
|
||||||
}, [data]) // eslint-disable-line react-hooks/exhaustive-deps
|
}, [data]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const onToolbarAction = useCallback((action: ToolbarAction) => {
|
const onToolbarAction = useCallback(
|
||||||
const view = viewRef.current
|
(action: ToolbarAction) => {
|
||||||
|
const view = viewRef.current
|
||||||
|
|
||||||
if (!view?.state) {
|
if (!view?.state) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Part of this code is copied from @uiwjs/react-markdown-editor
|
// Note: Part of this code is copied from @uiwjs/react-markdown-editor
|
||||||
// MIT License, Copyright (c) 2020 uiw
|
// MIT License, Copyright (c) 2020 uiw
|
||||||
// @see https://github.dev/uiwjs/react-markdown-editor/blob/2d3f45079c79616b867ef03681a8ba9799169921/src/commands/header.tsx
|
// @see https://github.dev/uiwjs/react-markdown-editor/blob/2d3f45079c79616b867ef03681a8ba9799169921/src/commands/header.tsx
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case ToolbarAction.HEADER: {
|
case ToolbarAction.HEADER: {
|
||||||
const lineInfo = view.state.doc.lineAt(view.state.selection.main.from)
|
const lineInfo = view.state.doc.lineAt(view.state.selection.main.from)
|
||||||
let mark = '#'
|
let mark = '#'
|
||||||
const matchMark = lineInfo.text.match(/^#+/)
|
const matchMark = lineInfo.text.match(/^#+/)
|
||||||
if (matchMark && matchMark[0]) {
|
if (matchMark && matchMark[0]) {
|
||||||
const txt = matchMark[0]
|
const txt = matchMark[0]
|
||||||
if (txt.length < 6) {
|
if (txt.length < 6) {
|
||||||
mark = txt + '#'
|
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: {
|
case ToolbarAction.BOLD: {
|
||||||
setFile(undefined)
|
view.dispatch(
|
||||||
setOpen(true)
|
view.state.changeByRange(range => ({
|
||||||
break
|
changes: [
|
||||||
}
|
{ from: range.from, insert: '**' },
|
||||||
|
{ from: range.to, insert: '**' }
|
||||||
case ToolbarAction.BOLD: {
|
],
|
||||||
view.dispatch(
|
range: EditorSelection.range(range.from + 2, range.to + 2)
|
||||||
view.state.changeByRange(range => ({
|
}))
|
||||||
changes: [
|
)
|
||||||
{ from: range.from, insert: '**' },
|
break
|
||||||
{ 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 = ''
|
|
||||||
}
|
}
|
||||||
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: {
|
case ToolbarAction.ITALIC: {
|
||||||
const lineInfo = view.state.doc.lineAt(view.state.selection.main.from)
|
view.dispatch(
|
||||||
let mark = '- [ ] '
|
view.state.changeByRange(range => ({
|
||||||
const matchMark = lineInfo.text.match(/^-\s\[\s\]\s/)
|
changes: [
|
||||||
if (matchMark && matchMark[0]) {
|
{ from: range.from, insert: '*' },
|
||||||
mark = ''
|
{ 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: {
|
case ToolbarAction.UNORDER_LIST: {
|
||||||
const main = view.state.selection.main
|
const lineInfo = view.state.doc.lineAt(view.state.selection.main.from)
|
||||||
const txt = view.state.sliceDoc(view.state.selection.main.from, view.state.selection.main.to)
|
let mark = '- '
|
||||||
view.dispatch({
|
const matchMark = lineInfo.text.match(/^-/)
|
||||||
changes: {
|
if (matchMark && matchMark[0]) {
|
||||||
from: main.from,
|
mark = ''
|
||||||
to: main.to,
|
}
|
||||||
insert: `\`\`\`tsx\n${txt}\n\`\`\``
|
view.dispatch({
|
||||||
},
|
changes: {
|
||||||
selection: EditorSelection.range(main.from + 3, main.from + 6)
|
from: lineInfo.from,
|
||||||
})
|
to: lineInfo.to,
|
||||||
break
|
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(() => {
|
useEffect(() => {
|
||||||
setDirtyProp?.(dirty)
|
setDirtyProp?.(dirty)
|
||||||
@ -605,8 +645,9 @@ export function MarkdownEditorWithPreview({
|
|||||||
size={ButtonSize.SMALL}
|
size={ButtonSize.SMALL}
|
||||||
variation={ButtonVariation.ICON}
|
variation={ButtonVariation.ICON}
|
||||||
icon={item.icon}
|
icon={item.icon}
|
||||||
|
title={item.title}
|
||||||
withoutCurrentColor
|
withoutCurrentColor
|
||||||
iconProps={{ color: Color.PRIMARY_10, size: 14 }}
|
iconProps={{ color: Color.PRIMARY_10, size: item.size || 14 }}
|
||||||
onClick={() => onToolbarAction(item.action)}
|
onClick={() => onToolbarAction(item.action)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -614,7 +655,7 @@ export function MarkdownEditorWithPreview({
|
|||||||
</Container>
|
</Container>
|
||||||
<Container className={css.tabContent}>
|
<Container className={css.tabContent}>
|
||||||
<Editor
|
<Editor
|
||||||
extensions={[myKeymap, decorationField, history()]}
|
extensions={[shortcuts, decorationField, history()]}
|
||||||
routingId={routingId}
|
routingId={routingId}
|
||||||
standalone={standalone}
|
standalone={standalone}
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
@ -633,7 +674,11 @@ export function MarkdownEditorWithPreview({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{selectedTab === MarkdownEditorTab.PREVIEW && (
|
{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 && (
|
{!standalone && showFeedback && (
|
||||||
<Container
|
<Container
|
||||||
|
227
web/src/components/MarkdownViewer/CodeSuggestionBlock.tsx
Normal file
227
web/src/components/MarkdownViewer/CodeSuggestionBlock.tsx
Normal file
@ -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 */
|
/* eslint-disable */
|
||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
|
export declare const added: string
|
||||||
export declare const main: 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
|
export declare const withMaxHeight: string
|
||||||
|
@ -15,15 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
|
import { Container, Utils } from '@harnessio/uicore'
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { Container } from '@harnessio/uicore'
|
|
||||||
import { isEmpty } from 'lodash-es'
|
import { isEmpty } from 'lodash-es'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
|
import { getCodeString } from 'rehype-rewrite'
|
||||||
import MarkdownPreview from '@uiw/react-markdown-preview'
|
import MarkdownPreview from '@uiw/react-markdown-preview'
|
||||||
import rehypeVideo from 'rehype-video'
|
import rehypeVideo from 'rehype-video'
|
||||||
import rehypeExternalLinks, { Element } from 'rehype-external-links'
|
import rehypeExternalLinks, { Element } from 'rehype-external-links'
|
||||||
import { INITIAL_ZOOM_LEVEL, generateAlphaNumericHash } from 'utils/Utils'
|
import { INITIAL_ZOOM_LEVEL, generateAlphaNumericHash } from 'utils/Utils'
|
||||||
import ImageCarousel from 'components/ImageCarousel/ImageCarousel'
|
import ImageCarousel from 'components/ImageCarousel/ImageCarousel'
|
||||||
|
import type { SuggestionBlock } from 'components/SuggestionBlock/SuggestionBlock'
|
||||||
|
import { CodeSuggestionBlock } from './CodeSuggestionBlock'
|
||||||
import css from './MarkdownViewer.module.scss'
|
import css from './MarkdownViewer.module.scss'
|
||||||
|
|
||||||
interface MarkdownViewerProps {
|
interface MarkdownViewerProps {
|
||||||
@ -34,6 +37,8 @@ interface MarkdownViewerProps {
|
|||||||
darkMode?: boolean
|
darkMode?: boolean
|
||||||
handleDescUpdate?: (payload: string) => void
|
handleDescUpdate?: (payload: string) => void
|
||||||
setOriginalContent?: React.Dispatch<React.SetStateAction<string>>
|
setOriginalContent?: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
suggestionBlock?: SuggestionBlock
|
||||||
|
suggestionCheckSums?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MarkdownViewer({
|
export function MarkdownViewer({
|
||||||
@ -41,10 +46,11 @@ export function MarkdownViewer({
|
|||||||
className,
|
className,
|
||||||
maxHeight,
|
maxHeight,
|
||||||
darkMode,
|
darkMode,
|
||||||
|
|
||||||
setOriginalContent,
|
setOriginalContent,
|
||||||
handleDescUpdate,
|
handleDescUpdate,
|
||||||
inDescriptionBox = false
|
inDescriptionBox = false,
|
||||||
|
suggestionBlock,
|
||||||
|
suggestionCheckSums
|
||||||
}: MarkdownViewerProps) {
|
}: MarkdownViewerProps) {
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
const [isOpen, setIsOpen] = useState<boolean>(false)
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
@ -203,6 +209,32 @@ export function MarkdownViewer({
|
|||||||
[rehypeVideo, { test: /\/(.*)(.mp4|.mov|.webm|.mkv|.flv)$/, details: null }],
|
[rehypeVideo, { test: /\/(.*)(.mp4|.mov|.webm|.mkv|.flv)$/, details: null }],
|
||||||
[rehypeExternalLinks, { rel: ['nofollow noreferrer noopener'], target: '_blank' }]
|
[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
|
<ImageCarousel
|
||||||
isOpen={isOpen}
|
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>
|
||||||
|
)
|
||||||
|
}
|
19
web/src/components/SuggestionBlock/SuggestionBlock.tsx
Normal file
19
web/src/components/SuggestionBlock/SuggestionBlock.tsx
Normal file
@ -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
|
blame: string
|
||||||
blameCommitLine: string
|
blameCommitLine: string
|
||||||
blameEmpty: string
|
blameEmpty: string
|
||||||
|
bold: string
|
||||||
botAlerts: string
|
botAlerts: string
|
||||||
bottom: string
|
bottom: string
|
||||||
branch: string
|
branch: string
|
||||||
@ -140,6 +141,7 @@ export interface StringsMap {
|
|||||||
characterLimit: string
|
characterLimit: string
|
||||||
checkRuns: string
|
checkRuns: string
|
||||||
checkSuites: string
|
checkSuites: string
|
||||||
|
checklist: string
|
||||||
checks: string
|
checks: string
|
||||||
clear: string
|
clear: string
|
||||||
clickHereToDownload: string
|
clickHereToDownload: string
|
||||||
@ -148,6 +150,7 @@ export interface StringsMap {
|
|||||||
cloneText: string
|
cloneText: string
|
||||||
close: string
|
close: string
|
||||||
closed: string
|
closed: string
|
||||||
|
code: string
|
||||||
'codeOwner.approvalCompleted': string
|
'codeOwner.approvalCompleted': string
|
||||||
'codeOwner.changesRequested': string
|
'codeOwner.changesRequested': string
|
||||||
'codeOwner.title': string
|
'codeOwner.title': string
|
||||||
@ -356,6 +359,7 @@ export interface StringsMap {
|
|||||||
getMyCloneTitle: string
|
getMyCloneTitle: string
|
||||||
gitIgnore: string
|
gitIgnore: string
|
||||||
gitness: string
|
gitness: string
|
||||||
|
heading: string
|
||||||
help: string
|
help: string
|
||||||
hideCommitHistory: string
|
hideCommitHistory: string
|
||||||
history: string
|
history: string
|
||||||
@ -422,6 +426,7 @@ export interface StringsMap {
|
|||||||
inactiveBranches: string
|
inactiveBranches: string
|
||||||
invalidResponse: string
|
invalidResponse: string
|
||||||
isRequired: string
|
isRequired: string
|
||||||
|
italic: string
|
||||||
key: string
|
key: string
|
||||||
'keywordSearch.sampleQueries.searchForClass': string
|
'keywordSearch.sampleQueries.searchForClass': string
|
||||||
'keywordSearch.sampleQueries.searchForFilesWithCMD': string
|
'keywordSearch.sampleQueries.searchForFilesWithCMD': string
|
||||||
@ -598,6 +603,8 @@ export interface StringsMap {
|
|||||||
poweredByAI: string
|
poweredByAI: string
|
||||||
'pr.ableToMerge': string
|
'pr.ableToMerge': string
|
||||||
'pr.addDescription': string
|
'pr.addDescription': string
|
||||||
|
'pr.addSuggestion': string
|
||||||
|
'pr.applySuggestions': string
|
||||||
'pr.authorCommentedPR': string
|
'pr.authorCommentedPR': string
|
||||||
'pr.branchHasNoConflicts': string
|
'pr.branchHasNoConflicts': string
|
||||||
'pr.cantBeMerged': string
|
'pr.cantBeMerged': string
|
||||||
@ -607,6 +614,8 @@ export interface StringsMap {
|
|||||||
'pr.checksFailure': string
|
'pr.checksFailure': string
|
||||||
'pr.collapseFullFile': string
|
'pr.collapseFullFile': string
|
||||||
'pr.commentLineNumbers': string
|
'pr.commentLineNumbers': string
|
||||||
|
'pr.commitSuggestion': string
|
||||||
|
'pr.commitSuggestions': string
|
||||||
'pr.copyLinkToComment': string
|
'pr.copyLinkToComment': string
|
||||||
'pr.createDraftPR': string
|
'pr.createDraftPR': string
|
||||||
'pr.descHasTooLongLine': string
|
'pr.descHasTooLongLine': string
|
||||||
@ -657,6 +666,7 @@ export interface StringsMap {
|
|||||||
'pr.prStateChanged': string
|
'pr.prStateChanged': string
|
||||||
'pr.prStateChangedDraft': string
|
'pr.prStateChangedDraft': string
|
||||||
'pr.readyForReview': string
|
'pr.readyForReview': string
|
||||||
|
'pr.removeSuggestion': string
|
||||||
'pr.requestSubmitted': string
|
'pr.requestSubmitted': string
|
||||||
'pr.reviewChanges': string
|
'pr.reviewChanges': string
|
||||||
'pr.reviewSubmitted': string
|
'pr.reviewSubmitted': string
|
||||||
@ -666,6 +676,8 @@ export interface StringsMap {
|
|||||||
'pr.state': string
|
'pr.state': string
|
||||||
'pr.status': string
|
'pr.status': string
|
||||||
'pr.statusLine': string
|
'pr.statusLine': string
|
||||||
|
'pr.suggestedChange': string
|
||||||
|
'pr.suggestionApplied': string
|
||||||
'pr.titleChanged': string
|
'pr.titleChanged': string
|
||||||
'pr.titleChangedTable': string
|
'pr.titleChangedTable': string
|
||||||
'pr.titleIsRequired': string
|
'pr.titleIsRequired': string
|
||||||
@ -837,6 +849,7 @@ export interface StringsMap {
|
|||||||
'stepCategory.select': string
|
'stepCategory.select': string
|
||||||
submitReview: string
|
submitReview: string
|
||||||
success: string
|
success: string
|
||||||
|
suggestion: string
|
||||||
summary: string
|
summary: string
|
||||||
switchBranch: string
|
switchBranch: string
|
||||||
switchBranchesTags: string
|
switchBranchesTags: string
|
||||||
@ -867,6 +880,7 @@ export interface StringsMap {
|
|||||||
'triggers.newTrigger': string
|
'triggers.newTrigger': string
|
||||||
'triggers.updateSuccess': string
|
'triggers.updateSuccess': string
|
||||||
turnOnSemanticSearch: string
|
turnOnSemanticSearch: string
|
||||||
|
unorderedList: string
|
||||||
unrsolvedComment: string
|
unrsolvedComment: string
|
||||||
'unsavedChanges.leave': string
|
'unsavedChanges.leave': string
|
||||||
'unsavedChanges.message': string
|
'unsavedChanges.message': string
|
||||||
@ -877,6 +891,7 @@ export interface StringsMap {
|
|||||||
updateUser: string
|
updateUser: string
|
||||||
updateWebhook: string
|
updateWebhook: string
|
||||||
updated: string
|
updated: string
|
||||||
|
upload: string
|
||||||
uploadAFileError: string
|
uploadAFileError: string
|
||||||
user: string
|
user: string
|
||||||
userCreated: string
|
userCreated: string
|
||||||
|
@ -242,6 +242,13 @@ diff: Diff
|
|||||||
draft: Draft
|
draft: Draft
|
||||||
conversation: Conversation
|
conversation: Conversation
|
||||||
pr:
|
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}}
|
commentLineNumbers: Comment on line {{start}} to {{end}}
|
||||||
moreComments: '{num} {count|0:Show more,1:1 more comment,more comments}'
|
moreComments: '{num} {count|0:Show more,1:1 more comment,more comments}'
|
||||||
copyLinkToComment: Copy link to comment
|
copyLinkToComment: Copy link to comment
|
||||||
@ -287,8 +294,8 @@ pr:
|
|||||||
requestSubmitted: Request for changes submitted.
|
requestSubmitted: Request for changes submitted.
|
||||||
prReviewSubmit: '{user} {state|approved:approved, rejected:rejected,changereq:requested changes to, reviewed} this pull request. {time}'
|
prReviewSubmit: '{user} {state|approved:approved, rejected:rejected,changereq:requested changes to, reviewed} this pull request. {time}'
|
||||||
prMergedBannerInfo: '{user} merged branch {source} into {target} {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}'
|
prMergedInfo: '{user} 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}'
|
prRebasedInfo: '{user} rebased changes from branch {source} onto {target}, now at {mergeSha} {time}'
|
||||||
prBranchPushInfo: '{user} pushed a new commit {commit}'
|
prBranchPushInfo: '{user} pushed a new commit {commit}'
|
||||||
prBranchDeleteInfo: '{user} deleted the source branch with latest commit {commit}'
|
prBranchDeleteInfo: '{user} deleted the source branch with latest commit {commit}'
|
||||||
prStateChanged: '{user} changed pull request state from {old} to {new}.'
|
prStateChanged: '{user} changed pull request state from {old} to {new}.'
|
||||||
@ -1028,3 +1035,11 @@ securitySettings:
|
|||||||
detectDesc: passive vulnerability will report errors but not block
|
detectDesc: passive vulnerability will report errors but not block
|
||||||
block: Block
|
block: Block
|
||||||
blockDesc: active vulnerability blocks commit if any vulnerability is found
|
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 { Container, Layout, Text, FlexExpander, Button, ButtonVariation, ButtonSize } from '@harnessio/uicore'
|
||||||
import { Color, FontVariation } from '@harnessio/design-system'
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
import { LogViewer } from 'components/LogViewer/LogViewer'
|
import { LogViewer } from 'components/LogViewer/LogViewer'
|
||||||
import { PullRequestCheckType } from 'utils/Utils'
|
import { PullRequestCheckType, PullRequestSection } from 'utils/Utils'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { Split } from 'components/Split/Split'
|
import { Split } from 'components/Split/Split'
|
||||||
@ -176,7 +176,7 @@ export const Checks: React.FC<ChecksProps> = ({ repoMetadata, pullReqMetadata, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={css.main}>
|
<Container className={css.main} data-page-section={PullRequestSection.CHECKS}>
|
||||||
<Match expr={prChecksDecisionResult?.overallStatus}>
|
<Match expr={prChecksDecisionResult?.overallStatus}>
|
||||||
<Truthy>
|
<Truthy>
|
||||||
<Split split="vertical" size={400} minSize={300} maxSize={700} primary="first">
|
<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`,
|
`diff --git a/src b/dest`,
|
||||||
`new file mode 100644`,
|
`new file mode 100644`,
|
||||||
'index 0000000..0000000',
|
'index 0000000..0000000',
|
||||||
'--- a/src',
|
`--- a/src/${get(commentItems[0], 'payload.code_comment.path')}`,
|
||||||
'+++ b/dest',
|
`+++ b/dest/${get(commentItems[0], 'payload.code_comment.path')}`,
|
||||||
get(commentItems[0], 'payload.payload.title', ''),
|
get(commentItems[0], 'payload.payload.title', ''),
|
||||||
...get(commentItems[0], 'payload.payload.lines', [])
|
...get(commentItems[0], 'payload.payload.lines', [])
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
@ -28,15 +28,15 @@ import {
|
|||||||
} from '@harnessio/uicore'
|
} from '@harnessio/uicore'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { useGet, useMutate } from 'restful-react'
|
import { useGet, useMutate } from 'restful-react'
|
||||||
import { orderBy } from 'lodash-es'
|
import { get, orderBy } from 'lodash-es'
|
||||||
import type { GitInfoProps } from 'utils/GitUtils'
|
import type { GitInfoProps } from 'utils/GitUtils'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { useAppContext } from 'AppContext'
|
import { useAppContext } from 'AppContext'
|
||||||
import type { TypesPullReqActivity, TypesPullReq, TypesPullReqStats, TypesCodeOwnerEvaluation } from 'services/code'
|
import type { TypesPullReqActivity, TypesPullReq, TypesPullReqStats, TypesCodeOwnerEvaluation } from 'services/code'
|
||||||
import { CommentAction, CommentBox, CommentBoxOutletPosition, CommentItem } from 'components/CommentBox/CommentBox'
|
import { CommentAction, CommentBox, CommentBoxOutletPosition, CommentItem } from 'components/CommentBox/CommentBox'
|
||||||
import { useConfirmAct } from 'hooks/useConfirmAction'
|
import { useConfirmAct } from 'hooks/useConfirmAction'
|
||||||
import { getErrorMessage, orderSortDate, ButtonRoleProps, PullRequestSection } from 'utils/Utils'
|
import { getErrorMessage, orderSortDate, ButtonRoleProps, PullRequestSection, filenameToLanguage } from 'utils/Utils'
|
||||||
import { activityToCommentItem } from 'components/DiffViewer/DiffViewerUtils'
|
import { activitiesToDiffCommentItems, activityToCommentItem } from 'components/DiffViewer/DiffViewerUtils'
|
||||||
import { NavigationCheck } from 'components/NavigationCheck/NavigationCheck'
|
import { NavigationCheck } from 'components/NavigationCheck/NavigationCheck'
|
||||||
import { ThreadSection } from 'components/ThreadSection/ThreadSection'
|
import { ThreadSection } from 'components/ThreadSection/ThreadSection'
|
||||||
import { CodeCommentStatusSelect } from 'components/CodeCommentStatusSelect/CodeCommentStatusSelect'
|
import { CodeCommentStatusSelect } from 'components/CodeCommentStatusSelect/CodeCommentStatusSelect'
|
||||||
@ -44,6 +44,7 @@ import { CodeCommentStatusButton } from 'components/CodeCommentStatusButton/Code
|
|||||||
import { CodeCommentSecondarySaveButton } from 'components/CodeCommentSecondarySaveButton/CodeCommentSecondarySaveButton'
|
import { CodeCommentSecondarySaveButton } from 'components/CodeCommentSecondarySaveButton/CodeCommentSecondarySaveButton'
|
||||||
import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision'
|
import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision'
|
||||||
import { UserPreference, useUserPreference } from 'hooks/useUserPreference'
|
import { UserPreference, useUserPreference } from 'hooks/useUserPreference'
|
||||||
|
import { CommentThreadTopDecoration } from 'components/CommentThreadTopDecoration/CommentThreadTopDecoration'
|
||||||
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
||||||
import { DescriptionBox } from './DescriptionBox'
|
import { DescriptionBox } from './DescriptionBox'
|
||||||
import { PullRequestActionsBox } from './PullRequestActionsBox/PullRequestActionsBox'
|
import { PullRequestActionsBox } from './PullRequestActionsBox/PullRequestActionsBox'
|
||||||
@ -254,9 +255,24 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
}></ThreadSection>
|
}></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 (
|
return (
|
||||||
<ThreadSection
|
<ThreadSection
|
||||||
key={`comment-${threadId}`}
|
key={`comment-${threadId}-${activity?.created}-${activity?.edited}-${activity?.resolved}-${activity?.code_comment?.outdated}`}
|
||||||
onlyTitle={
|
onlyTitle={
|
||||||
activityBlocks[index + 1] !== undefined && isSystemComment(activityBlocks[index + 1]) ? true : false
|
activityBlocks[index + 1] !== undefined && isSystemComment(activityBlocks[index + 1]) ? true : false
|
||||||
}
|
}
|
||||||
@ -278,6 +294,7 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
enableReplyPlaceHolder={true}
|
enableReplyPlaceHolder={true}
|
||||||
autoFocusAndPosition={true}
|
autoFocusAndPosition={true}
|
||||||
copyLinkToComment={copyLinkToComment}
|
copyLinkToComment={copyLinkToComment}
|
||||||
|
suggestionBlock={suggestionBlock}
|
||||||
handleAction={async (action, value, commentItem) => {
|
handleAction={async (action, value, commentItem) => {
|
||||||
let result = true
|
let result = true
|
||||||
let updatedItem: CommentItem<TypesPullReqActivity> | undefined = undefined
|
let updatedItem: CommentItem<TypesPullReqActivity> | undefined = undefined
|
||||||
@ -328,12 +345,15 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
}}
|
}}
|
||||||
outlets={{
|
outlets={{
|
||||||
[CommentBoxOutletPosition.TOP_OF_FIRST_COMMENT]: isCodeComment(commentItems) && (
|
[CommentBoxOutletPosition.TOP_OF_FIRST_COMMENT]: isCodeComment(commentItems) && (
|
||||||
<CodeCommentHeader
|
<>
|
||||||
commentItems={commentItems}
|
<CommentThreadTopDecoration startLine={startLine} endLine={endLine} />
|
||||||
threadId={threadId}
|
<CodeCommentHeader
|
||||||
repoMetadata={repoMetadata}
|
commentItems={commentItems}
|
||||||
pullReqMetadata={pullReqMetadata}
|
threadId={threadId}
|
||||||
/>
|
repoMetadata={repoMetadata}
|
||||||
|
pullReqMetadata={pullReqMetadata}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
),
|
),
|
||||||
[CommentBoxOutletPosition.LEFT_OF_OPTIONS_MENU]: (
|
[CommentBoxOutletPosition.LEFT_OF_OPTIONS_MENU]: (
|
||||||
<CodeCommentStatusSelect
|
<CodeCommentStatusSelect
|
||||||
@ -363,11 +383,11 @@ export const Conversation: React.FC<ConversationProps> = ({
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[activityBlocks, currentUser, pullReqMetadata]
|
[activityBlocks, currentUser, pullReqMetadata, activities]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PullRequestTabContentWrapper>
|
<PullRequestTabContentWrapper section={PullRequestSection.CONVERSATION}>
|
||||||
<Container>
|
<Container>
|
||||||
<Layout.Vertical spacing="xlarge">
|
<Layout.Vertical spacing="xlarge">
|
||||||
<PullRequestActionsBox
|
<PullRequestActionsBox
|
||||||
|
@ -55,6 +55,7 @@ import {
|
|||||||
import { UserPreference, useUserPreference } from 'hooks/useUserPreference'
|
import { UserPreference, useUserPreference } from 'hooks/useUserPreference'
|
||||||
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
|
||||||
import RuleViolationAlertModal from 'components/RuleViolationAlertModal/RuleViolationAlertModal'
|
import RuleViolationAlertModal from 'components/RuleViolationAlertModal/RuleViolationAlertModal'
|
||||||
|
import { PullReqSuggestionsBatch } from 'components/PullReqSuggestionsBatch/PullReqSuggestionsBatch'
|
||||||
import css from './PullRequestActionsBox.module.scss'
|
import css from './PullRequestActionsBox.module.scss'
|
||||||
|
|
||||||
const codeOwnersNotFoundMessage = 'CODEOWNERS file not found'
|
const codeOwnersNotFoundMessage = 'CODEOWNERS file not found'
|
||||||
@ -330,6 +331,7 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
|
|||||||
<Render when={loading || loadingState}>
|
<Render when={loading || loadingState}>
|
||||||
<Icon name={CodeIcon.InputSpinner} size={16} margin={{ right: 'xsmall' }} />
|
<Icon name={CodeIcon.InputSpinner} size={16} margin={{ right: 'xsmall' }} />
|
||||||
</Render>
|
</Render>
|
||||||
|
<PullReqSuggestionsBatch />
|
||||||
<Match expr={isDraft}>
|
<Match expr={isDraft}>
|
||||||
<Truthy>
|
<Truthy>
|
||||||
<SplitButton
|
<SplitButton
|
||||||
|
@ -189,7 +189,7 @@ export default function PullRequest() {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
panel: (
|
panel: (
|
||||||
<Container className={css.changes}>
|
<Container className={css.changes} data-page-section={PullRequestSection.FILES_CHANGED}>
|
||||||
{!!repoMetadata && !!pullReqMetadata && !!pullReqStats && (
|
{!!repoMetadata && !!pullReqMetadata && !!pullReqStats && (
|
||||||
<Changes
|
<Changes
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
|
@ -19,6 +19,7 @@ import type { TypesListCommitResponse } from 'services/code'
|
|||||||
import type { GitInfoProps } from 'utils/GitUtils'
|
import type { GitInfoProps } from 'utils/GitUtils'
|
||||||
import { useStrings } from 'framework/strings'
|
import { useStrings } from 'framework/strings'
|
||||||
import { CommitsView } from 'components/CommitsView/CommitsView'
|
import { CommitsView } from 'components/CommitsView/CommitsView'
|
||||||
|
import { PullRequestSection } from 'utils/Utils'
|
||||||
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper'
|
||||||
|
|
||||||
interface PullRequestCommitsProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullReqMetadata'> {
|
interface PullRequestCommitsProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullReqMetadata'> {
|
||||||
@ -33,7 +34,7 @@ export const PullRequestCommits: React.FC<PullRequestCommitsProps> = ({
|
|||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PullRequestTabContentWrapper>
|
<PullRequestTabContentWrapper section={PullRequestSection.COMMITS}>
|
||||||
<CommitsView
|
<CommitsView
|
||||||
commits={pullReqCommits?.commits || []}
|
commits={pullReqCommits?.commits || []}
|
||||||
repoMetadata={repoMetadata}
|
repoMetadata={repoMetadata}
|
||||||
|
@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Container, PageError } from '@harnessio/uicore'
|
import { Container, PageError } from '@harnessio/uicore'
|
||||||
import { getErrorMessage } from 'utils/Utils'
|
import { PullRequestSection, getErrorMessage } from 'utils/Utils'
|
||||||
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
|
||||||
|
|
||||||
interface PullRequestTabContentWrapperProps {
|
interface PullRequestTabContentWrapperProps {
|
||||||
|
section: PullRequestSection
|
||||||
className?: string
|
className?: string
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
error?: Unknown
|
error?: Unknown
|
||||||
@ -27,6 +28,7 @@ interface PullRequestTabContentWrapperProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const PullRequestTabContentWrapper: React.FC<PullRequestTabContentWrapperProps> = ({
|
export const PullRequestTabContentWrapper: React.FC<PullRequestTabContentWrapperProps> = ({
|
||||||
|
section,
|
||||||
className,
|
className,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
@ -34,7 +36,7 @@ export const PullRequestTabContentWrapper: React.FC<PullRequestTabContentWrapper
|
|||||||
children
|
children
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Container className={className} padding="xlarge">
|
<Container className={className} padding="xlarge" data-page-section={section}>
|
||||||
<LoadingSpinner visible={loading} withBorder={true} />
|
<LoadingSpinner visible={loading} withBorder={true} />
|
||||||
{error && <PageError message={getErrorMessage(error)} onClick={onRetry} />}
|
{error && <PageError message={getErrorMessage(error)} onClick={onRetry} />}
|
||||||
{!error && children}
|
{!error && children}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import type { EnumPullReqReviewDecision, TypesPullReqActivity } from 'services/code'
|
import type { EnumPullReqReviewDecision, TypesPullReqActivity } from 'services/code'
|
||||||
import type { CommentItem } from 'components/CommentBox/CommentBox'
|
import type { CommentItem } from 'components/CommentBox/CommentBox'
|
||||||
import { CommentType } from 'components/DiffViewer/DiffViewerUtils'
|
import { CommentType } from 'components/DiffViewer/DiffViewerUtils'
|
||||||
|
import type { PullRequestSection } from 'utils/Utils'
|
||||||
|
|
||||||
export function isCodeComment(commentItems: CommentItem<TypesPullReqActivity>[]) {
|
export function isCodeComment(commentItems: CommentItem<TypesPullReqActivity>[]) {
|
||||||
return commentItems[0]?.payload?.type === CommentType.CODE_COMMENT
|
return commentItems[0]?.payload?.type === CommentType.CODE_COMMENT
|
||||||
@ -45,3 +46,12 @@ export const processReviewDecision = (
|
|||||||
review_decision === PullReqReviewDecision.approved && reviewedSHA !== sourceSHA
|
review_decision === PullReqReviewDecision.approved && reviewedSHA !== sourceSHA
|
||||||
? PullReqReviewDecision.outdated
|
? PullReqReviewDecision.outdated
|
||||||
: review_decision
|
: 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
|
return activities
|
||||||
}
|
}
|
||||||
|
|
||||||
const pullReqAtom = atom<TypesPullReq | undefined>(undefined)
|
export const pullReqAtom = atom<TypesPullReq | undefined>(undefined)
|
||||||
const pullReqStatsAtom = atom<TypesPullReqStats | 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)
|
const pullReqCommitsAtom = atom<TypesListCommitResponse | undefined>(undefined)
|
||||||
|
|
||||||
// Note: We just list COMMITS_LIMIT commits in PR page
|
// Note: We just list COMMITS_LIMIT commits in PR page
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": "./src",
|
"baseUrl": "./src",
|
||||||
"target": "es2018",
|
"target": "es2018",
|
||||||
"lib": ["dom"],
|
"lib": ["dom", "es2021"],
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
257
web/yarn.lock
257
web/yarn.lock
@ -2441,6 +2441,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/unist" "*"
|
"@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":
|
"@types/history@^4.7.11":
|
||||||
version "4.7.11"
|
version "4.7.11"
|
||||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
|
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
|
||||||
@ -2541,6 +2548,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/unist" "*"
|
"@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@*":
|
"@types/mime@*":
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
|
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"
|
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
||||||
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
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":
|
"@types/ws@^8.5.5":
|
||||||
version "8.5.5"
|
version "8.5.5"
|
||||||
resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb"
|
resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb"
|
||||||
@ -3143,6 +3162,11 @@
|
|||||||
remark-gfm "~3.0.1"
|
remark-gfm "~3.0.1"
|
||||||
unist-util-visit "^4.1.0"
|
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":
|
"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5":
|
||||||
version "1.11.5"
|
version "1.11.5"
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c"
|
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"
|
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
|
||||||
integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
|
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:
|
character-entities-legacy@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
|
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"
|
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
|
||||||
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
|
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:
|
diff-sequences@^26.6.2:
|
||||||
version "26.6.2"
|
version "26.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
|
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"
|
vfile-location "^4.0.0"
|
||||||
web-namespaces "^2.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:
|
hast-util-has-property@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz#8ec99c3e8f02626304ee438cdb9f0528b017e083"
|
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:
|
dependencies:
|
||||||
"@types/hast" "^2.0.0"
|
"@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:
|
hast-util-raw@^7.2.0:
|
||||||
version "7.2.3"
|
version "7.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-7.2.3.tgz#dcb5b22a22073436dbdc4aa09660a644f4991d99"
|
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"
|
web-namespaces "^2.0.0"
|
||||||
zwitch "^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:
|
hast-util-select@^5.0.5, hast-util-select@~5.0.1:
|
||||||
version "5.0.5"
|
version "5.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/hast-util-select/-/hast-util-select-5.0.5.tgz#be9ccb71d2278681ca024727f12abd4f93b3e9bc"
|
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"
|
unist-util-visit "^4.0.0"
|
||||||
zwitch "^2.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:
|
hast-util-to-parse5@^7.0.0:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz#c49391bf8f151973e0c9adcd116b561e8daf29f3"
|
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"
|
web-namespaces "^2.0.0"
|
||||||
zwitch "^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:
|
hast-util-to-string@^2.0.0:
|
||||||
version "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"
|
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"
|
resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz#0ec64e257e6fc216c7d14c8a1b74d27d650b4557"
|
||||||
integrity sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==
|
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:
|
hastscript@^7.0.0:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-7.2.0.tgz#0eafb7afb153d047077fa2a833dc9b7ec604d10b"
|
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"
|
property-information "^6.0.0"
|
||||||
space-separated-tokens "^2.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:
|
he@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
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"
|
resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f"
|
||||||
integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==
|
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:
|
html-webpack-plugin@^5.3.1:
|
||||||
version "5.5.1"
|
version "5.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.1.tgz#826838e31b427f5f7f30971f8d8fa2422dfa6763"
|
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-position "^4.0.0"
|
||||||
unist-util-visit "^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:
|
mdast-util-to-markdown@^1.0.0, mdast-util-to-markdown@^1.3.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz#c13343cb3fc98621911d33b5cd42e7d0731171c6"
|
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-symbol "^1.0.0"
|
||||||
micromark-util-types "^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:
|
micromark-util-chunked@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz#5b40d83f3d53b84c4c6bce30ed4257e9a4c79d06"
|
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"
|
resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz#2c1c22d3800870ad770ece5686ebca5920353383"
|
||||||
integrity sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==
|
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:
|
micromark-util-html-tag-name@^1.0.0:
|
||||||
version "1.1.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"
|
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-encode "^1.0.0"
|
||||||
micromark-util-symbol "^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:
|
micromark-util-subtokenize@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz#ff6f1af6ac836f8bfdbf9b02f40431760ad89105"
|
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"
|
resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz#b90344db62042ce454f351cf0bebcc0a6da4920e"
|
||||||
integrity sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==
|
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:
|
micromark-util-types@^1.0.0, micromark-util-types@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.0.2.tgz#f4220fdb319205812f99c40f8c87a9be83eded20"
|
resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-1.0.2.tgz#f4220fdb319205812f99c40f8c87a9be83eded20"
|
||||||
integrity sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==
|
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:
|
micromark@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/micromark/-/micromark-3.1.0.tgz#eeba0fe0ac1c9aaef675157b52c166f125e89f62"
|
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"
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||||
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
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:
|
parseurl@~1.3.2, parseurl@~1.3.3:
|
||||||
version "1.3.3"
|
version "1.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||||
@ -11617,6 +11801,14 @@ string_decoder@~1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
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:
|
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
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:
|
dependencies:
|
||||||
"@types/unist" "^2.0.0"
|
"@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:
|
unist-util-position@^4.0.0:
|
||||||
version "4.0.4"
|
version "4.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-4.0.4.tgz#93f6d8c7d6b373d9b825844645877c127455f037"
|
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:
|
dependencies:
|
||||||
"@types/unist" "^2.0.0"
|
"@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:
|
unist-util-stringify-position@^3.0.0:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d"
|
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:
|
dependencies:
|
||||||
"@types/unist" "^2.0.0"
|
"@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:
|
unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1:
|
||||||
version "5.1.3"
|
version "5.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb"
|
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"
|
"@types/unist" "^2.0.0"
|
||||||
unist-util-is "^5.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:
|
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"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz#125a42d1eb876283715a3cb5cceaa531828c72e2"
|
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-is "^5.0.0"
|
||||||
unist-util-visit-parents "^5.1.1"
|
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:
|
universalify@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
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"
|
"@types/unist" "^2.0.0"
|
||||||
vfile "^5.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:
|
vfile-message@^3.0.0:
|
||||||
version "3.1.4"
|
version "3.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.4.tgz#15a50816ae7d7c2d1fa87090a7f9f96612b59dea"
|
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"
|
"@types/unist" "^2.0.0"
|
||||||
unist-util-stringify-position "^3.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:
|
vfile@^5.0.0:
|
||||||
version "5.3.7"
|
version "5.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.7.tgz#de0677e6683e3380fafc46544cfe603118826ab7"
|
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"
|
unist-util-stringify-position "^3.0.0"
|
||||||
vfile-message "^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:
|
vscode-languageserver-textdocument@^1.0.0:
|
||||||
version "1.0.8"
|
version "1.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz#9eae94509cbd945ea44bca8dcfe4bb0c15bb3ac0"
|
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"
|
synchronous-promise "^2.0.13"
|
||||||
toposort "^2.0.2"
|
toposort "^2.0.2"
|
||||||
|
|
||||||
zwitch@^2.0.0:
|
zwitch@^2.0.0, zwitch@^2.0.4:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
|
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
|
||||||
integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==
|
integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==
|
||||||
|
Loading…
x
Reference in New Issue
Block a user