mirror of
https://github.com/harness/drone.git
synced 2025-05-04 11:11:35 +08:00
Handle markdown links and videos (#357)
This commit is contained in:
parent
a591567683
commit
a96ed98e5d
5
web/docs/markdown.md
Normal file
5
web/docs/markdown.md
Normal 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
|
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
) {
|
||||
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' }]
|
||||
]}
|
||||
/>
|
||||
</Suspense>
|
||||
</Container>
|
||||
|
5
web/src/global.d.ts
vendored
5
web/src/global.d.ts
vendored
@ -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'
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user