diff --git a/web/src/components/CommentBox/CommentBox.tsx b/web/src/components/CommentBox/CommentBox.tsx index c71e1ad4e..d283d61f1 100644 --- a/web/src/components/CommentBox/CommentBox.tsx +++ b/web/src/components/CommentBox/CommentBox.tsx @@ -14,7 +14,7 @@ import { MarkdownEditorWithPreview, MarkdownEditorWithPreviewResetProps } from 'components/MarkdownEditorWithPreview/MarkdownEditorWithPreview' -import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer' +import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer' import css from './CommentBox.module.scss' export interface CommentItem { @@ -331,7 +331,7 @@ const CommentsThread = ({ {getString('commentDeleted')} - + diff --git a/web/src/components/ImageCarousel/ImageCarousel.tsx b/web/src/components/ImageCarousel/ImageCarousel.tsx index 032d6e91e..a20549221 100644 --- a/web/src/components/ImageCarousel/ImageCarousel.tsx +++ b/web/src/components/ImageCarousel/ImageCarousel.tsx @@ -1,8 +1,7 @@ import React, { useState } from 'react' import { ButtonGroup, ButtonVariation, Button, Container, Dialog, Carousel } from '@harness/uicore' import { ZOOM_INC_DEC_LEVEL } from 'utils/Utils' - -import { useStrings } from 'framework/strings' +import type { UseStringsReturn } from 'framework/strings' import css from './ImageCarousel.module.scss' interface ImageCarouselProps { isOpen: boolean @@ -10,12 +9,11 @@ interface ImageCarouselProps { setZoomLevel: (value: number) => void zoomLevel: number imgEvent: string[] + getString: UseStringsReturn['getString'] } const ImageCarousel = (props: ImageCarouselProps) => { - const { getString } = useStrings() - - const { isOpen, setIsOpen, setZoomLevel, zoomLevel, imgEvent } = props + const { getString, isOpen, setIsOpen, setZoomLevel, zoomLevel, imgEvent } = props const [imgTitle, setImageTitle] = useState(imgEvent[0]) return ( summary { + padding-bottom: var(--spacing-xsmall); + + > svg { + margin: 0 var(--spacing-small); + } + } + } + } + } +} diff --git a/web/src/components/MarkdownViewer/MarkdownViewer.module.scss.d.ts b/web/src/components/MarkdownViewer/MarkdownViewer.module.scss.d.ts new file mode 100644 index 000000000..9e614bf2d --- /dev/null +++ b/web/src/components/MarkdownViewer/MarkdownViewer.module.scss.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable */ +// this is an auto-generated file +declare const styles: { + readonly main: string +} +export default styles diff --git a/web/src/components/MarkdownViewer/MarkdownViewer.tsx b/web/src/components/MarkdownViewer/MarkdownViewer.tsx new file mode 100644 index 000000000..d8c06624a --- /dev/null +++ b/web/src/components/MarkdownViewer/MarkdownViewer.tsx @@ -0,0 +1,87 @@ +import { useHistory } from 'react-router-dom' +import React, { useCallback, useState } from 'react' +import { Container } from '@harness/uicore' +import MarkdownEditor from '@uiw/react-markdown-editor' +import rehypeVideo from 'rehype-video' +import rehypeExternalLinks from 'rehype-external-links' +import { INITIAL_ZOOM_LEVEL } from 'utils/Utils' +import type { UseStringsReturn } from 'framework/strings' +import ImageCarousel from 'components/ImageCarousel/ImageCarousel' +import css from './MarkdownViewer.module.scss' + +interface MarkdownViewerProps { + source: string + getString: UseStringsReturn['getString'] +} + +export function MarkdownViewer({ source, getString }: MarkdownViewerProps) { + const [isOpen, setIsOpen] = useState(false) + const history = useHistory() + const [zoomLevel, setZoomLevel] = useState(INITIAL_ZOOM_LEVEL) + + const [imgEvent, setImageEvent] = useState([]) + + const interceptClickEventOnViewerContainer = useCallback( + event => { + const { target } = event + + const imageArray = source.split('\n').filter(string => string.includes('![image]')) + const imageStringArray = imageArray.map(string => { + const imageSrc = string.split('![image]')[1] + return imageSrc.slice(1, imageSrc.length - 1) + }) + setImageEvent(imageStringArray) + if (target?.tagName?.toLowerCase() === 'a') { + const { href } = target + + // Intercept click event on internal links and navigate to pages to avoid full page reload + if (href && !href.startsWith('#')) { + try { + const url = new URL(href) + + if (url.origin === window.location.origin) { + event.stopPropagation() + event.preventDefault() + history.push(url.pathname) + } + } catch (e) { + // eslint-disable-next-line no-console + console.error('Error: MarkdownViewer/interceptClickEventOnViewerContainer', e) + } + } + } else if (event.target.nodeName?.toLowerCase() === 'img') { + setIsOpen(true) + } + }, + [history, source] + ) + + return ( + + { + if ((node as unknown as HTMLDivElement).tagName === 'a') { + if (parent && /^h(1|2|3|4|5|6)/.test((parent as unknown as HTMLDivElement).tagName)) { + parent.children = parent.children.slice(1) + } + } + }} + rehypePlugins={[ + rehypeVideo, + [rehypeExternalLinks, { rel: ['nofollow noreferrer noopener'], target: '_blank' }] + ]} + /> + + + ) +} diff --git a/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss index cd31d6b43..4626c582e 100644 --- a/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss +++ b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss @@ -7,9 +7,14 @@ } .wrapper { - padding: 0 0 0 var(--spacing-small) !important; + padding-left: var(--spacing-small) !important; margin-bottom: 0 !important; + &.spinnerOnRight { + padding-left: 0 !important; + padding-right: var(--spacing-small) !important; + } + .input { span[data-icon], span[icon] { diff --git a/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss.d.ts b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss.d.ts index c891473a1..031dcf0db 100644 --- a/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss.d.ts +++ b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.module.scss.d.ts @@ -4,6 +4,7 @@ declare const styles: { readonly main: string readonly layout: string readonly wrapper: string + readonly spinnerOnRight: string readonly input: string } export default styles diff --git a/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.tsx b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.tsx index 74be4b25a..027f3b07c 100644 --- a/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.tsx +++ b/web/src/components/SearchInputWithSpinner/SearchInputWithSpinner.tsx @@ -1,4 +1,6 @@ import React from 'react' +import { Render } from 'react-jsx-match' +import cx from 'classnames' import { Color, Container, Icon, IconName, Layout, TextInput } from '@harness/uicore' import { useStrings } from 'framework/strings' import css from './SearchInputWithSpinner.module.scss' @@ -11,6 +13,7 @@ interface SearchInputWithSpinnerProps { placeholder?: string icon?: IconName spinnerIcon?: IconName + spinnerPosition?: 'left' | 'right' } export const SearchInputWithSpinner: React.FC = ({ @@ -20,16 +23,20 @@ export const SearchInputWithSpinner: React.FC = ({ width = 250, placeholder, icon = 'search', - spinnerIcon = 'spinner' + spinnerIcon = 'spinner', + spinnerPosition = 'left' }) => { const { getString } = useStrings() + const spinner = + const spinnerOnRight = spinnerPosition === 'right' + return ( - {loading && } + {spinner} = ({ onFocus={event => event.target.select()} onInput={event => setQuery(event.currentTarget.value || '')} /> + {spinner} ) diff --git a/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx b/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx index 90a2b10bb..ff7196abf 100644 --- a/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx +++ b/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx @@ -1,93 +1,6 @@ -import { useHistory } from 'react-router-dom' -import React, { Suspense, useCallback, useState } from 'react' -import { Container, Text } from '@harness/uicore' -import MarkdownEditor from '@uiw/react-markdown-editor' -import rehypeVideo from 'rehype-video' -import rehypeExternalLinks from 'rehype-external-links' -import { useStrings } from 'framework/strings' +import React from 'react' import { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor' -import { INITIAL_ZOOM_LEVEL, SourceCodeEditorProps } from 'utils/Utils' - -import ImageCarousel from 'components/ImageCarousel/ImageCarousel' -import css from './SourceCodeViewer.module.scss' - -interface MarkdownViewerProps { - source: string -} - -export function MarkdownViewer({ source }: MarkdownViewerProps) { - const { getString } = useStrings() - const [isOpen, setIsOpen] = useState(false) - const history = useHistory() - const [zoomLevel, setZoomLevel] = useState(INITIAL_ZOOM_LEVEL) - - const [imgEvent, setImageEvent] = useState([]) - - const interceptClickEventOnViewerContainer = useCallback( - event => { - const { target } = event - - const imageArray = source.split('\n').filter(string => string.includes('![image]')) - const imageStringArray = imageArray.map(string => { - const imageSrc = string.split('![image]')[1] - return imageSrc.slice(1, imageSrc.length - 1) - }) - setImageEvent(imageStringArray) - if (target?.tagName?.toLowerCase() === 'a') { - const { href } = target - - // Intercept click event on internal links and navigate to pages to avoid full page reload - if (href && !href.startsWith('#')) { - try { - const url = new URL(href) - - if (url.origin === window.location.origin) { - event.stopPropagation() - event.preventDefault() - history.push(url.pathname) - } - } catch (e) { - // eslint-disable-next-line no-console - console.error('Error: MarkdownViewer/interceptClickEventOnViewerContainer', e) - } - } - } else if (event.target.nodeName?.toLowerCase() === 'img') { - setIsOpen(true) - } - }, - [history, source] - ) - - return ( - - {getString('loading')}}> - { - if ((node as unknown as HTMLDivElement).tagName === 'a') { - if (parent && /^h(1|2|3|4|5|6)/.test((parent as unknown as HTMLDivElement).tagName)) { - parent.children = parent.children.slice(1) - } - } - }} - rehypePlugins={[ - rehypeVideo, - [rehypeExternalLinks, { rel: ['nofollow noreferrer noopener'], target: '_blank' }] - ]} - /> - - - - ) -} +import type { SourceCodeEditorProps } from 'utils/Utils' type SourceCodeViewerProps = Omit diff --git a/web/src/pages/PullRequest/Conversation/Conversation.tsx b/web/src/pages/PullRequest/Conversation/Conversation.tsx index cbd15a3ab..82f51000e 100644 --- a/web/src/pages/PullRequest/Conversation/Conversation.tsx +++ b/web/src/pages/PullRequest/Conversation/Conversation.tsx @@ -23,7 +23,7 @@ import ReactTimeago from 'react-timeago' import { orderBy } from 'lodash-es' import { Render } from 'react-jsx-match' import { CodeIcon, GitInfoProps } from 'utils/GitUtils' -import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer' +import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer' import { useStrings } from 'framework/strings' import { useAppContext } from 'AppContext' import type { TypesPullReqActivity } from 'services/code' @@ -680,6 +680,7 @@ const SystemBox: React.FC = ({ pullRequestMetadata, commentItems margin={{ top: 'medium', left: 'xxxlarge' }} style={{ maxWidth: 'calc(100vw - 450px)', overflow: 'auto' }}> = ({ /> )) || ( - +