feat: [CODE-25]: Handle relative links to repository files inside a README

This commit is contained in:
“tan-nhu” 2023-05-02 13:32:53 -07:00
parent 67048fb2c8
commit a1818991aa
4 changed files with 58 additions and 11 deletions

View File

@ -1,5 +1,5 @@
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
import React, { useCallback, useState } from 'react' import React, { useCallback, useMemo, useState } from 'react'
import { Container } from '@harness/uicore' import { Container } from '@harness/uicore'
import cx from 'classnames' import cx from 'classnames'
import MarkdownPreview from '@uiw/react-markdown-preview' import MarkdownPreview from '@uiw/react-markdown-preview'
@ -19,9 +19,8 @@ export function MarkdownViewer({ source, className, maxHeight }: MarkdownViewerP
const [isOpen, setIsOpen] = useState<boolean>(false) const [isOpen, setIsOpen] = useState<boolean>(false)
const history = useHistory() const history = useHistory()
const [zoomLevel, setZoomLevel] = useState(INITIAL_ZOOM_LEVEL) const [zoomLevel, setZoomLevel] = useState(INITIAL_ZOOM_LEVEL)
const [imgEvent, setImageEvent] = useState<string[]>([]) const [imgEvent, setImageEvent] = useState<string[]>([])
const refRootHref = useMemo(() => document.getElementById('repository-ref-root')?.getAttribute('href'), [])
const interceptClickEventOnViewerContainer = useCallback( const interceptClickEventOnViewerContainer = useCallback(
event => { event => {
const { target } = event const { target } = event
@ -31,19 +30,26 @@ export function MarkdownViewer({ source, className, maxHeight }: MarkdownViewerP
const imageSrc = string.split('![image]')[1] const imageSrc = string.split('![image]')[1]
return imageSrc.slice(1, imageSrc.length - 1) return imageSrc.slice(1, imageSrc.length - 1)
}) })
setImageEvent(imageStringArray) setImageEvent(imageStringArray)
if (target?.tagName?.toLowerCase() === 'a') { if (target?.tagName?.toLowerCase() === 'a') {
const { href } = target const href = target.getAttribute('href')
// Intercept click event on internal links and navigate to pages to avoid full page reload // Intercept click event on internal links and navigate to pages to avoid full page reload
if (href && !href.startsWith('#')) { if (href) {
try { try {
const url = new URL(href) const url = new URL(target.href)
if (url.origin === window.location.origin) { if (url.origin === window.location.origin) {
event.stopPropagation() event.stopPropagation()
event.preventDefault() event.preventDefault()
history.push(url.pathname)
if (href.startsWith('#')) {
document.getElementById(href.slice(1))?.scrollIntoView()
} else {
history.push(url.pathname)
}
} }
} catch (e) { } catch (e) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -71,6 +77,43 @@ export function MarkdownViewer({ source, className, maxHeight }: MarkdownViewerP
if (parent && /^h(1|2|3|4|5|6)/.test((parent as unknown as HTMLDivElement).tagName)) { if (parent && /^h(1|2|3|4|5|6)/.test((parent as unknown as HTMLDivElement).tagName)) {
parent.children = parent.children.slice(1) parent.children = parent.children.slice(1)
} }
// Rewrite a.href to point to the correct location for relative links to files inside repository.
// Relative links are defined as links that do not start with /, #, https:, http:, mailto:,
// tel:, data:, javascript:, sms:, or http(s):
if (refRootHref) {
const { properties } = node as unknown as { properties: { href: string } }
let href: string = properties.href
if (
href &&
!href.startsWith('/') &&
!href.startsWith('#') &&
!href.startsWith('https:') &&
!href.startsWith('http:') &&
!href.startsWith('mailto:') &&
!href.startsWith('tel:') &&
!href.startsWith('data:') &&
!href.startsWith('javascript:') &&
!href.startsWith('sms:') &&
!/^http(s)?:/.test(href)
) {
try {
// Some relative links are prefixed by `./`, normalize them
if (href.startsWith('./')) {
href = properties.href = properties.href.replace('./', '')
}
// Test if the link is relative to the current page.
// If true, rewrite it to point to the correct location
if (new URL(window.location.href + '/' + href).origin === window.location.origin) {
properties.href = (refRootHref + '/~/' + href).replace(/^\/ng\//, '/')
}
} catch (_exception) {
// eslint-disable-line no-empty
}
}
}
} }
}} }}
rehypePlugins={[ rehypePlugins={[

View File

@ -68,7 +68,9 @@ export function ContentHeader({
/> />
<Container> <Container>
<Layout.Horizontal spacing="small"> <Layout.Horizontal spacing="small">
<Link to={routes.toCODERepository({ repoPath: repoMetadata.path as string, gitRef })}> <Link
id="repository-ref-root"
to={routes.toCODERepository({ repoPath: repoMetadata.path as string, gitRef })}>
<Icon name={CodeIcon.Folder} /> <Icon name={CodeIcon.Folder} />
</Link> </Link>
<Text color={Color.GREY_900}>/</Text> <Text color={Color.GREY_900}>/</Text>

View File

@ -8,8 +8,6 @@
.heading { .heading {
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);
// border-top: 1px solid var(--grey-200);
// border-bottom: 1px solid var(--grey-200);
align-items: center; align-items: center;
padding-left: var(--spacing-large) !important; padding-left: var(--spacing-large) !important;
background-color: var(--grey-100); background-color: var(--grey-100);

View File

@ -1,4 +1,4 @@
import React from 'react' import React, { useEffect } from 'react'
import { Container } from '@harness/uicore' import { Container } from '@harness/uicore'
import { GitInfoProps, isDir } from 'utils/GitUtils' import { GitInfoProps, isDir } from 'utils/GitUtils'
import { ContentHeader } from './ContentHeader/ContentHeader' import { ContentHeader } from './ContentHeader/ContentHeader'
@ -12,6 +12,10 @@ export function RepositoryContent({
resourcePath, resourcePath,
resourceContent resourceContent
}: Pick<GitInfoProps, 'repoMetadata' | 'gitRef' | 'resourcePath' | 'resourceContent'>) { }: Pick<GitInfoProps, 'repoMetadata' | 'gitRef' | 'resourcePath' | 'resourceContent'>) {
useEffect(() => {
window.scroll({ top: 0 })
}, [gitRef, resourcePath])
return ( return (
<Container className={css.resourceContent}> <Container className={css.resourceContent}>
<ContentHeader <ContentHeader