Skip to content

Commit 08721b6

Browse files
author
Lucas Bento
authored
Merge pull request #349 from blakef/feat-add-anchor-links
feat: Add support for anchor links
2 parents 9847119 + 92ba445 commit 08721b6

File tree

5 files changed

+135
-38
lines changed

5 files changed

+135
-38
lines changed

src/components/common/Diff/Diff.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ const Diff = ({
157157
<DiffHeader
158158
oldPath={oldPath}
159159
newPath={newPath}
160+
fromVersion={fromVersion}
160161
toVersion={toVersion}
161162
type={type}
162163
diffKey={diffKey}

src/components/common/Diff/DiffHeader.js

Lines changed: 83 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { Fragment } from 'react'
1+
import React from 'react'
22
import styled from '@emotion/styled'
33
import { Tag, Button, Popover } from 'antd'
44
import {
@@ -7,6 +7,7 @@ import {
77
RightOutlined,
88
CopyOutlined,
99
RollbackOutlined,
10+
LinkOutlined,
1011
} from '@ant-design/icons'
1112
import { getFilePathsToShow } from '../../../utils'
1213
import { CopyToClipboard } from 'react-copy-to-clipboard'
@@ -57,6 +58,15 @@ const FileName = ({ oldPath, newPath, type, appName }) => {
5758
return <span>{newPath}</span>
5859
}
5960

61+
function generatePathId(oldPath, newPath) {
62+
const isMoved = oldPath !== newPath
63+
if (newPath === '/dev/null') {
64+
newPath = 'deleted'
65+
}
66+
const path = isMoved ? oldPath + '-' + newPath : oldPath
67+
return encodeURIComponent(path.replace(/[/\\]/g, '-'))
68+
}
69+
6070
const FileStatus = ({ type, ...props }) => {
6171
const colors = {
6272
add: 'blue',
@@ -154,6 +164,49 @@ const CopyPathToClipboardButton = styled(
154164
${defaultIconButtonStyle}
155165
`
156166

167+
const copyAnchorLinks = {
168+
default: 'Click to copy anchor link',
169+
copied: 'Anchor link copied!',
170+
}
171+
172+
const CopyAnchorLinksToClipboardButton = styled(
173+
({ id, type, onCopy, fromVersion, toVersion, ...props }) => {
174+
const [content, setContent] = React.useState(copyAnchorLinks.default)
175+
const resetContent = () => setContent(copyAnchorLinks.default)
176+
const onCopyContent = () => setContent(copyAnchorLinks.copied)
177+
178+
const url = React.useMemo(() => {
179+
const url = new URL(window.location)
180+
url.hash = id
181+
url.searchParams.set('from', fromVersion)
182+
url.searchParams.set('to', toVersion)
183+
return url.toString()
184+
}, [id])
185+
186+
return (
187+
<CopyToClipboard text={url} onCopy={onCopyContent}>
188+
<Popover
189+
content={content}
190+
trigger="hover"
191+
overlayStyle={{
192+
width: '175px',
193+
textAlign: 'center',
194+
}}
195+
>
196+
<Button
197+
{...props}
198+
type="ghost"
199+
icon={<LinkOutlined />}
200+
onMouseOver={resetContent}
201+
/>
202+
</Popover>
203+
</CopyToClipboard>
204+
)
205+
}
206+
)`
207+
${defaultIconButtonStyle}
208+
`
209+
157210
const CollapseClickableArea = styled.div`
158211
display: inline-block;
159212
&:hover {
@@ -182,6 +235,7 @@ const CollapseDiffButton = styled(({ open, isDiffCollapsed, ...props }) =>
182235
const DiffHeader = ({
183236
oldPath,
184237
newPath,
238+
fromVersion,
185239
toVersion,
186240
type,
187241
diffKey,
@@ -200,8 +254,13 @@ const DiffHeader = ({
200254
}) => {
201255
const sanitizedFilePaths = getFilePathsToShow({ oldPath, newPath, appName })
202256

257+
const id = React.useMemo(
258+
() => generatePathId(oldPath, newPath),
259+
[oldPath, newPath]
260+
)
261+
203262
return (
204-
<Wrapper {...props}>
263+
<Wrapper id={id} {...props}>
205264
<div>
206265
<CollapseClickableArea
207266
data-testid={testIDs.collapseClickableArea}
@@ -228,6 +287,12 @@ const DiffHeader = ({
228287
copyPathPopoverContent={copyPathPopoverContent}
229288
resetCopyPathPopoverContent={resetCopyPathPopoverContent}
230289
/>
290+
<CopyAnchorLinksToClipboardButton
291+
id={id}
292+
type={type}
293+
fromVersion={fromVersion}
294+
toVersion={toVersion}
295+
/>
231296

232297
<DiffCommentReminder
233298
comments={diffComments}
@@ -236,24 +301,22 @@ const DiffHeader = ({
236301
/>
237302
</div>
238303
<div>
239-
<Fragment>
240-
<ViewFileButton
241-
open={hasDiff && type !== 'delete'}
242-
version={toVersion}
243-
path={newPath}
244-
packageName={packageName}
245-
/>
246-
<DownloadFileButton
247-
open={!hasDiff && type !== 'delete'}
248-
version={toVersion}
249-
path={newPath}
250-
packageName={packageName}
251-
/>
252-
<CompleteDiffButton
253-
open={isDiffCompleted}
254-
onClick={() => onCompleteDiff(diffKey)}
255-
/>
256-
</Fragment>
304+
<ViewFileButton
305+
open={hasDiff && type !== 'delete'}
306+
version={toVersion}
307+
path={newPath}
308+
packageName={packageName}
309+
/>
310+
<DownloadFileButton
311+
open={!hasDiff && type !== 'delete'}
312+
version={toVersion}
313+
path={newPath}
314+
packageName={packageName}
315+
/>
316+
<CompleteDiffButton
317+
open={isDiffCompleted}
318+
onClick={() => onCompleteDiff(diffKey)}
319+
/>
257320
</div>
258321
</Wrapper>
259322
)

src/components/common/DiffViewer.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect, useRef } from 'react'
1+
import React, { useState, useEffect, useRef, useReducer } from 'react'
22
import styled from '@emotion/styled'
33
import { Alert } from 'antd'
44
import { motion, AnimatePresence, AnimateSharedLayout } from 'framer-motion'
@@ -31,6 +31,20 @@ const getDiffKey = ({ oldRevision, newRevision }) =>
3131

3232
const scrollToRef = (ref) => ref.current.scrollIntoView({ behavior: 'smooth' })
3333

34+
// Lazy loaded content won't respect anchor links in the URL, so we have to help
35+
// the viewport once we know our diff has loaded.
36+
const jumpToAnchor = (stopScrolling) => {
37+
if (!window.location.hash || stopScrolling) {
38+
return true
39+
}
40+
const ref = document.getElementById(window.location.hash.slice(1))
41+
if (!ref) {
42+
return true
43+
}
44+
ref.scrollIntoView()
45+
return true
46+
}
47+
3448
const DiffViewer = ({
3549
packageName,
3650
language,
@@ -111,6 +125,8 @@ const DiffViewer = ({
111125
localStorage.setItem('viewStyle', newViewStyle)
112126
}
113127

128+
const [, jumpToAnchorOnce] = useReducer(jumpToAnchor, false)
129+
114130
useEffect(() => {
115131
if (!isDone) {
116132
resetCompletedDiffs()
@@ -149,6 +165,7 @@ const DiffViewer = ({
149165
initial={{ opacity: 0, translateY: 75 }}
150166
animate={{ opacity: 1, translateY: 0 }}
151167
transition={{ duration: getTransitionDuration(0.5) }}
168+
onAnimationComplete={jumpToAnchorOnce}
152169
>
153170
<UsefulContentSection
154171
isLoading={isLoading}

src/components/common/VersionSelector.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ const getVersionsInURL = () => {
5858
}
5959
}
6060

61+
// Users making changes to version should not retain anchor links
62+
// to files that may or may not change.
63+
const stripAnchorInUrl = () => {
64+
if (window.location.hash) {
65+
const url = new URL(window.location)
66+
url.hash = ''
67+
window.history.pushState({}, '', url)
68+
}
69+
return true
70+
}
71+
6172
const compareReleaseCandidateVersions = ({ version, versionToCompare }) =>
6273
['prerelease', 'prepatch', null].includes(
6374
semver.diff(version, versionToCompare)
@@ -309,7 +320,9 @@ const VersionSelector = ({
309320
loading={isLoading}
310321
value={localFromVersion}
311322
options={fromVersionList}
312-
onChange={(chosenVersion) => setLocalFromVersion(chosenVersion)}
323+
onChange={(chosenVersion) =>
324+
stripAnchorInUrl() && setLocalFromVersion(chosenVersion)
325+
}
313326
/>
314327

315328
<ToVersionSelector
@@ -327,7 +340,9 @@ const VersionSelector = ({
327340
/>
328341
)
329342
}
330-
onChange={(chosenVersion) => setLocalToVersion(chosenVersion)}
343+
onChange={(chosenVersion) =>
344+
stripAnchorInUrl() && setLocalToVersion(chosenVersion)
345+
}
331346
/>
332347
</Selectors>
333348

src/utils/update-url.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,22 @@ export function updateURL({
77
fromVersion,
88
toVersion,
99
}) {
10-
const pageURL = window.location.href.replace(window.location.search, '')
10+
const url = new URL(window.location.origin)
11+
url.pathname = window.location.pathname
12+
url.hash = window.location.hash
1113

12-
const newURL =
13-
fromVersion !== '' || toVersion !== ''
14-
? `?from=${fromVersion}&to=${toVersion}`
15-
: '?'
16-
const packageNameInURL = isPackageNameDefinedInURL
17-
? `&package=${packageName}`
18-
: ''
19-
const languageInURL =
20-
packageName === PACKAGE_NAMES.RNW ? `&language=${language}` : ''
14+
if (fromVersion) {
15+
url.searchParams.set('from', fromVersion)
16+
}
17+
if (toVersion) {
18+
url.searchParams.set('to', toVersion)
19+
}
20+
if (isPackageNameDefinedInURL) {
21+
url.searchParams.set('package', packageName)
22+
}
23+
if (packageName === PACKAGE_NAMES.RNW) {
24+
url.searchParams.set('language', language)
25+
}
2126

22-
window.history.replaceState(
23-
null,
24-
null,
25-
`${pageURL}${newURL}${packageNameInURL}${languageInURL}`
26-
)
27+
window.history.replaceState(null, null, url.toString())
2728
}

0 commit comments

Comments
 (0)