diff --git a/web/docs/markdown.md b/web/docs/markdown.md new file mode 100644 index 000000000..8e231ad19 --- /dev/null +++ b/web/docs/markdown.md @@ -0,0 +1,5 @@ +# Markdown Examples + +## Embed a video with a title + +https://user-images.githubusercontent.com/1680273/138299599-88547edd-859c-44c9-8b52-2cc06f7f2dd3.mov?!#title=Example%20Display diff --git a/web/package.json b/web/package.json index a59109d5f..566c8836d 100644 --- a/web/package.json +++ b/web/package.json @@ -1,21 +1,21 @@ { - "name": "ui-template", - "description": "Harness Inc", + "name": "codeui", + "description": "Harness Code UI", "version": "0.0.1", "author": "Harness Inc", "license": "Harness Inc", "private": true, - "homepage": "http://harness.io/", + "homepage": "http://app.harness.io/", "repository": { "type": "git", - "url": "https://github.com/wings-software/ui-template.git" + "url": "https://github.com/harness/gitness.git" }, "bugs": { - "url": "https://github.com/wings-software/ui-template/issues" + "url": "https://github.com/harness/gitness/issues" }, "keywords": [], "scripts": { - "dev": "NODE_ENV=development webpack serve --config config/webpack.dev.js", + "dev": "webpack serve --config config/webpack.dev.js", "test": "jest src --silent", "test:watch": "jest --watch", "lint": "eslint --rulesdir ./scripts/eslint-rules --ext .ts --ext .tsx src", @@ -80,9 +80,11 @@ "react-split-pane": "^0.1.92", "react-table": "^7.1.0", "react-timeago": "^4.4.0", + "rehype-external-links": "^2.0.1", "rehype-highlight": "^5.0.2", "rehype-raw": "^6.1.1", "rehype-sanitize": "^5.0.1", + "rehype-video": "^1.2.2", "remark-gfm": "^3.0.1", "restful-react": "15.6.0", "webpack-retry-chunk-load-plugin": "^3.1.0", diff --git a/web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss b/web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss index 9f51b1099..a4b6caf10 100644 --- a/web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss +++ b/web/src/components/SourceCodeViewer/SourceCodeViewer.module.scss @@ -4,6 +4,19 @@ pre { position: relative; } + + // Customize https://wangchujiang.com/rehype-video/ + details.octicon.octicon-video { + display: block; + + > summary { + padding-bottom: var(--spacing-xsmall); + + > svg { + margin: 0 var(--spacing-small); + } + } + } } } } diff --git a/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx b/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx index 7d48da0de..df9b51339 100644 --- a/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx +++ b/web/src/components/SourceCodeViewer/SourceCodeViewer.tsx @@ -1,6 +1,8 @@ -import React, { Suspense } from 'react' +import React, { Suspense, useCallback } 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 { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor' import type { SourceCodeEditorProps } from 'utils/Utils' @@ -8,27 +10,57 @@ import css from './SourceCodeViewer.module.scss' interface MarkdownViewerProps { source: string + navigateTo: (url: string) => void } -export function MarkdownViewer({ source }: MarkdownViewerProps) { +export function MarkdownViewer({ source, navigateTo }: MarkdownViewerProps) { const { getString } = useStrings() + const interceptClickEventOnViewerContainer = useCallback( + event => { + const { target } = event + + 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 origin = new URL(href).origin + + if (origin === window.location.origin) { + // For some reason, history.push(href) does not work in the context of @uiw/react-markdown-editor library. + navigateTo?.(href) + event.stopPropagation() + event.preventDefault() + } + } catch (e) { + // eslint-disable-next-line no-console + console.error('MarkdownViewer/interceptClickEventOnViewerContainer', e) + } + } + } + }, + [navigateTo] + ) return ( - + {getString('loading')}}> { - if ( - (node as unknown as HTMLDivElement).tagName === 'a' && - parent && - /^h(1|2|3|4|5|6)/.test((parent as unknown as HTMLDivElement).tagName) - ) { - parent.children = parent.children.slice(1) + 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/global.d.ts b/web/src/global.d.ts index 3ef87fcc5..b6d006236 100644 --- a/web/src/global.d.ts +++ b/web/src/global.d.ts @@ -47,10 +47,11 @@ declare interface Window { harnessNameSpace: string bugsnagClient?: any STRIP_CODE_PREFIX?: boolean - __ENABLE_CDN__: string - __webpack_public_path__: string } +declare const __ENABLE_CDN__: boolean +declare let __webpack_public_path__: string + declare const monaco: any declare module '*.scss' diff --git a/web/src/pages/PullRequest/Conversation/Conversation.tsx b/web/src/pages/PullRequest/Conversation/Conversation.tsx index c2a648508..d06720031 100644 --- a/web/src/pages/PullRequest/Conversation/Conversation.tsx +++ b/web/src/pages/PullRequest/Conversation/Conversation.tsx @@ -21,6 +21,7 @@ import cx from 'classnames' import { useGet, useMutate } from 'restful-react' import ReactTimeago from 'react-timeago' import { orderBy } from 'lodash-es' +import { useHistory } from 'react-router-dom' import { Render } from 'react-jsx-match' import { CodeIcon, GitInfoProps } from 'utils/GitUtils' import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer' @@ -35,12 +36,12 @@ import { CommentType, PullRequestCodeCommentPayload } from 'components/DiffViewer/DiffViewerUtils' +import { ThreadSection } from 'components/ThreadSection/ThreadSection' import { PullRequestTabContentWrapper } from '../PullRequestTabContentWrapper' import { DescriptionBox } from './DescriptionBox' import { PullRequestActionsBox } from './PullRequestActionsBox/PullRequestActionsBox' import PullRequestSideBar from './PullRequestSideBar/PullRequestSideBar' import css from './Conversation.module.scss' -import { ThreadSection } from 'components/ThreadSection/ThreadSection' export interface ConversationProps extends Pick { onCommentUpdate: () => void @@ -529,6 +530,7 @@ const generateReviewDecisionIcon = ( } const SystemBox: React.FC = ({ pullRequestMetadata, commentItems }) => { + const history = useHistory() const { getString } = useStrings() const payload = commentItems[0].payload const type = payload?.type @@ -692,6 +694,7 @@ const SystemBox: React.FC = ({ pullRequestMetadata, commentItems ) ) .join('\n')} + navigateTo={history.push} /> diff --git a/web/src/pages/PullRequest/Conversation/DescriptionBox.tsx b/web/src/pages/PullRequest/Conversation/DescriptionBox.tsx index e7dff5a2e..1c506ce06 100644 --- a/web/src/pages/PullRequest/Conversation/DescriptionBox.tsx +++ b/web/src/pages/PullRequest/Conversation/DescriptionBox.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react' import { Container, useToaster } from '@harness/uicore' import cx from 'classnames' +import { useHistory } from 'react-router-dom' import { useMutate } from 'restful-react' import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer' import { useStrings } from 'framework/strings' @@ -16,6 +17,7 @@ export const DescriptionBox: React.FC = ({ pullRequestMetadata, onCommentUpdate: refreshPullRequestMetadata }) => { + const history = useHistory() const [edit, setEdit] = useState(false) // const [updated, setUpdated] = useState(pullRequestMetadata.edited as number) const [originalContent, setOriginalContent] = useState(pullRequestMetadata.description as string) @@ -63,7 +65,7 @@ export const DescriptionBox: React.FC = ({ /> )) || ( - + { const history = useHistory() - const { routes } = useAppContext() const { getString } = useStrings() const { currentUserProfileURL } = useAppContext() @@ -140,7 +139,7 @@ const EmptyRepositoryInfo: React.FC {getString('emptyRepoHeader')} - +