Handle markdown links and videos (#357)

This commit is contained in:
Tan Nhu 2023-02-27 11:15:57 -08:00 committed by GitHub
parent a591567683
commit a96ed98e5d
10 changed files with 110 additions and 23 deletions

5
web/docs/markdown.md Normal file
View File

@ -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

View File

@ -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",

View File

@ -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);
}
}
}
}
}
}

View File

@ -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 (
<Container className={css.main}>
<Container className={css.main} onClick={interceptClickEventOnViewerContainer}>
<Suspense fallback={<Text>{getString('loading')}</Text>}>
<MarkdownEditor.Markdown
source={source}
skipHtml={true}
warpperElement={{ 'data-color-mode': 'light' }}
rehypeRewrite={(node, _index, parent) => {
if (
(node as unknown as HTMLDivElement).tagName === 'a' &&
parent &&
/^h(1|2|3|4|5|6)/.test((parent as unknown as HTMLDivElement).tagName)
) {
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' }]
]}
/>
</Suspense>
</Container>

5
web/src/global.d.ts vendored
View File

@ -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'

View File

@ -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<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
onCommentUpdate: () => void
@ -529,6 +530,7 @@ const generateReviewDecisionIcon = (
}
const SystemBox: React.FC<SystemBoxProps> = ({ 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<SystemBoxProps> = ({ pullRequestMetadata, commentItems
)
)
.join('\n')}
navigateTo={history.push}
/>
</Container>
</Render>

View File

@ -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<ConversationProps> = ({
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<ConversationProps> = ({
/>
)) || (
<Container className={css.mdWrapper}>
<MarkdownViewer source={content} />
<MarkdownViewer source={content} navigateTo={history.push} />
<Container className={css.menuWrapper}>
<OptionsMenuButton
isDark={true}

View File

@ -117,7 +117,6 @@ const EmptyRepositoryInfo: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourc
resourceContent
) => {
const history = useHistory()
const { routes } = useAppContext()
const { getString } = useStrings()
const { currentUserProfileURL } = useAppContext()
@ -140,7 +139,7 @@ const EmptyRepositoryInfo: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourc
padding={{ top: 'xxlarge', bottom: 'xxlarge', left: 'xxlarge', right: 'xxlarge' }}
className={css.divContainer}>
<Text font={{ variation: FontVariation.H5 }}>{getString('emptyRepoHeader')}</Text>
<Layout.Horizontal padding={{ top: 'large', }}>
<Layout.Horizontal padding={{ top: 'large' }}>
<Button
variation={ButtonVariation.PRIMARY}
text={getString('addNewFile')}
@ -190,6 +189,7 @@ const EmptyRepositoryInfo: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourc
source={getString('repoEmptyMarkdownClone')
.replace(/REPO_URL/g, repoMetadata.git_url || '')
.replace(/REPO_NAME/g, repoMetadata.uid || '')}
navigateTo={history.push}
/>
</Container>
<Container
@ -201,6 +201,7 @@ const EmptyRepositoryInfo: React.FC<Pick<GitInfoProps, 'repoMetadata' | 'resourc
.replace(/REPO_URL/g, '...')
.replace(/REPO_NAME/g, repoMetadata.uid || '')
.replace(/CREATE_API_TOKEN_URL/g, currentUserProfileURL || '')}
navigateTo={history.push}
/>
</Container>
</Container>

View File

@ -61,7 +61,10 @@ function ReadmeViewer({ metadata, gitRef, readmeInfo, contentOnly, maxWidth }: F
<Render when={(data?.content as RepoFileContent)?.data}>
<Container className={css.readmeContent}>
<MarkdownViewer source={window.atob((data?.content as RepoFileContent)?.data || '')} />
<MarkdownViewer
source={window.atob((data?.content as RepoFileContent)?.data || '')}
navigateTo={history.push}
/>
</Container>
</Render>
</Container>

View File

@ -6262,7 +6262,7 @@ highlight.js@11.6.0, highlight.js@~11.6.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.6.0.tgz#a50e9da05763f1bb0c1322c8f4f755242cff3f5a"
integrity sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==
history@^4.9.0:
history@^4.10.1, history@^4.9.0:
version "4.10.1"
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
@ -6722,6 +6722,11 @@ ipaddr.js@^2.0.1:
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
is-absolute-url@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-4.0.1.tgz#16e4d487d4fded05cfe0685e53ec86804a5e94dc"
integrity sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==
is-accessor-descriptor@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
@ -10905,6 +10910,18 @@ rehype-autolink-headings@~6.1.1:
unified "^10.0.0"
unist-util-visit "^4.0.0"
rehype-external-links@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/rehype-external-links/-/rehype-external-links-2.0.1.tgz#fe54f9f227a1a2f8f6afe442ac4c9ee748f08756"
integrity sha512-u2dNypma+ps12SJWlS23zvbqwNx0Hl24t0YHXSM/6FCZj/pqWETCO3WyyrvALv4JYvRtuPjhiv2Lpen15ESqbA==
dependencies:
"@types/hast" "^2.0.0"
extend "^3.0.0"
is-absolute-url "^4.0.0"
space-separated-tokens "^2.0.0"
unified "^10.0.0"
unist-util-visit "^4.0.0"
rehype-highlight@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/rehype-highlight/-/rehype-highlight-5.0.2.tgz#de952123cd4d9672f21a4a38d3b119b88a08eafa"
@ -10987,6 +11004,14 @@ rehype-slug@~5.1.0:
unified "^10.0.0"
unist-util-visit "^4.0.0"
rehype-video@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/rehype-video/-/rehype-video-1.2.2.tgz#a1dbd62c487e22b7282e9a23fa0b57f1f745ade9"
integrity sha512-p18JzIZiFSUqk21RHeJTU/7/7n9Moy0aQzFzbujVWb4vPrRZHQAN0ZfzTV8J32yDsZE5d6JDGWOzmMdvJrHq0w==
dependencies:
unified "~10.1.1"
unist-util-visit "~4.1.0"
relateurl@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"